-- LogImpl.mesa -- Last edited by -- MBrown on January 30, 1984 10:19:45 am PST DIRECTORY AlpineEnvironment, AlpineInline, AlpineInternal USING [], AlpineZones USING [static], Coordinator USING [Handle, Object], ConvertUnsafe, FilePageMgr, InitProcs, Log, LogBasic, LogControl, LogInline, LogRep, Process, RestartFile, Rope, SafeStorage USING [ReclaimCollectibleObjects], TransactionMap, Worker USING [Handle, Object]; LogImpl: MONITOR IMPORTS AlpineInline, AlpineZones, ConvertUnsafe, FilePageMgr, InitProcs, LogBasic, LogInline, LogRep, Process, RestartFile, Rope, SafeStorage, TransactionMap EXPORTS AlpineInternal, Log, LogControl = BEGIN VolumeID: TYPE = AlpineEnvironment.VolumeID; FileID: TYPE = AlpineEnvironment.FileID; FileStore: TYPE = AlpineEnvironment.FileStore; TransID: TYPE = AlpineEnvironment.TransID; nullTransID: TransID = AlpineEnvironment.nullTransID; PageNumber: TYPE = AlpineEnvironment.PageNumber; PageCount: TYPE = AlpineEnvironment.PageCount; WordNumber: TYPE = LogBasic.WordNumber; WordCount: TYPE = LogBasic.WordCount; RecordID: TYPE = Log.RecordID; nullRecordID: RecordID = Log.nullRecordID; Block: TYPE = Log.Block; RecordType: TYPE = Log.RecordType; wordsPerPage: CARDINAL = AlpineEnvironment.wordsPerPage; ClientProgrammingError: ERROR = CODE; InternalProgrammingError: ERROR = CODE; TransObject: PUBLIC TYPE = Worker.Object; CoordinatorObject: PUBLIC TYPE = Coordinator.Object; WriteFailed: PUBLIC ERROR = CODE; -- Log.WriteFailed Compare: PUBLIC PROC [a, b: RecordID] RETURNS [Log.Comparison] = { -- Log.Compare RETURN [LogInline.Compare[a, b]] }; Format: PUBLIC PROC [ logVolume: VolumeID, logFile, restartFile: FileID, myFileStore: FileStore] = { -- LogControl.Format logSize: PageCount; thisCheckpointBeginRecord: RecordID; LogBasic.EstablishLogFile[logVolume, logFile]; logSize ← LogBasic.LogFileSize[]; RestartFile.EstablishRestartFile[logVolume, restartFile]; -- Write a noop record on each page of the log file. A noop record is precisely --one page long. LogBasic.OpenForPut[nextPage: 0, version: 0, nextRecord: nullRecordID]; LogBasic.Release[beforeRecord: nullRecordID]; LogBasic.Release[beforeRecord: WriteNoopRecords[logSize/2].followingRecord]; [] ← WriteNoopRecords[logSize - logSize/2]; -- Write a CheckpointBegin record, then a CheckpointComplete record pointing --to it (and the restart record in the restart file). thisCheckpointBeginRecord ← LogBasic.RecordIDOfNextPut[]; -- If all has gone well, the CheckpointBegin record was written as first record on --page 0 of the log file. IF LogBasic.WordNumberFromRecordID[thisCheckpointBeginRecord] # 0 THEN ERROR InternalProgrammingError; LogBasic.Release[beforeRecord: thisCheckpointBeginRecord]; [] ← WriteCheckpointCompleteRecord[ startAnalysisRecord: thisCheckpointBeginRecord, keepRecord: thisCheckpointBeginRecord, myFileStore: myFileStore]; LogBasic.CloseForPut[]; }; WriteNoopRecords: PUBLIC PROC [nPages: PageCount] RETURNS [followingRecord: RecordID] = { -- This proc has a large frame. noopRecordBody: LogRep.NoopRecord ← []; FOR page: PageCount ← 0, page+1 UNTIL page = nPages DO [followingRecord: followingRecord] ← LogBasic.Put[from: [base: @noopRecordBody, length: SIZE[LogRep.NoopRecord]]]; ENDLOOP; RETURN [followingRecord]; }; WriteCheckpointCompleteRecord: PROC [ startAnalysisRecord, keepRecord: RecordID, myFileStore: FileStore] RETURNS [thisRecord: RecordID] = { -- This call waits both for a log force and for a restart file write before returning. checkpointCompleteRecordBody: LogRep.CheckpointCompleteRecord ← [startAnalysisRecordID: startAnalysisRecord, keepRecordID: keepRecord]; ConvertUnsafe.AppendRope[ to: LOOPHOLE[LONG[@(checkpointCompleteRecordBody.myFileStore)]], from: myFileStore]; [thisRecord: thisRecord] ← LogBasic.Put[from: [base: @checkpointCompleteRecordBody, length: LogRep.CheckpointCompleteRecordSize[myFileStore.Length]], writeID: TRUE, force: TRUE]; RestartFile.WriteRestartRecord[ LogBasic.WordNumberFromRecordID[thisRecord], thisRecord]; }; Recover: PUBLIC PROC [logVolume: VolumeID, logFile, restartFile: FileID] = { myFileStore: FileStore; DiscoverWhereToStartAnalysisPass: PROC [] RETURNS [ checkpointWord: WordNumber, checkpointRecord, startAnalysisRecord: RecordID] = { CheckpointCompleteRecordFromWord: PROC [checkpointWord: WordNumber] RETURNS [ ok: BOOL, checkpointRecord, startAnalysisRecord: RecordID] = { -- Updates: Recover.myFileStore. -- Returns ok = FALSE if any error detected. { checkpointCompleteRecordBody: LogRep.CheckpointCompleteRecord; notStartOfRecord: BOOL; currentRecord: RecordID; [notStartOfRecord: notStartOfRecord, currentRecord: currentRecord] ← LogBasic.OpenRecordStreamFromWord[checkpointWord ! LogBasic.InvalidPage => GOTO failReturn ]; IF notStartOfRecord THEN GOTO failReturn; IF LogBasic.CheckCurrentRecord[].truncated THEN GOTO failReturn; IF LogBasic.GetCurrentRecord[currentRecord, [base: @checkpointCompleteRecordBody, length: SIZE[LogRep.CheckpointCompleteRecord]]].status = destinationFull OR checkpointCompleteRecordBody.type # checkpointComplete THEN GOTO failReturn; myFileStore ← ConvertUnsafe.ToRope[ from: LOOPHOLE[LONG[@checkpointCompleteRecordBody.myFileStore]]]; checkpointRecord ← checkpointCompleteRecordBody.thisRecordID; startAnalysisRecord ← checkpointCompleteRecordBody.startAnalysisRecordID; ok ← TRUE; EXITS failReturn => { ok ← FALSE }; }; LogBasic.CloseRecordStream[]; RETURN [ok, checkpointRecord, startAnalysisRecord]; };--CheckpointCompleteRecordFromWord FindNewestCheckpointCompleteRecord: PROC [] RETURNS [WordNumber] = { -- Do a sequential scan of log, starting at LogBasic.LocateFirstRecord[]. bigRecordID: RecordID = LogInline.RecordIDFromWordNumber[ AlpineInline.WordsFromPages[LogBasic.LogFileSize[]]]; firstWord: WordNumber = LogBasic.LocateFirstRecord[]; checkpointRecord, currentRecord: RecordID; foundCheckpointRecord: BOOL ← FALSE; notStartOfRecord: BOOL; [notStartOfRecord: notStartOfRecord, currentRecord: currentRecord] ← LogBasic.OpenRecordStreamFromWord[firstWord]; IF notStartOfRecord THEN ERROR InternalProgrammingError; DO nextRecord: RecordID; endOfLog, truncatedRecord: BOOL; currentType: RecordType ← PeekCurrentType[currentRecord]; [endOfLog, truncatedRecord, nextRecord] ← LogBasic.AdvanceRecordStream[]; IF currentType = checkpointComplete AND NOT truncatedRecord THEN { checkpointRecord ← currentRecord; foundCheckpointRecord ← TRUE; }; IF endOfLog THEN EXIT; currentRecord ← nextRecord; ENDLOOP; IF NOT foundCheckpointRecord THEN ERROR InternalProgrammingError; RETURN [LogInline.WordsFromSubtract[larger: checkpointRecord, smaller: IF Compare[checkpointRecord, bigRecordID] # less THEN bigRecordID ELSE nullRecordID]]; };--FindNewestCheckpointCompleteRecord checkpointRecordFromRestartFile: RecordID; ok: BOOL; [checkpointWord, checkpointRecordFromRestartFile] ← RestartFile.ReadRestartRecord[]; [ok, checkpointRecord, startAnalysisRecord] ← CheckpointCompleteRecordFromWord[checkpointWord]; IF NOT ok OR checkpointRecord # checkpointRecordFromRestartFile THEN { checkpointWord ← FindNewestCheckpointCompleteRecord[]; [ok, checkpointRecord, startAnalysisRecord] ← CheckpointCompleteRecordFromWord[checkpointWord]; IF NOT ok THEN ERROR InternalProgrammingError; }; RETURN [checkpointWord, checkpointRecord, startAnalysisRecord]; };--DiscoverWhereToStartAnalysisPass checkpointWord: WordNumber; checkpointRecord: RecordID; startAnalysisRecord: RecordID; [] ← LogBasic.EstablishLogFile[logVolume, logFile]; RestartFile.EstablishRestartFile[logVolume, restartFile]; [checkpointWord, checkpointRecord, startAnalysisRecord] ← DiscoverWhereToStartAnalysisPass[]; InitProcs.CalledBeforeAnalysisPass[myFileStore, logVolume]; AnalysisPass[checkpointWord, checkpointRecord, startAnalysisRecord]; InitProcs.CalledAfterAnalysisPass[]; -- This writes log records. To make them available for reading by the update --pass, they must be forced. LogBasic.Force[followingRecord: LogBasic.RecordIDOfNextPut[]]; InitProcs.CalledBeforeUpdatePass[]; UpdatePass[checkpointWord, checkpointRecord, startAnalysisRecord, LogBasic.RecordIDOfNextPut[]]; InitProcs.CalledAfterUpdatePass[]; [] ← Checkpoint[nullRecordID, nullRecordID, myFileStore]; LogBasic.AssertNormalOperation[]; };--Recover AnalysisPass: PROC [ checkpointWord: WordNumber, checkpointRecord, firstRecord: RecordID] = { -- Scans log, calling procs registered for analysis pass of recovery. currentRecord, nextRecord: RecordID; TurnOffCheckpointRegistration[]; { notStartOfRecord: BOOL; [notStartOfRecord: notStartOfRecord, currentRecord: currentRecord] ← LogBasic.OpenRecordStreamFromCheckpoint[checkpointWord: checkpointWord, checkpointRecord: checkpointRecord, firstRecord: firstRecord]; IF notStartOfRecord THEN ERROR InternalProgrammingError; }; DO currentRecordType: RecordType ← PeekCurrentType[currentRecord]; -- no errors analysisProc: LogControl.AnalysisProc; IF currentRecordType IN AnalysisProcsIndex AND (analysisProc ← analysisProcs[currentRecordType]) # NIL AND NOT LogBasic.CheckCurrentRecord[].truncated THEN { transID: TransID ← PeekCurrentTrans[currentRecord]; analysisProc[currentRecord, currentRecordType, transID]; }; { endOfLog, truncatedRecord: BOOL; [endOfLog: endOfLog, truncatedRecord: truncatedRecord, currentRecord: nextRecord] ← LogBasic.AdvanceRecordStream[]; IF truncatedRecord THEN NoteTruncatedRecord[currentRecord]; IF endOfLog THEN EXIT; }; currentRecord ← nextRecord; ENDLOOP; -- Analysis complete. If no checkpoint seen, we are in big trouble. IF NOT checkpointState.checkpointSeen THEN ERROR InternalProgrammingError; -- Overwrite enough pages past the end to ensure that we see no inconsistency due --to out-of-order log page writes. Then open log for writing at correct position. { nextPageToWrite: PageNumber; versionOfNextPageToWrite: LogRep.PageVersion; [nextPageToWrite, versionOfNextPageToWrite] ← LogBasic.GetCurrentPageAndVersion[]; LogBasic.CloseRecordStream[]; LogBasic.OpenForPut[nextPage: nextPageToWrite, version: 1-versionOfNextPageToWrite, nextRecord: nextRecord]; LogBasic.Release[beforeRecord: checkpointState.keepRecord]; LogBasic.Force[followingRecord: WriteNoopRecords[nPages: LogBasic.maxInvalidLogPages].followingRecord]; LogBasic.CloseForPut[]; LogBasic.OpenForPut[nextPage: nextPageToWrite, version: versionOfNextPageToWrite, nextRecord: nextRecord] }; };--AnalysisPass RoundUpToPages: PROC [recordID: RecordID] RETURNS [RecordID] = { words: CARDINAL = LogInline.WordInPageFromRecordID[recordID]; RETURN [ IF words= 0 THEN recordID ELSE LogInline.AddC[recordID, wordsPerPage-words]]; }; firstTruncatedRecord, lastTruncatedRecord: LIST OF RecordID ← NIL; NoteTruncatedRecord: PROC [record: RecordID] = { new: LIST OF RecordID ← CONS [first: record, rest: NIL]; IF lastTruncatedRecord # NIL THEN lastTruncatedRecord.rest ← new; lastTruncatedRecord ← new; IF firstTruncatedRecord = NIL THEN firstTruncatedRecord ← new; }; CheckpointCompleteAnalysisProc: PROC [ record: RecordID, type: RecordType, trans: TransID] = { -- We use this to find the keepRecord value in the LAST checkpoint record. checkpointCompleteRecordBody: LogRep.CheckpointCompleteRecord; IF LogBasic.GetCurrentRecord[currentRecord: record, to: [base: @checkpointCompleteRecordBody, length: SIZE[LogRep.CheckpointCompleteRecord]]].status = destinationFull OR checkpointCompleteRecordBody.type # checkpointComplete THEN ERROR; checkpointState.keepRecord ← checkpointCompleteRecordBody.keepRecordID; checkpointState.checkpointSeen ← TRUE; }; CheckpointState: TYPE = RECORD [ checkpointSeen: BOOL ← FALSE, keepRecord: RecordID ]; checkpointState: CheckpointState; UpdatePass: PROC [checkpointWord: WordNumber, checkpointRecord, firstRecord, nextRecordToWrite: RecordID] = { currentTruncatedRecord: LIST OF RecordID ← firstTruncatedRecord; notStartOfRecord: BOOL; currentRecord: RecordID; expectTruncatedRecord: BOOL; TurnOffRecoveryRegistration[]; [notStartOfRecord: notStartOfRecord, currentRecord: currentRecord] ← LogBasic.OpenRecordStreamFromCheckpoint[checkpointWord: checkpointWord, checkpointRecord: checkpointRecord, firstRecord: firstRecord]; IF notStartOfRecord THEN ERROR InternalProgrammingError; DO expectTruncatedRecord ← FALSE; { currentRecordType: RecordType; recoveryProc: Log.RecoveryProc; IF currentTruncatedRecord # NIL THEN SELECT Compare[currentRecord, currentTruncatedRecord.first] FROM less => NULL; equal => { expectTruncatedRecord ← TRUE; currentTruncatedRecord ← currentTruncatedRecord.rest; GOTO GetNext }; greater => ERROR InternalProgrammingError; ENDCASE => ERROR; SELECT Compare[currentRecord, nextRecordToWrite] FROM less => NULL; equal => EXIT; greater => ERROR InternalProgrammingError; ENDCASE => ERROR; IF (currentRecordType ← PeekCurrentType[currentRecord]) IN RecoveryProcsIndex AND (recoveryProc ← recoveryProcs[currentRecordType]) # NIL THEN { transID: TransID ← PeekCurrentTrans[currentRecord]; transHandle: TransactionMap.Handle = TransactionMap.GetHandle[transID]; IF transHandle # TransactionMap.nullHandle THEN { s: TransactionMap.TransState ← TransactionMap.StateDuringRecovery[transHandle]; state: Log.TransState ← IF s = committed THEN committed ELSE IF s = aborted THEN aborted ELSE ready; recoveryProc[currentRecord, currentRecordType, transHandle, state]; }; }; GOTO GetNext; EXITS GetNext => { endOfLog, truncatedRecord: BOOL; nextRecord: RecordID; [endOfLog: endOfLog, truncatedRecord: truncatedRecord, currentRecord: nextRecord] ← LogBasic.AdvanceRecordStream[]; IF endOfLog AND (nextRecord # nextRecordToWrite) OR (truncatedRecord # expectTruncatedRecord) THEN ERROR InternalProgrammingError; currentRecord ← nextRecord; }; }; ENDLOOP; FilePageMgr.ForceOutEverything[]; }; PeekCurrentType: PROC [thisRecord: RecordID] RETURNS [recordType: RecordType] = { recordTypeHeader: LogRep.RecordTypeHeader; status: Log.ReadProcStatus; [status: status] ← LogBasic.GetCurrentRecord[currentRecord: thisRecord, to: [base: @recordTypeHeader, length: LogRep.RecordTypeHeader.SIZE]]; IF status = sourceExhausted THEN ERROR InternalProgrammingError; RETURN [recordTypeHeader.type]; }; PeekCurrentTrans: PROC [thisRecord: RecordID] RETURNS [TransID] = { transactionHeader: LogRep.TransactionHeader; status: Log.ReadProcStatus; [status: status] ← LogBasic.GetCurrentRecord[currentRecord: thisRecord, to: [base: @transactionHeader, length: LogRep.TransactionHeader.SIZE]]; IF status = sourceExhausted THEN RETURN [nullTransID] ELSE RETURN [transactionHeader.transID]; }; ReadForRecovery: PUBLIC PROC [ thisRecord: RecordID, wordsToSkip: CARDINAL ← 0, to: Block] RETURNS [status: Log.ReadProcStatus, wordsRead: CARDINAL] = { -- This is a Log.ReadProc. IF wordsToSkip > LAST[CARDINAL] - SIZE[LogRep.TransactionHeader] THEN wordsToSkip ← LAST[CARDINAL] ELSE wordsToSkip ← wordsToSkip + SIZE[LogRep.TransactionHeader]; [status: status, wordsRead: wordsRead] ← LogBasic.GetCurrentRecord[currentRecord: thisRecord, to: [base: NIL, length: wordsToSkip, rest: @to]]; wordsRead ← wordsRead - SIZE[LogRep.TransactionHeader]; }; -- Log writing -- Exported to Log Write: PUBLIC PROC [ trans: Worker.Handle, recordType: RecordType, recordData: Block, force: BOOL] RETURNS [thisRecord, followingRecord: RecordID] = { recordHeader: LogRep.TransactionHeader ← [type: recordType, transID: trans.transID]; [thisRecord, followingRecord] ← LogBasic.Put[ from: [base: @recordHeader, length: SIZE[LogRep.TransactionHeader], rest: @recordData], force: force, writeID: FALSE]; }; CoordinatorWrite: PUBLIC PROC [ trans: Coordinator.Handle, recordType: RecordType, recordData: Block, force: BOOL] RETURNS [thisRecord, followingRecord: RecordID] = { recordHeader: LogRep.TransactionHeader ← [type: recordType, transID: trans.transID]; [thisRecord, followingRecord] ← LogBasic.Put[ from: [base: @recordHeader, length: SIZE[LogRep.TransactionHeader], rest: @recordData], force: force, writeID: FALSE]; }; Force: PUBLIC PROC [followingRecord: RecordID] = { IF followingRecord # nullRecordID THEN LogBasic.Force[followingRecord]; }; -- Miscellaneous info concerning the log. -- Exported to LogControl WordsUsableByClient: PUBLIC PROC [] RETURNS [INT] = { RETURN [(3*LogBasic.LogFileSize[]/4)*wordsPerPage]; }; RecordIDOfNextWrite: PUBLIC PROC [] RETURNS [RecordID] = { RETURN [LogBasic.RecordIDOfNextPut[]]; }; -- Log reading -- Exported to Log Read: PUBLIC PROC [ thisRecord: RecordID, wordsToSkip: CARDINAL, to: Log.Block] RETURNS [status: Log.ReadProcStatus, wordsRead: CARDINAL] = { IF wordsToSkip > LAST[CARDINAL] - SIZE[LogRep.TransactionHeader] THEN wordsToSkip ← LAST[CARDINAL] ELSE wordsToSkip ← wordsToSkip + SIZE[LogRep.TransactionHeader]; [status: status, wordsRead: wordsRead] ← LogBasic.Get[ thisRecord: thisRecord, to: [base: NIL, length: wordsToSkip, rest: @to]]; wordsRead ← wordsRead - SIZE[LogRep.TransactionHeader]; }; -- Recovery procs -- Exported to Log noMoreRecoveryRegistration: BOOL ← FALSE; TurnOffRecoveryRegistration: ENTRY PROC [] = INLINE { noMoreRecoveryRegistration ← TRUE; }; RecoveryProcsIndex: TYPE = RecordType [noop .. reserved); RecoveryProcsArray: TYPE = ARRAY RecoveryProcsIndex OF Log.RecoveryProc; recoveryProcs: REF RecoveryProcsArray = AlpineZones.static.NEW[RecoveryProcsArray ← ALL [NIL]]; RegisterRecoveryProc: PUBLIC ENTRY PROC [ recordType: RecordType, recoveryProc: Log.RecoveryProc] = { IF noMoreRecoveryRegistration THEN RETURN WITH ERROR ClientProgrammingError; IF recordType NOT IN RecoveryProcsIndex THEN RETURN WITH ERROR InternalProgrammingError; recoveryProcs[recordType] ← recoveryProc; }; -- Analysis procs, checkpoint procs -- Exported to LogControl noMoreCheckpointRegistration: BOOL ← FALSE; TurnOffCheckpointRegistration: ENTRY PROC [] = INLINE { noMoreCheckpointRegistration ← TRUE; }; IsCheckpointProcCallable: ENTRY PROC [] RETURNS [BOOL] = INLINE { RETURN [noMoreCheckpointRegistration]; }; AnalysisProcsIndex: TYPE = RecordType [checkpointBegin .. workerComplete]; AnalysisProcsArray: TYPE = ARRAY AnalysisProcsIndex OF LogControl.AnalysisProc; analysisProcs: REF AnalysisProcsArray = AlpineZones.static.NEW[AnalysisProcsArray ← ALL [NIL]]; RegisterAnalysisProc: PUBLIC ENTRY PROC [ recordType: RecordType, analysisProc: LogControl.AnalysisProc] = { IF noMoreCheckpointRegistration THEN RETURN WITH ERROR ClientProgrammingError; IF recordType NOT IN AnalysisProcsIndex THEN RETURN WITH ERROR InternalProgrammingError; analysisProcs[recordType] ← analysisProc; }; checkpointProcs: ARRAY [0 .. maxCheckpointProcs) OF LogControl.CheckpointProc ← ALL [NIL]; maxCheckpointProcs: INT = 5; nCheckpointProcs: INT [0 .. maxCheckpointProcs] ← 0; RegisterCheckpointProc: PUBLIC ENTRY PROC [ checkpointProc: LogControl.CheckpointProc] = { IF noMoreCheckpointRegistration THEN RETURN WITH ERROR ClientProgrammingError; IF nCheckpointProcs = maxCheckpointProcs THEN RETURN WITH ERROR InternalProgrammingError; checkpointProcs[nCheckpointProcs] ← checkpointProc; nCheckpointProcs ← nCheckpointProcs + 1; }; ForkCheckpointProcess: PUBLIC PROC [myFileStore: FileStore, wakeupPeriodInSeconds: NAT] = { Process.Detach[FORK CheckpointProcess[myFileStore, wakeupPeriodInSeconds]]; }; CheckpointProcess: PROC [myFileStore: FileStore, wakeupPeriodInSeconds: NAT] = { keepRecord, startAnalysisRecord: RecordID ← nullRecordID; DO Process.Pause[Process.SecondsToTicks[wakeupPeriodInSeconds]]; [keepRecord, startAnalysisRecord] ← Checkpoint[keepRecord, startAnalysisRecord, myFileStore]; SafeStorage.ReclaimCollectibleObjects[suspendMe: TRUE, traceAndSweep: FALSE]; ENDLOOP; }; Checkpoint: PROC [ previousKeepRecord, previousStartAnalysisRecord: RecordID, myFileStore: FileStore] RETURNS [keepRecord, startAnalysisRecord: RecordID] = { -- Writes a checkpointCompleteRecord only if it contains a value different than --the previous checkpointCompleteRecord. onlineLogLength: INT = AlpineInline.WordsFromPages[LogBasic.LogFileSize[]]; differentRecordIDsSeen: BOOL ← FALSE; NoticeDifferentRecordIDs: PROC [old, new: RecordID] = { SELECT Compare[old, new] FROM less => differentRecordIDsSeen ← TRUE; greater => ERROR InternalProgrammingError; ENDCASE; }; keepRecord ← startAnalysisRecord ← LogBasic.RecordIDOfNextPut[]; IF NOT IsCheckpointProcCallable[] THEN ERROR InternalProgrammingError; FOR i: INT [0 .. maxCheckpointProcs] IN [0 .. nCheckpointProcs) DO thisKeepRecord, thisStartAnalysisRecord: RecordID; [thisKeepRecord, thisStartAnalysisRecord] ← checkpointProcs[i][]; keepRecord ← LogInline.Min[thisKeepRecord, keepRecord]; startAnalysisRecord ← LogInline.Min[thisStartAnalysisRecord, startAnalysisRecord] ENDLOOP; NoticeDifferentRecordIDs[previousKeepRecord, keepRecord]; NoticeDifferentRecordIDs[previousStartAnalysisRecord, startAnalysisRecord]; IF differentRecordIDsSeen THEN { [] ← WriteCheckpointCompleteRecord[ startAnalysisRecord: startAnalysisRecord, keepRecord: keepRecord, myFileStore: myFileStore]; LogBasic.Release[beforeRecord: keepRecord]; previousKeepRecord ← keepRecord; previousStartAnalysisRecord ← startAnalysisRecord; }; RETURN [keepRecord, startAnalysisRecord]; }; RegisterAnalysisProc[ recordType: checkpointComplete, analysisProc: CheckpointCompleteAnalysisProc]; END. CHANGE LOG Changed by MBrown on April 1, 1983 1:25 pm -- Checkpoint process is now implemented here, not in InitProcsImpl(!). Changed by MBrown on June 16, 1983 9:35 am -- Fixed bug in AnalysisPass. After analysis is complete, we must write a few noop records to --clean up the log tail, but we failed to call LogBasic.Release[keepRecord] to tell lower-level --log routines that overwriting those pages is ok. Changed by MBrown on June 27, 1983 10:06 am -- Eliminated bug/feature in AnalysisPass that reclaimed "extra" log pages occupied by --a multi-page truncated record found at end of log. This never really worked --(it has caused several restart failures) and the LogBasic abstraction does not --allow for its clean implementation. How much is it worth? -- Added call to LogBasic.AssertNormalOperation at end of restart; this permits --restart to write closer to the beginning of the log than normal clients are allowed to. Changed by MBrown on January 30, 1984 10:20:09 am PST -- Cedar 5.0: don't mention Environment.