-- Transactions>TransactionStateImpl.mesa  (last edited by Levin on August 26, 1982 11:05 am)
-- This module implements access to the transaction data base and operations on it.
-- This module runs at a level within Pilot below the FileManager and above SimpleSpace.  In fact, Log is only called from inside the FileImpl monitor.  (However, during initialization this module uses the facilities of the VMMgr and FileMgr.)

DIRECTORY
   Environment USING [Base, first64K],
   Inline USING [LowHalf],
   ResidentHeap USING [FreeNode, MakeNode],
   SimpleSpace USING [CopyOut, Create, Handle],
   Space USING [Handle, WindowOrigin],
   SpecialTransaction USING [OptionalCrash],
   Transaction USING [],
   TransactionInternal USING [Checksum, FilePageNumber, Handle, InitializeLogsA, InitializeLogsB, invalidTxEdition, LogInternal, nullTxIndex, State, StateData, StateEdition, stateCount, TxIndex, ValidTxEdition, ValidTxIndex],
   TransactionState USING [FileOps, LogEntryPtr, SpaceNode, SpaceNodePtr],
   Volume USING [InsufficientSpace],
   Zone USING [Status];

TransactionStateImpl: MONITOR
   IMPORTS Inline, ResidentHeap, SimpleSpace, TransactionInternal, Volume
   EXPORTS SpecialTransaction, Transaction, TransactionInternal, TransactionState =

BEGIN OPEN TransactionInternal;

--Transaction.--Handle: PUBLIC TYPE = TransactionInternal.Handle;

crashProc: SpecialTransaction.OptionalCrash ← NullProc;

--TransactionInternal.--disabled: PUBLIC BOOLEAN ← FALSE; -- TRUE causes all (public) transaction operations to no-op.

--Transaction.--InvalidHandle: PUBLIC ERROR = CODE;

--Transaction.--nullHandle: PUBLIC Handle ← [txEdition: invalidTxEdition, index: nullTxIndex];

--TransactionInternal.--state: PUBLIC LONG POINTER TO State;  -- (LOOPHOLE[state] is also a pointer to stateData)

txAllocRover: ValidTxIndex ← FIRST[ValidTxIndex];  -- wanders along looking for a free transaction.
--TransactionInternal.--state1Window, state2Window: PUBLIC Space.WindowOrigin;  -- the two representatives for the stable storage containing the state table.
--TransactionInternal.--stateSpace: PUBLIC SimpleSpace.Handle;  -- the working copy of the state in VM.

Bug: PRIVATE --PROGRAMMING-- ERROR [BugType] = CODE;
BugType: TYPE = {freeTransButFileInUse, funnyCase, invalidTransaction, operationsNotClosed, spaceAlreadyInTransaction, spaceNotInTransaction, tooManyTransactions};

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Initialization
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

--TransactionState.--InitializeStateA: PUBLIC ENTRY PROCEDURE [] =
   -- (stateSpace must be a SimpleSpace because Log can be called from within the VMMgr monitor.)
   { stateSpace ← SimpleSpace.Create[stateCount, hyperspace];  -- (may want swap units someday.)
      TransactionInternal.InitializeLogsA[] };

--TransactionState.--InitializeStateB: PUBLIC ENTRY PROCEDURE [] =
   { TransactionInternal.InitializeLogsB[];
      UpdateStateFile[]  };  -- (now that all initialization and crash recovery has been done to State.)

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Monitor External Procedures
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

--TransactionInternal.--MaybeCrash: PUBLIC SpecialTransaction.OptionalCrash =
   {crashProc[unimportance]};

--SpecialTransaction.--NullProc: PUBLIC SpecialTransaction.OptionalCrash = {};

--SpecialTransaction.--SetCrashProcedure: PUBLIC PROCEDURE [optionalCrash: SpecialTransaction.OptionalCrash] =
   {crashProc ← optionalCrash};

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Monitor Entry Procedures
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

--TransactionState.--AddToTransaction: PUBLIC ENTRY PROCEDURE
	 [transaction: TransactionInternal.Handle, space: Space.Handle] =
   BEGIN
   txi: TxIndex;
   spNode: TransactionState.SpaceNodePtr;
   IF ~((txi ← transaction.index) IN ValidTxIndex) OR transaction.txEdition ~= state[txi].txEdition THEN
	 RETURN WITH ERROR InvalidHandle;
   WITH trans: state[txi] SELECT FROM
      active =>
	 BEGIN
	 IF trans.noMoreOperations THEN RETURN WITH ERROR InvalidHandle;
	 FOR spNode ← trans.spaceList, spNode.nextSpace UNTIL spNode=NIL DO  -- (for debugging only)
	   IF spNode.handle=space THEN Bug[spaceAlreadyInTransaction];
	   ENDLOOP;
	 spNode ← first64K.NEW[TransactionState.SpaceNode];
	 spNode.handle ← space; spNode.nextSpace ← trans.spaceList;  -- (spNode.window is just scratch storage)
	 trans.spaceList ← spNode;
	 END;
      ENDCASE => RETURN WITH ERROR InvalidHandle;
   END;

