-- 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.