-- Transactions>TransactionImpl.mesa -- Last edited by Levin on August 26, 1982 11:04 am -- This module implements high-level transaction operations: -- Commit, Abort, Crash Recovery. Serial access to individual transactions -- is accomplished by checking-out a transaction from TransactionStateImpl. -- This module runs at a level within Pilot above the VMMgr. DIRECTORY Environment USING [wordsPerPage], File USING [ Capability, Create, Delete, Error, GetSize, MakeImmutable, MakePermanent, Move, nullCapability, nullID, PageCount, SetSize, Unknown], Inline USING [LongCOPY], KernelFile USING [ CreateWithID, ExistingFile, GetRootFile, MakeMutable, MakeTemporary], KernelSpace USING [ReleaseFromTransaction], PilotFileTypes USING [tTransactionStateFile], PilotMP USING [cTransactionCrashRecovery], PilotSwitches USING [switches --.x--], ProcessorFace USING [SetMP], Runtime USING [CallDebugger, IsBound], SimpleSpace USING [Kill, Map, Unmap], Space USING [ CopyOut, Create, Delete, defaultBase, defaultWindow, Error, ForceOut, GetAttributes, GetWindow, Handle, Kill, LongPointer, LongPointerFromPage, Map, PageCount, PageNumber, PageOffset, Unmap, virtualMemory, VMPageNumber, WindowOrigin], SpecialTransaction USING [ClientStart], StoragePrograms USING [], TemporaryTransaction USING [], Transaction USING [Handle], TransactionExtras USING [], TransactionInternal USING [ Checksum, countLogEntry, disabled, FilePageCount, FilePageNumber, Handle, LogFileEntry, LogFileEntryData, LogFileEntryPtr, logFileFormatVersion, MaybeCrash, permissions, ShortenPageCount, standardLogFileCount, state, State, state1Window, state2Window, stateCount, StateData, StateEdition, stateFormatVersion, stateSpace, TxDescPtr, TxIndex, ValidTxEdition, ValidTxIndex], TransactionState USING [ InitializeStateA, InitializeStateB, LogOp, NoMoreOperations, RecordCommit, ReleaseTransaction, SpaceNodePtr], Utilities USING [LongPointerFromPage], Volume USING [systemID, Unknown]; TransactionImpl: MONITOR -- This module's monitor is only used to control access to the two utility -- spaces used by Abort and crash recovery. Access to individual -- transactions is controlled by the monitor of TransactionStateImpl. IMPORTS File, Inline, KernelFile, KernelSpace, PilotSwitches, ProcessorFace, Runtime, SimpleSpace, Space, SpecialTransaction, TransactionInternal, TransactionState, Utilities, Volume EXPORTS StoragePrograms, TemporaryTransaction, Transaction, TransactionExtras SHARES File --USING [permissions]-- = BEGIN OPEN TransactionInternal; --Transaction.--Handle: PUBLIC TYPE = TransactionInternal.Handle; logSpaceSize: Space.PageCount _ 500; -- arbitrary number, big enough to avoid doing lots of Maps and Unmaps -- on logSpace entrySpace, logSpace, clientSpace: Space.Handle; -- for Abort processing. logZeroSpace: Space.Handle; -- for zeroing out log files logZeroData: LONG POINTER; -- to logZeroSpace BugType: TYPE = { xxx, abortFileError, abortSpaceError, illegalLogOperation, inactiveTransaction, invalidLogEntry, stateDataNotEqualToState, unexpectedErrorRestoringSpaces, unexpectedErrorValidateLogFile}; -- BugType: TYPE = {commitSpaceError, crashRecoveryFileError, crashRecoverySpaceError, illegalLogOperation, impossibleStateInconsistency, invalidLogHandle, invalidVersionSeal, logAlreadyAllocated, logFileDisappeared, logFileVolumeError, missingPinnedPageGroup, remoteLogFile, spaceAlreadyInTransaction, tooManyTransactions}; Bug: PRIVATE --PROGRAMMING--ERROR [BugType] = CODE; --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Monitor External Procedures --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --TemporaryTransaction.--DisableTransactions: PUBLIC PROCEDURE = {disabled _ TRUE}; Abort: PUBLIC SAFE PROCEDURE [transaction: TransactionInternal.Handle] = TRUSTED BEGIN -- Save the windows that the spaces are currently mapped to, and unmap them: spaceList: TransactionState.SpaceNodePtr; IF disabled THEN RETURN; spaceList _ TransactionState.NoMoreOperations[transaction]; -- may raise InvalidHandle FOR space: TransactionState.SpaceNodePtr _ spaceList, space.nextSpace UNTIL space = NIL DO space.window _ Space.GetWindow[space.handle ! -- (note that GetWindow will return defaultWindow if the space is -- mapped as a data space. We will not do anything to that space -- during Abort processing.) Space.Error, File.Unknown, Volume.Unknown => {space.window _ Space.defaultWindow; CONTINUE}]; -- we assume client deleted the space or the file. IF space.window # Space.defaultWindow THEN Space.Unmap[space.handle ! Space.Error => {space.window _ Space.defaultWindow; CONTINUE}]; ENDLOOP; -- Back out the changes that have been made to the client's files: RestoreFilesInTransaction[transaction.index]; -- Map spaces back to their windows and deallocate the space list elements: FOR space: TransactionState.SpaceNodePtr _ spaceList, space.nextSpace UNTIL space = NIL DO IF space.window # Space.defaultWindow THEN Space.Map[space.handle, space.window ! Space.Error, File.Unknown, Volume.Unknown => CONTINUE; -- we assume client deleted the space -- or the file was deleted in the course of Abort processing. ANY => ERROR Bug[unexpectedErrorRestoringSpaces]]; -- client messing with files during transaction! KernelSpace.ReleaseFromTransaction[space.handle, transaction ! Space.Error => CONTINUE]; ENDLOOP; ReleaseLog[transaction.index]; TransactionState.ReleaseTransaction[transaction]; END; -- Note: Begin[] is implemented in TransactionStateImpl. Commit: PUBLIC SAFE PROCEDURE [transaction: TransactionInternal.Handle] = TRUSTED BEGIN -- At present, all transaction space and file operations use an "undo log", -- so Commit is very simple: force out all mapped spaces, record the commit -- in the Transaction State file, then clear the log file, then free the tx -- entry in the State. spaceList: TransactionState.SpaceNodePtr; IF disabled THEN RETURN; spaceList _ TransactionState.NoMoreOperations[transaction]; -- may raise InvalidHandle FOR space: TransactionState.SpaceNodePtr _ spaceList, space.nextSpace UNTIL space = NIL DO Space.ForceOut[space.handle ! Space.Error => CONTINUE]; -- If error, we assume client deleted the space. KernelSpace.ReleaseFromTransaction[ space.handle, transaction ! Space.Error => CONTINUE]; ENDLOOP; TransactionState.RecordCommit[transaction]; -- now the log file will be ignored if we crash. ReleaseLog[transaction.index]; TransactionState.ReleaseTransaction[transaction]; END; --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Initialization and Crash Recovery --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --StoragePrograms.--InitializeTransactionData: PUBLIC PROCEDURE[] = -- Initializes nullHandle exported variable, creates SimpleSpaces for logging. {TransactionState.InitializeStateA[]}; -- creates stateSpace. --StoragePrograms.--RecoverTransactions: PUBLIC --EXTERNAL--PROCEDURE[] = -- Initializes Transaction modules, performs Crash Recovery. -- This procedure is called during Pilot initialization after the FileMgr and -- VMMgr are started, but before anyone is using transactions, so (1) we can -- use high-level File and Space operations, and (2) no one will call Log -- from inside FileImpl, which would trigger a deadlock since we are using -- File operations ourself. BEGIN ProcessorFace.SetMP[PilotMP.cTransactionCrashRecovery]; IF PilotSwitches.switches.x = down THEN Runtime.CallDebugger["Key Stop X"L]; IF Runtime.IsBound[SpecialTransaction.ClientStart] THEN SpecialTransaction.ClientStart[]; logSpace _ Space.Create[logSpaceSize, Space.virtualMemory]; clientSpace _ Space.Create[logSpaceSize, Space.virtualMemory]; entrySpace _ Space.Create[countLogEntry, Space.virtualMemory]; -- Create real swap units to avoid UniformSwapUnit deadlock bug: logZeroSpace _ Space.Create[standardLogFileCount-countLogEntry, Space.virtualMemory]; logZeroData _ Space.LongPointer[logZeroSpace]; Space.Map[logZeroSpace]; -- always mapped FOR base: Space.PageOffset _ 0, base + 20 WHILE base < logSpaceSize DO [] _ Space.Create[MIN[20, logSpaceSize - base], logSpace, base]; [] _ Space.Create[MIN[20, logSpaceSize - base], clientSpace, base]; ENDLOOP; DoCrashRecovery[]; END; --TransactionExtras.--DoCrashRecovery: PUBLIC PROCEDURE = BEGIN RecoverState[]; -- performs Crash Recovery on the State Table. TransactionState.InitializeStateB[]; FOR txi: ValidTxIndex IN ValidTxIndex DO ValidateLogFile[txi]; WITH trans: state[txi] SELECT FROM active => BEGIN trans.noMoreOperations _ TRUE; -- (keeps ReleaseTransaction happy.) trans.spaceList _ NIL; TransactionInternal.MaybeCrash[1]; IF ~trans.committed THEN RestoreFilesInTransaction[txi, TRUE]; TransactionInternal.MaybeCrash[5]; ReleaseLog[txi]; TransactionState.ReleaseTransaction[ TransactionInternal.Handle[trans.txEdition, txi]] END; free => NULL; ENDCASE; ENDLOOP; END; --TransactionExtras.--TransactionsInProgress: PUBLIC PROCEDURE RETURNS [BOOLEAN] = BEGIN FOR txi: ValidTxIndex IN ValidTxIndex DO WITH trans: state[txi] SELECT FROM active => IF ~trans.committed THEN RETURN[TRUE]; ENDCASE; ENDLOOP; RETURN[FALSE]; END; --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- Support routines --~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ RestoreFilesInTransaction: ENTRY PROCEDURE [ txi: ValidTxIndex, crashRecovery: BOOLEAN _ FALSE] = -- Restores client files involved in the transaction. -- Notice that this routine may be (possibly partially) executed an -- arbitrary number of times if we crash during crash recovery. BEGIN pTrans: TxDescPtr = @state[txi]; entry: LogFileEntry; logPage: Space.PageNumber _ Space.VMPageNumber[logSpace]; clientPage: Space.PageNumber _ Space.VMPageNumber[clientSpace]; pageBase, pageEntry: FilePageNumber; WITH trans: pTrans SELECT FROM active => IF trans.pageFree > 0 THEN -- there's something in the log.. BEGIN pageEntry _ trans.pageFree - trans.countPrevLogEntry; -- start with the last entry, process file back to front entry.countPrevEntry _ trans.countPrevLogEntry; -- so maybe we have to do some adjusting for the first one, but this -- saves keeping this number in the state file... DO -- UNTIL all entries processed -- first find the minimum possible pageBase value that would include the -- header page for the next entry to be processed... pageBase _ IF pageEntry < logSpaceSize THEN 0 ELSE (pageEntry + countLogEntry) - logSpaceSize; -- then, if not all of the entry about to be mapped would be in the space -- with that pageBase, add to it to get as much as possible of that entry -- (all of it, if it will fit) into the space, remembering not to make -- pageBase greater than pageEntry. IF pageEntry+entry.countPrevEntry > pageBase+logSpaceSize THEN pageBase _ MIN[ pageEntry+(entry.countPrevEntry-countLogEntry), pageEntry]; Space.Map[logSpace, [[trans.logFile, permissions], pageBase]]; WHILE pageEntry >= pageBase DO -- scope of IgnorableError -- BEGIN ENABLE BEGIN File.Unknown, Volume.Unknown => GOTO IgnorableError; File.Error => SELECT type FROM immutable, notImmutable => GOTO IgnorableError; ENDCASE => ERROR Bug[abortFileError]; END; entry _ LOOPHOLE[Utilities.LongPointerFromPage[ logPage + (pageEntry - pageBase)], LogFileEntryPtr]^; IF entry.seal # logFileFormatVersion OR entry.transactionHandle.index # txi OR entry.transactionHandle.txEdition # trans.txEdition OR entry.transactionHandle2.index # txi OR entry.transactionHandle2.txEdition # trans.txEdition THEN ERROR Bug[invalidLogEntry]; WITH entry.op SELECT FROM create => KernelFile.CreateWithID[volume, size, type, id ! KernelFile.ExistingFile => CONTINUE]; -- (we already created it during previous incomplete -- crash Recovery.) delete => File.Delete[[id, permissions]]; makeMutable => KernelFile.MakeMutable[[id, permissions]]; makeTemporary => IF crashRecovery THEN File.Delete[[id, permissions]] ELSE KernelFile.MakeTemporary[[id, permissions]]; move => File.Move[[id, permissions], volume]; setContents => BEGIN -- Note: must do things in the proper order below! -- (FIRST set contents of file, THEN MakePermanent, -- THEN MakeImmutable) clientFile: File.Capability = [id, permissions]; fileSize: File.PageCount = File.GetSize[clientFile]; mapSize: Space.PageCount _ MIN[ ShortenPageCount[size], logSpaceSize]; logData: LONG POINTER TO UNSPECIFIED _ LOOPHOLE[Utilities.LongPointerFromPage[ logPage + (pageEntry - pageBase) + countLogEntry]]; clientData: LONG POINTER TO UNSPECIFIED _ LOOPHOLE[Utilities.LongPointerFromPage[clientPage]]; originalWindow: Space.WindowOrigin = [clientFile, base]; clientWindow: Space.WindowOrigin _ originalWindow; IF fileSize pageBase+logSpaceSize THEN BEGIN -- get logSpace and clientWindow in sync pageBase _ pageEntry + countLogEntry; Space.Unmap[logSpace]; Space.Map[logSpace, [[trans.logFile, permissions], pageBase]]; logData _ LOOPHOLE[Utilities.LongPointerFromPage[logPage]]; END; DO Space.Map[clientSpace, clientWindow]; Inline.LongCOPY[from: logData, to: clientData, nwords: MIN[mapSize, ShortenPageCount[(base+size)- clientWindow.base]]*Environment.wordsPerPage]; IF clientWindow.base+mapSize>=originalWindow.base+size THEN EXIT; clientWindow.base _ clientWindow.base + mapSize; pageBase _ pageBase + mapSize; Space.Unmap[clientSpace]; Space.Unmap[logSpace]; Space.Map[logSpace, [[trans.logFile, permissions], pageBase]]; ENDLOOP; Space.Unmap[clientSpace]; IF makePermanent THEN File.MakePermanent[clientFile]; IF makeImmutable THEN File.MakeImmutable[clientFile]; TransactionInternal.MaybeCrash[6]; END; shrink => File.SetSize[[id, permissions], size]; ENDCASE => ERROR Bug[illegalLogOperation]; EXITS IgnorableError => NULL; -- come here on all errors that may result from trying to execute -- a log entry that has already been executed (e.g., trying to -- delete a file that already been deleted). This can occur if -- there was a crash during a previous attempt to process the log -- for this transaction. END; IF pageEntry = 0 THEN GOTO Done; pageEntry _ pageEntry - entry.countPrevEntry; ENDLOOP; Space.Unmap[logSpace]; IF pageBase = 0 THEN EXIT; REPEAT Done => Space.Unmap[logSpace]; ENDLOOP; END; ENDCASE => ERROR Bug[inactiveTransaction]; -- the transaction should have been active. END; ReleaseLog: ENTRY PROCEDURE [txi: ValidTxIndex] = {ReleaseLogInternal[txi]}; ReleaseLogInternal: INTERNAL PROCEDURE [txi: ValidTxIndex] = -- Resets the transaction's log file to the standard size, zeroes its -- contents, then releases it for general use. BEGIN entryData: LONG POINTER TO UNSPECIFIED _ Space.LongPointer[entrySpace]; trans: TxDescPtr = @state[txi]; IF trans.fileInUse THEN BEGIN Space.Map[entrySpace, [[trans.logFile, permissions], 0]]; Space.Kill[entrySpace]; -- first clear page 0 to avoid confusing crash recovery if we crash while -- releasing the file... entryData^ _ 0; Inline.LongCOPY[from: entryData, to: entryData+1, nwords: (countLogEntry*Environment.wordsPerPage)-1]; Space.Unmap[entrySpace]; IF trans.logFileSize # standardLogFileCount THEN File.SetSize[[trans.logFile, permissions], trans.logFileSize _ standardLogFileCount]; Space.Kill[logZeroSpace]; -- clear all of the pages so that we will not re-execute any entries of -- this log file due to any future crashes.. logZeroData^ _ 0; Inline.LongCOPY[from: logZeroData, to: logZeroData+1, nwords: ((standardLogFileCount-countLogEntry)*Environment.wordsPerPage)-1]; Space.CopyOut[logZeroSpace, [[trans.logFile, permissions], countLogEntry]]; END; trans.fileInUse _ FALSE; END; RecoverState: PROCEDURE[] = BEGIN -- Finds the valid data in the old Transaction State File (creates a new -- State Table if none), maps stateSpace, and puts the State Table into it. -- First look for the old transaction state file. If it doesn't exist, -- we're just starting up with a new system volume, so create the file and -- put it into the logical volume root page. If it does exist, do crash -- recovery on it: discover which copy is the most-recent correct one, and -- copy it to stateSpace. -- The present algorithm should happily handle a file containing an -- unreadable page if the Scavenger were to substitute another page for it -- (filled with zeroes). createStateTable: BOOLEAN; state1Edition, state2Edition: StateEdition; state1Valid, state2Valid: BOOLEAN; minStateFileCount: File.PageCount = 3*stateCount; -- two stable-storage copies + scratch storage. dataFile: File.Capability; workingStateWindow: Space.WindowOrigin; state1Space, state2Space: Space.Handle; state1, state2: LONG POINTER TO State; stateData: LONG POINTER TO StateData; -- (stateSpace is created by InitializeStateA.) stateData _ LOOPHOLE[Space.LongPointer[stateSpace]]; state _ @stateData.state; -- we use state also as a pointer to stateData (to avoid having to keep two -- copies of an equivalent pointer in our global frame), so, check that they -- are indeed equal. IF LOOPHOLE[stateData, LONG POINTER] # LOOPHOLE[state, LONG POINTER] THEN ERROR Bug[stateDataNotEqualToState]; state1Space _ Space.Create[stateCount, Space.virtualMemory, Space.defaultBase]; state2Space _ Space.Create[stateCount, Space.virtualMemory, Space.defaultBase]; state1 _ LOOPHOLE[Space.LongPointer[state1Space]]; state2 _ LOOPHOLE[Space.LongPointer[state2Space]]; state1Window.base _ 0; state2Window.base _ state1Window.base + stateCount; workingStateWindow.base _ state2Window.base + stateCount; createStateTable _ FALSE; -- assume State file is well-formed.. DO -- UNTIL the State is well-formed -- scope of CreateStateFile -- BEGIN dataFile _ KernelFile.GetRootFile[ PilotFileTypes.tTransactionStateFile, Volume.systemID]; state1Window.file _ state2Window.file _ workingStateWindow.file _ dataFile; IF File.GetSize[dataFile ! File.Unknown => GO TO CreateStateFile] < minStateFileCount THEN File.SetSize[dataFile, minStateFileCount]; -- (fills with zeros) [state1Valid, state1Edition] _ CheckState[@state1Window, state1Space, state1]; [state2Valid, state2Edition] _ CheckState[@state2Window, state2Space, state2]; -- If this isn't startup, stateSpace is already mapped. Since we can't catch the signal -- out of SimpleSpace.Map, we test first. IF ~Space.GetAttributes[stateSpace].mapped THEN SimpleSpace.Map[stateSpace, workingStateWindow, --andPin:--FALSE]; -- the working copy of the State. SimpleSpace.Kill[stateSpace]; -- Find the latest, correct copy of the State Table, and copy it into state: SELECT TRUE FROM state1Valid AND state2Valid => SELECT CompareStateEdition[value: state1Edition, to: state2Edition] FROM equal, greater => Inline.LongCOPY[from: state1, nwords: SIZE[StateData], to: state]; less => Inline.LongCOPY[from: state2, nwords: SIZE[StateData], to: state]; ENDCASE; state1Valid AND ~state2Valid => Inline.LongCOPY[from: state1, nwords: SIZE[StateData], to: state]; ~state1Valid AND state2Valid => Inline.LongCOPY[from: state2, nwords: SIZE[StateData], to: state]; ~state1Valid AND ~state2Valid => -- no valid State. IF ~createStateTable THEN { SimpleSpace.Unmap[stateSpace]; GO TO CreateStateFile} ELSE -- create the initial State Table.. FOR txi: TxIndex IN TxIndex DO -- (TxIndex, not ValidTxIndex) state[txi] _ [stateEdition: FIRST[StateEdition], seal: stateFormatVersion, txEdition: FIRST[ValidTxEdition], fileInUse: FALSE, logFile: File.nullID, logFileSize: 0, vp: free[]]; ENDLOOP; ENDCASE; EXIT; -- state is now well-formed. EXITS CreateStateFile => BEGIN Space.Unmap[state1Space ! Space.Error => CONTINUE]; -- ("Error[noWindow]") Space.Unmap[state2Space ! Space.Error => CONTINUE]; -- ("Error[noWindow]") IF dataFile # File.nullCapability THEN {Runtime.CallDebugger[ "Crash recovery data malformed. P(roceed) will delete it."L]; File.Delete[dataFile ! File.Unknown => CONTINUE]}; dataFile _ File.Create[ Volume.systemID, minStateFileCount, PilotFileTypes.tTransactionStateFile]; File.MakePermanent[dataFile]; -- enters file in volume root page as a side effect. (ugh) createStateTable _ TRUE; -- (we will create it next time through the loop.) END; END; -- scope of CreateStateFile -- ENDLOOP; -- loop until the State is well-formed. -- At this point, state contains a well-formed State Table and is the latest -- available version. Space.Delete[state1Space]; Space.Delete[state2Space]; END; CheckState: PROCEDURE [ window: POINTER TO Space.WindowOrigin, space: Space.Handle, testState: LONG POINTER TO State] RETURNS [valid: BOOLEAN, testStateEdition: StateEdition] = -- As a side effect, maps space to window. BEGIN pStateData: LONG POINTER TO StateData = LOOPHOLE[testState]; Space.Map[space, window^]; IF pStateData.checksumData # Checksum[0, SIZE[State], @pStateData.state] THEN {valid _ FALSE; RETURN}; testStateEdition _ testState[FIRST[TxIndex]].stateEdition; FOR txi: TxIndex IN TxIndex DO -- check for consistency (TxIndex, not ValidTxIndex) IF testState[txi].seal # stateFormatVersion OR ~(testState[txi].txEdition IN ValidTxEdition) OR testState[txi].stateEdition # testStateEdition THEN {valid _ FALSE; RETURN}; ENDLOOP; valid _ TRUE; RETURN; END; CompareStateEdition: PROCEDURE [value: StateEdition, to: StateEdition] RETURNS [relationship: {less, equal, greater}] = -- compares "value" to "to", taking possible wraparound into account. BEGIN wrapLimit: CARDINAL = (LAST[StateEdition] - FIRST[StateEdition])/2; -- if more than half of the range behind, we assume it wrapped around. IF value=to THEN RETURN[equal] ELSE IF valueto--{ IF value-to < wrapLimit THEN RETURN[greater] -- "value" is greater than, and close to "to". ELSE RETURN[less]}; END; ValidateLogFile: ENTRY PROCEDURE [txi: ValidTxIndex] = -- If transaction is active, locates end of valid log entries. -- If inactive, sets to standard size and clears it. -- Sets logFileSize, fileInUse, pageFree, countPrevLogEntry, as appropriate. BEGIN pTrans: TxDescPtr = @state[txi]; IF pTrans.logFile = File.nullID THEN pTrans.fileInUse _ FALSE ELSE -- scope of FileError -- BEGIN ENABLE { File.Unknown => GO TO FileError; ANY => ERROR Bug[unexpectedErrorValidateLogFile]}; pTrans.logFileSize _ ShortenPageCount[ File.GetSize[File.Capability[pTrans.logFile, permissions]]]; -- may raise File.Unknown. WITH trans: pTrans SELECT FROM active => BEGIN countOp, countOpPrev: FilePageCount _ 0; -- total number of pages logged for this, previous operation. pageBase, pageEntry: FilePageNumber _ 0; entryData: LONG POINTER TO LogFileEntryData; -- scope of PartialLogFileEntry, etc. -- BEGIN pageLog: Space.PageNumber _ Space.VMPageNumber[logSpace]; entry: LogFileEntryPtr; DO -- UNTIL pageEntry=trans.logFileSize Space.Map[logSpace, [[trans.logFile, permissions], pageBase]]; -- may raise File.Unknown. WHILE pageEntry < pageBase+logSpaceSize DO entryData _ LOOPHOLE[Space.LongPointerFromPage[ pageLog + pageEntry - pageBase]]; entry _ @entryData.logFileEntry; IF Checksum[0, SIZE[LogFileEntry], entry] # entryData.checksumData OR entry.seal # logFileFormatVersion OR entry.transactionHandle.index # txi OR entry.transactionHandle.txEdition # trans.txEdition OR entry.countPrevEntry # countOp OR ~(entry.op.operation IN TransactionState.LogOp) OR entry.transactionHandle2.index # txi OR entry.transactionHandle2.txEdition # trans.txEdition THEN GO TO InvalidLogEntry; countOpPrev _ countOp; countOp _ countLogEntry + (WITH oper: entry.op SELECT FROM setContents => ShortenPageCount[oper.size], ENDCASE => 0); IF (pageEntry _ pageEntry+countOp) > trans.logFileSize THEN GO TO PartialLogFileEntry; -- (equal is OK) IF pageEntry=trans.logFileSize THEN GO TO Done; ENDLOOP; Space.Unmap[logSpace]; pageBase _ pageEntry; REPEAT Done => NULL; ENDLOOP; trans.countPrevLogEntry _ countOp; trans.pageFree _ pageEntry; EXITS PartialLogFileEntry => {Runtime.CallDebugger[ "Incomplete Transaction Log File entry. (P)roceed will ignore it."L]; trans.pageFree _ pageEntry-countOp; -- back up to last valid entry. trans.countPrevLogEntry _ countOpPrev}; InvalidLogEntry => { IF LOOPHOLE[entryData, LONG POINTER TO LONG CARDINAL]^ # 0 THEN Runtime.CallDebugger[ "Garbage Transaction Log File entry. (P)roceed will ignore it."L]; trans.pageFree _ pageEntry; trans.countPrevLogEntry _ countOp}; END; Space.Unmap[logSpace]; IF trans.fileInUse AND trans.pageFree=0 THEN ReleaseLogInternal[txi]; trans.fileInUse _ (trans.pageFree > 0); END; free => {trans.fileInUse _ TRUE; -- (assures that file will be filled with zeroes by ReleaseLog.) ReleaseLogInternal[txi]}; ENDCASE; EXITS FileError => { Runtime.CallDebugger[ "Missing Transaction Log File. (P)roceed will ignore it."L]; pTrans.logFile _ File.nullID; pTrans.fileInUse _ FALSE}; END; 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 8:18 PM Knutsen Major cleanup. Added State and LogFile crash recovery code. Split code out into TransactionStateImpl. September 8, 1980 6:53 PM Gobbel Reset spaceList in crash recovery. September 15, 1980 10:22 AM Knutsen Made compatible with new ReleaseFromTransaction. September 15, 1980 5:47 PM Gobbel Made RecoverFilesInTransaction, ValidateLogFile, and ReleaseLog able to deal with arbitrarily large log files. October 16, 1980 4:16 PM Fay Added Volume.Unknown to catch phrases for Space.GetWindow and Space.Map calls in Abort; made Abort do nothing if transactions disabled. October 16, 1980 4:16 PM Fay Added Volume.Unknown to catch phrases December 31, 1980 10:38 AM Gobbel Make RestoreFilesInTransaction discard permissions in client's original capability January 14, 1981 7:04 PM Gobbel Use dedicated small space for zeroing log files August 26, 1982 11:04 am Levin Break out DoCrashRecovery; add TransactionsInProgress. June 1, 1982 2:26 pm Levin Make things SAFE.