--Transaction.--Begin: PUBLIC ENTRY SAFE PROCEDURE RETURNS [Handle] = TRUSTED
   -- Finds a free transaction, makes it active, updates State.
   -- No log file is allocated at this time because client may just be allocating a handle to reserve for special uses.  Star does this.
   BEGIN
   examLimit: CARDINAL = LAST[ValidTxIndex] - FIRST[ValidTxIndex] + 1;
   examined: CARDINAL ← 0;
   IF disabled THEN RETURN[nullHandle];  -- (this will cause Space and File ops to no-op.)
   UNTIL state[txAllocRover].status = free DO
      txAllocRover ← IF txAllocRover < LAST[ValidTxIndex] THEN SUCC[txAllocRover] ELSE FIRST[ValidTxIndex];
      IF (examined ← SUCC[examined]) >= examLimit THEN Bug[tooManyTransactions];
      ENDLOOP;
   state[txAllocRover].vp ← active[
      noMoreOperations: FALSE,
      committed: FALSE,
      spaceList: NIL,
      pageFree: FIRST[FilePageNumber],
      countPrevLogEntry: 0  ];
   IF state[txAllocRover].fileInUse THEN Bug[freeTransButFileInUse];
   UpdateStateFile[];  -- now the transaction has officially begun.
   RETURN[ TransactionInternal.Handle[txEdition: state[txAllocRover].txEdition, index: txAllocRover] ];
   END;

--TransactionState.--RecordCommit: PUBLIC ENTRY PROCEDURE [transaction: TransactionInternal.Handle] = 
   -- All updates to client files must have been forced out to the disk before this routine is called!
   BEGIN
   txi: TxIndex;
   IF ~((txi ← transaction.index) IN ValidTxIndex) OR transaction.txEdition ~= state[txi].txEdition THEN Bug[invalidTransaction];
   WITH trans: state[txi] SELECT FROM
      active =>
	 { IF ~trans.noMoreOperations THEN Bug[operationsNotClosed];
	    trans.committed ← TRUE;  UpdateStateFile[]  };  -- if we crash after this, the undo log will be ignored.
      ENDCASE => Bug[funnyCase];
   END;

Log: PUBLIC ENTRY PROCEDURE [transaction: TransactionInternal.Handle, op: TransactionState.LogEntryPtr,
		   fileOps: TransactionState.FileOps] =
   -- May raise Volume.InsufficientSpace.
   BEGIN
   txi: TxIndex;
   IF ~((txi ← transaction.index) IN ValidTxIndex) OR transaction.txEdition ~= state[txi].txEdition THEN
	 RETURN WITH ERROR InvalidHandle;
   WITH trans: state[txi] SELECT FROM
      active =>
	 { IF trans.noMoreOperations THEN RETURN WITH ERROR InvalidHandle;
	    TransactionInternal.LogInternal[txi, op, fileOps
		    ! Volume.InsufficientSpace => GO TO VolInsuffSpace ] };
      ENDCASE => RETURN WITH ERROR InvalidHandle;
   EXITS
   VolInsuffSpace => RETURN WITH ERROR Volume.InsufficientSpace;
   END;

--TransactionState.--NoMoreOperations: PUBLIC ENTRY PROCEDURE [transaction: TransactionInternal.Handle]
	      RETURNS [spaceList: TransactionState.SpaceNodePtr] =
   -- Checks out this transaction to Abort or Commit.  Further client operations will raise InvalidHandle.
   BEGIN
   txi: TxIndex;
   IF ~((txi ← transaction.index) IN ValidTxIndex) OR transaction.txEdition ~= state[txi].txEdition THEN
	 RETURN WITH ERROR InvalidHandle;
   WITH trans: state[txi] SELECT FROM
      active =>
	 { IF trans.noMoreOperations THEN RETURN WITH ERROR InvalidHandle;
	    trans.noMoreOperations ← TRUE;
	    RETURN[trans.spaceList] };
      ENDCASE => RETURN WITH ERROR InvalidHandle;
   END;

--TransactionState.--ReleaseTransaction: PUBLIC ENTRY PROCEDURE [transaction: TransactionInternal.Handle] = 
   BEGIN
   txi: TxIndex;
   IF ~((txi ← transaction.index) IN ValidTxIndex) OR transaction.txEdition ~= state[txi].txEdition THEN Bug[invalidTransaction];
   WITH trans: state[txi] SELECT FROM
      active =>
	 BEGIN
	 space, spaceNext: TransactionState.SpaceNodePtr;
	 IF ~trans.noMoreOperations THEN Bug[operationsNotClosed];
	 FOR space ← trans.spaceList, spaceNext UNTIL space=NIL DO
	   spaceNext ← space.nextSpace;
	   first64K.FREE[@space]
	   ENDLOOP;
	 trans.txEdition ← IF trans.txEdition < LAST[ValidTxEdition] THEN SUCC[trans.txEdition] ELSE FIRST[ValidTxEdition];  -- makes the previous tx handle obsolete.
	 state[txi].vp ← free[];
	 -- UpdateStateFile[];  ++ updating here would cost extra disk writes now, but save extra work during Crash Recovery.
	 END;
      ENDCASE => Bug[funnyCase];
   END;

