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