MODULE fmm$rollback_example;
?? RIGHT := 110 ??
{  This discusses rollback as it relates to active_job_recovery.
{  1. What is rollback?
{    Rollback refers to the process of backing out of a process
{    following a system failure, and to allow retrying of that operation.
{    More specifically in the file system it involves
{    leaving both the mainframe and the job tables in a consistant state,
{    specifically sfid's (system file id) of
{    a permanent file or catalog will be wrong.  The file manager ONLY
{    has to rollback those operations involving permanent files, as this
{    the only case where the mainframe and job tables can be out of sync.
{
{    The code involving permanent files or catalogs can not continue without
{    rollback since the segments associated with catalogs would be gone.
{    Following a system crash the system wide tables are re-initialized
{    but the job tables are retained.  The rollback process must undo
{    what was ever going on by backing out of the operation.  Table locks
{    must be cleared, etc.  The rollback must proceed such that
{    the fmp$recover_job_files process may execute and not worry about
{    entries locked, or half updated tables.  After the fmp$recover_job_files
{    executes, the processing continues to the ring 3 (or above caller)
{    who must be allowed to retry the operation.
{    For example assume the follwing structure.
{    PROCEDURE [XDCL, #GATE] pfp$r3_attach ...  (RING 3)
{
{       REPEAT
{          pfp$r2_attach (...
{       UNTIL status.normal OR (status.condition <> ose$job_recovery_condition);
{
{
{        PROCEDURE [XDCL] pfp$r2_attach (....   (RING 2)
{           This must establish a condition handler to
{            rollback in case of a job recovery condition occuring
{
{    If the job recovery condition arises while pfp$r2_attach is executing
{    it must be prepared to back out of what it was doing.
{    Upon return from pfp$r2_attach, and prior to pfp$r3_attach
{    executing the fmp$recover_job_files procedure is executed.
{    A few notes on condition handlers:
{    .  If a condition_handler returns abnormal status the
{       task aborts
{    .  IF a condition occurs, and any where in the calling sequence
{       (within the current ring) there is a condition handler it will get
{       immediate control.
{    .  If the current handler calls pmp$continue_to_cause and there
{       are no more handlers, control will return to this handler.
{    . If there is NO condition handler what will happen on the job
{       recovery condition. DAVE ?????????????????????
{
{ 2.  WHAT needs to rollback?
{     FM - As stated above those file manager operatations involving
{     permanent files will need a condition handler and rollback.
{        - fmp$attach_file
{        - return_attached_file
{     DAVE- Will the other FM operations need a condition handler that
{       will allow the processing to continue unaltered.????????
{     PF - all permanent file operations will need a rollback.
{        - Those operations that are catalog only, will only really
{          need to retry the operation.  The catalog changes will be lost
{          or not.  How will you know if the catalog changes were
{          completed or not????
{          Can we asume if the catalog is closed that the change was complete??
{        - Those operations involving permanent files will need to undo
{          the file manager operation and the attached_pf_table.
{          -  pfp$r2_attach
{          -  pfp$r2_define, pfp$r2_define_data
{          -  pfp$return_permanent_file
{          -  pfp$save_file_label
{      DF - File server
{       The file server queues are not recovered. Any request that is referencing
{       data structures in server wired must be prepared to rollback.
{       Most of the rollback for server could depend on users effectively
{       waiting for server_not_active.
{
{ 3. SCHEDULING
{   Rollback is probably alot of work.
{
{ 4. ROLLBACK TECHNIQUES
{   This discusses some general principals of rolling back.
{   . A general model for rollback is sort of a state table
{     (decision table).  Since the number of rules in a decision table
{     is 2 ** number_of_conditions, a goal should be to keep the number
{     of conditions that have to be rolled back to a minimum.
{     Also try to keep the conditions independant.  IF each condition
{     can be undone regardless of the state of neighboring conditions
{     the number of rules approaches the number of conditions.
{   . The number of conditions can be kept down by:
{      -Use a local copy of the table entry being updated.  Make the update
{       of the actual table in one step. See the example of setting
{       lint_entry.record_state.  The table is thus always in a known
{       state. That is, you know if the update has completed or not,
{       The record itself could be queuried.  If needed the procedure
{       keeps a copy of the entry as it was prior to any updating.
{      -Do not repeat code, either force the code to all flow through the
{       same path, or put the code in  a procedure.
{   . When the condition occurs the condition handler must determine the
{     the "state" of the code at the time of the crash.  This may be done by
{       - Dynamically determining the condition state:
{         example osp$test_signature_lock (see below)
{       - Keep track of state by use of state variable available in the
{         condition handler.
{        If this technique is employed #SPOIL should be done on the state
{         variable to force it to be written to memory.
{       - See example below of determining whether a procedure has been
{         called or not.
{
{  5.RANDOM NOTES:
{   . Rollback should follow normal structured programming rules.  That is
{     put the condition handler in the procedure that is establishing the
{     state that needs to be done.
{     Example:
{       pfp$r2_attach
{         fmp$attach_file
{           fmp$process_pt_request
{              Put condition handler in here to undo any table locks
{              osp$set_signature_lock
{     The problem with this is that the fmp$process_pt_request is a shared
{       interface between interfaces requiring rollback and those that do not.
{       While the PF calling sequence will want to return and retry at
{       the outermost level teh non-pf caller will want an immediate retry.
{       Example:
{         REPEAT
{           fmp$process_pt_request  (...., status);
{         UNTIL (status.normal) OR (status.condition = ose$job_recovery_complete
{
{    . On interfaces that are designed to undo an operation, provide
{      as much verification of the call as possible.
{      Example fmp$undo_attach_file  (lfn, apfid, gfn, sfid,...
{           Where apfid, gfn, and sfid are really only used to verify
{           the caller.
{
{   . You can NOT always assume a cybil assignment (:=) statement
{     always complete or not.  Where the structure is > 256 bytes the
{     assignement loops, so the possibility of a half updated record
{     exists.  Boolean,s pointers, and ordinals will probably either complete
{     or not.
{
{   . An operation that takes in a "work list" should also probably return
{     a work result if the operation is one that needs to be rolled back.
{     An example of this might be fmp$locate_lnt_entry
{
{   . Its OK to loose a few allocated chunks of memory.
{
{    . There are probably two classes of permanent file requests.
{      i. Those that can be restarted without any work.
{         An example of this is display_catalog
{      ii. Those that are not restartable.
{         An example of this is attach_file.
{       Those requests that are restartable should just be able to
{         pass back ose$volume_unavailable since most callers are
{        prepared to wait and retry.
{
{ 6. QUESTIONS.
{  A.   If the code was executing in ring 2, and no condition handler was
{    established, does that code continue executing??
{    Upon return to its outer ring caller, fmp$recover_job_files would
{    be invoked.  (DAVE is that ^ sentence true????????????)
{       (or must must there be a condition handler which does nothing
{       but return NORMAL status so the task will resume executing as is.
{    It would be easiest if ring 2 code without condition handlers could
{    just continue executing.
{  B.  If the process had just completed a catalog operation, and was now
{    doing something that assumed that catalog operation was done
{    We cannot say anything about the catalog operation.  In a system
{    crash recovery will write unlocked modified pages from the image file.
{    If we are not recovering with an image file we can not say anything
{    about the catalog, since we do not wait for the write_modified pages
{    on the unlock.


?? PUSH (LISTEXT := ON) ??
*copyc osd$default_pragmats
*copyc osp$establish_condition_handler
*copyc pmp$establish_condition_handler
*copyc pmp$continue_to_cause
*copyc osp$set_status_abnormal
*copyc pmt$program_parameters
*copyc clp$put_job_output
*copyc ost$wait
*copyc ost$signature_lock

  PROCEDURE osp$set_signature_lock (VAR lock: ost$signature_lock;
        wait: ost$wait;
    VAR status: ost$status);
    status.normal := TRUE;
    lock.lock_id := 1;
    RETURN;
  PROCEND osp$set_signature_lock;

  PROCEDURE osp$clear_signature_lock (VAR lock: ost$signature_lock;
    VAR status: ost$status);
    status.normal := TRUE;
    lock.lock_id := 0;
    RETURN;
  PROCEND osp$clear_signature_lock;


  PROCEDURE fmp$locate_lint_entry (VAR lint_entry: ^lint_record);

  PROCEND;


  PROCEDURE osp$test_signature_lock (VAR lock: ost$signature_lock;
    VAR lock_status: ost$signature_lock_status;
    VAR status: ost$status);
    status.normal := TRUE;
    IF lock.lock_id = 0 THEN
      lock_status := osc$sls_not_locked;
    ELSEIF lock.lock_id = 1 THEN
      lock_status := osc$sls_locked_by_current_task;
    ELSE
      lock_status := osc$sls_locked_by_another_task;
    IFEND;
  PROCEND osp$test_signature_lock;
*copyc pmp$cause_condition

  CONST
    ose$not_initiated_yet = 567,
    ose$job_recovery_condition = 569,
    ose$processing_started = 568;

?? POP ??

?? EJECT ??

  TYPE
    lint_record  = record
      case record_state: (free_record, record_update_in_progress,
        normal_record) of
      = record_update_in_progress, normal_record =
        lint_field_1: integer,
        lint_field_2: string (256),
        lint_field_3: string (256),
        line_field_4: integer,
      = free_record =
        ,
      casend,
    recend;

  CONST
    job_recovery_condition = 'JOB_RECOVERY                   ';

  VAR
    lock1,
    lock3,
    lock2: ost$signature_lock := [0];


?? TITLE := '*** fmp$gizmo ***', EJECT ??

  PROCEDURE [XDCL, #GATE] fmp$gizmo (p: pmt$program_parameters;
    VAR status: ost$status);

    VAR
      lint_entry: ^lint_record,
      lint_record_update_started: boolean,
      lint_entry_as_it_was: lint_record,
      fmp$gizmo_child_status: ost$status,
      local_status: ost$status,
      lock_one_set: boolean,
      conds: pmt$condition,
      ed: pmt$established_handler;

?? NEWTITLE := '   *** rollback_gizmo ***', EJECT ??

    PROCEDURE rollback_gizmo (condition: pmt$condition;
          cd: ^pmt$condition_information;
          sa: ^ost$stack_frame_save_area;
      VAR ch_status: ost$status);

      VAR
        lock_status: ost$signature_lock_status,
        ignored_status: ost$status;

      ch_status.normal := TRUE;

{     Cleanup all "states" sets in fmp$gizmo
{
{  Note comment on window on boolean lock_one_set
      IF lock_one_set THEN
        clp$put_job_output (' clear lock 1', ignored_status);
        osp$clear_signature_lock (lock1, ignored_status);
      IFEND;

{
{ 1. This is an example of dynamically determining the state of the procedures
{  processing.
      osp$test_signature_lock (lock2, lock_status, ignored_status);
      IF lock_status = osc$sls_locked_by_current_task THEN
        clp$put_job_output (' clear lock 2', ignored_status);
        osp$clear_signature_lock (lock2, status);
      ELSE
        clp$put_job_output (' dont clear lock 2', ignored_status);
      IFEND;

      osp$test_signature_lock (lock3, lock_status, ignored_status);
      IF lock_status = osc$sls_locked_by_current_task THEN
        clp$put_job_output (' clear lock 3', ignored_status);
        osp$clear_signature_lock (lock3, status);
      ELSE
        clp$put_job_output (' dont clear lock 3', ignored_status);
      IFEND;

{
{ 2. This is an example of using the status condition of a child
{      procedure to determine what needs to be done.  This assumes
{      the child has cleaned up after itself.
      IF fmp$gizmo_child_status.normal THEN
        { fmp$gizmo_child completed successfully
        { undo fmp$gizmo_child (There should be an opposite procedure
        { to call.
      ELSE
        CASE fmp$gizmo_child_status.condition OF
        = ose$not_initiated_yet =
          { fmp$gizmo_child has not been called yet
        = ose$processing_started =
          { JOB RECOVERY ERROR
          { fmp$gizmo_child job recovery condition should handle this
        = ose$job_recovery_condition =
          { The fmp$gizmo_child has detected and rolled back the condition
          { All is OK
        ELSE
          { Another error occurred in fmp$gizmo_child (eg. lfn_in_use)
          { act based on that error
        CASEND;
      IFEND;

{ 3. Demonstrate use of the tri-state record state and use
{    of saving the old record as it was.
      IF lint_record_update_started THEN
        CASE lint_entry^.record_state OF
        = record_update_in_progress =
          lint_entry^ := lint_entry_as_it_was;
        = free_record =
          { Record update not started
        = normal_record =
          { Record update has been completed!
          { Use normal cleanup procedures
        CASEND;
      IFEND;


{
{  Set this status now so that the caller may either retry this operator, or
{ the callers condition handler may know that this procedure has rolled back.
      osp$set_status_abnormal ('FM', ose$job_recovery_condition, 'fmp$gizmo',
            status);
      pmp$continue_to_cause (pmc$inhibit_standard_procedure, ignored_status);
      EXIT fmp$gizmo;
    PROCEND rollback_gizmo;
?? OLDTITLE ??
?? EJECT ??
{  Set up "states" prior to establishing a condition handler
    lock_one_set := FALSE;
    local_status.normal := TRUE;
    lint_record_update_started := FALSE;
    fmp$gizmo_child_status.normal := FALSE;
    fmp$gizmo_child_status.condition := ose$not_initiated_yet;
    #SPOIL (local_status.normal, lint_record_update_started,
          fmp$gizmo_child_status, fmp$gizmo_child_status);

{  Establish condition handler
    conds.selector := pmc$user_defined_condition;
    conds.user_condition_name := job_recovery_condition;
    pmp$establish_condition_handler (conds, ^rollback_gizmo, ^ed,
          local_status);
    IF NOT local_status.normal THEN
      status := local_status;
      RETURN;
    IFEND;

{  set this procedures condition to indicate work has started
    #SPOIL (status);
    status.condition := ose$processing_started;
    status.normal := FALSE;
    #SPOIL (status);

{  Begin procedure work, using local_status
    osp$set_signature_lock (lock1, osc$wait, local_status);
{ eeek! a window! This is an example to why the following techniques are better
{  - Dynamically determining the state (Example osp$test_signature_lock)
{  - Using the status.condition field to allow the child to set " complete"
{     (as in fmp$gizmo_child example
    #SPOIL (lock_one_set);
    lock_one_set := TRUE;
    #SPOIL (lock_one_set);

    osp$set_signature_lock (lock2, osc$wait, local_status);
    IF NOT local_status.normal THEN
      status := local_status;
      RETURN;
    IFEND;

{ emulate lock 3 set by another task
    lock3.lock_id := 20;

    fmp$gizmo_child (fmp$gizmo_child_status);

    fmp$locate_lint_entry (lint_entry);
    lint_record_update_started := TRUE;
    lint_entry_as_it_was := lint_entry^;
    lint_entry^.record_state := record_update_in_progress;
    lint_entry^.lint_field_1 := 12;
    lint_entry^.lint_field_2 := ' If you can imagine it';

    { Make the final update in one step that will either complete
    { or NOT (as opposed to half updated
    lint_entry^.record_state := normal_record;


{  pmp$cause_condition used for testing only
{    pmp$cause_condition (job_recovery_condition, NIL, local_status);
    status := local_status;
  PROCEND fmp$gizmo;
?? EJECT ??

  PROCEDURE fmp$gizmo_child (VAR status: ost$status);

{ 1. Set up states
{ Set any "Grandchildren status to ose$not_initiated_yet

{ 2. Establish handler for job recovery condition
{ This condition handler should roll back and return the status condition
{ ose$Job_recovery_condition
{ pmp$establish_condition_handler (rollback_fmp$gizmo_child

{ 3.Set status to starting
    status.condition := ose$processing_started;
    status.normal := FALSE;

{ 4.
{ DO PROCEDURE SUBPROCESSING

{ 5. Complete
    status.normal := TRUE;
  PROCEND fmp$gizmo_child;

?? TITLE := ' ANOTHER EXAMPLE ', EJECT ??
?? EJECT ??
  PROCEDURE [XDCL, #GATE] dfp$rollback_example
    (VAR status: ost$status);


{ PURPOSE:
{   This condition handler is used around the code that is referencing file server wire
{   the case of job recovery.  The file server queues are kept in server wired and the queue definition is not
{   recovered.  If the job is recovered while it is doing dfp$begin_remote_procedur_call
{   the condition handler will perform a non-local exit with an abnormal status of
{   DFE$SERVER_NOT_ACTIVE - almost all callers are prepared for this.
{

    PROCEDURE job_recovery_condition_handler
      (    condition: pmt$condition;
           condition_information: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR handler_status: ost$status);

      VAR
        display_status: ost$status;

      IF (condition.selector = pmc$user_defined_condition) AND (condition.user_condition_name =
            'OSC$JOB_RECOVERY') THEN
        osp$set_status_abnormal ('DF', 123 {dfe$server_not_active} , ' job recovery - begin_rpc  ',
              status);
        EXIT dfp$rollback_example;
      IFEND;

      pmp$continue_to_cause (pmc$execute_standard_procedure, handler_status);
      IF NOT handler_status.normal THEN
        RETURN;
      IFEND;

    PROCEND job_recovery_condition_handler;


    osp$establish_condition_handler (^job_recovery_condition_handler, FALSE);

    { Do other stuff as required.



  PROCEND dfp$rollback_example;

MODEND fmm$rollback_example;