--TransactionState.--WithdrawFromTransaction: PUBLIC ENTRY PROCEDURE [transaction: TransactionInternal.Handle, space: Space.Handle] =
   BEGIN
   txi: TxIndex;
   sNodePrevP: LONG POINTER TO TransactionState.SpaceNodePtr;
   sNode: TransactionState.SpaceNodePtr;
   IF ~((txi ← transaction.index) IN ValidTxIndex) OR transaction.txEdition#state[txi].txEdition THEN RETURN WITH ERROR InvalidHandle;
   WITH trans: state[txi] SELECT FROM
      active =>
	 BEGIN
	 IF trans.noMoreOperations THEN RETURN WITH ERROR InvalidHandle;
	 sNodePrevP ← @trans.spaceList;
	 FOR sNode ← sNodePrevP↑, sNode.nextSpace UNTIL sNode=NIL DO
	   IF sNode.handle=space THEN EXIT;
	   sNodePrevP ← @sNode.nextSpace;
	   REPEAT FINISHED => Bug[spaceNotInTransaction];
	   ENDLOOP;
	 sNodePrevP↑ ← sNode.nextSpace;
	 first64K.FREE[@sNode];
	 END;
      ENDCASE => RETURN WITH ERROR InvalidHandle;
   END;

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Monitor Internal Procedures
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

--TransactionInternal.--UpdateStateFile: PUBLIC INTERNAL PROCEDURE [] =
   -- Marks all transactions with the stamp of the (incremented) current edition of State, then writes two copies of the state data on the disk.  In the event of a crash while writing the two copies, the edition info is used by Crash Recovery to determine which copy is the most-recent and correct copy.
   BEGIN
   pStateData: LONG POINTER TO StateData = LOOPHOLE[state];
   currentStateEdition: StateEdition ← state[ FIRST[ValidTxIndex] ].stateEdition;  -- (any one will do)
   currentStateEdition ← IF currentStateEdition < LAST[StateEdition] THEN SUCC[currentStateEdition] ELSE FIRST[StateEdition];
   FOR txi: TxIndex IN TxIndex DO
      state[txi].stateEdition ← currentStateEdition;
      ENDLOOP;
   pStateData.checksumData ← Checksum[0, SIZE[State], @pStateData.state];
   MaybeCrash[3];
   SimpleSpace.CopyOut[stateSpace, state1Window];
   MaybeCrash[2];
   SimpleSpace.CopyOut[stateSpace, state2Window];
   MaybeCrash[4];
   END;

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Allocator for SpaceNodes
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

-- The procedures here have no particular relation to Monitors;  They may be called from inside or outside any monitor.

Procs: TYPE = MACHINE DEPENDENT RECORD [
   Make (0): PROCEDURE [UNCOUNTED ZONE, CARDINAL] RETURNS [LONG POINTER],
   Free (1): PROCEDURE [UNCOUNTED ZONE, LONG POINTER]];

uncountedZoneObject: MACHINE DEPENDENT RECORD [
   procs (0:0..31): LONG POINTER TO Procs,
   data (2:0..31): LONG POINTER --to instance state, if any-- ]
      ← [procs: @procs, data: NIL --never used--];

procs: Procs ← [Make: MakeNode, Free: FreeNode];

first64K: UNCOUNTED ZONE ← LOOPHOLE[LONG[@uncountedZoneObject]];

ResidentHeapProblem: ERROR = CODE;

MakeNode: PROCEDURE [z: UNCOUNTED ZONE, n: CARDINAL] RETURNS [LONG POINTER] =
   BEGIN
   r: Environment.Base RELATIVE POINTER;
   status: Zone.Status;
   [node: r, s: status] ← ResidentHeap.MakeNode[n];
   IF status~=okay THEN ERROR ResidentHeapProblem;
   RETURN[@Environment.first64K[r]]
   END;

FreeNode: PROCEDURE [z: UNCOUNTED ZONE, p: LONG POINTER] =
   BEGIN
   status: Zone.Status ← ResidentHeap.FreeNode[LOOPHOLE[Inline.LowHalf[p]]];
   IF status~=okay THEN ERROR ResidentHeapProblem;
   END;

END.


LOG

May 8, 1980  1:21 PM	Gobbel	Create file.
July 19, 1980  1:43 PM	McJones	Space.Copy=>CopyOut.  export Transaction.Handle.
July 22, 1980  4:13 PM	Gobbel	Make it real.
September 2, 1980  3:50 PM	Knutsen	Major cleanup.  Split code out from TransactionImpl.
September 8, 1980  6:35 PM	Gobbel	Added WithdrawFromTransaction.
September 25, 1980  12:40 PM	Gobbel	Added SpecialTransaction ops.
January 7, 1981  4:52 PM	Knutsen	Cleanup error codes.
June 1, 1982 2:47 pm  Levin  x ~IN y => ~(x IN y)
August 26, 1982 11:05 am	Levin	Make things SAFE.