-- LogBasicTailImpl.mesa -- Implements log writing. -- Last edited by -- MBrown on January 30, 1984 11:44:13 am PST DIRECTORY AlpineEnvironment, Basics, Log, LogBasic, LogInline, LogBasicInternal, LogRep, PrincOpsUtils; LogBasicTailImpl: MONITOR IMPORTS Basics, Log, LogInline, LogBasicInternal, PrincOpsUtils EXPORTS LogBasic = BEGIN PageNumber: TYPE = AlpineEnvironment.PageNumber; RecordID: TYPE = Log.RecordID; wordsPerPage: CARDINAL = AlpineEnvironment.wordsPerPage; Header: PROC [p: LONG POINTER] RETURNS [LONG POINTER TO LogRep.Header] = INLINE { RETURN [LOOPHOLE[p]] }; CallerProgrammingError: ERROR = CODE; -- Nonspecific error that clients are not expected to catch. -- Monitored state: curPageRecordID: Log.RecordID; curPageWordsUsed: INT [0 .. wordsPerPage]; curPagePtr: LONG POINTER ← NIL; -- Points to first word of current page buffer. pagesLeft: INT; -- Number of pages in buffer represented by curPagePtr. curVersion: LogRep.PageVersion; -- Version bit for page now being written. nPagesWritten: INT ← 0; nPartFullPagesWritten: INT ← 0; -- for curiosity only CurrentRecordID: INTERNAL PROC [] RETURNS [RecordID] = INLINE { RETURN [LogInline.AddC[curPageRecordID, curPageWordsUsed]] }; OpenForPut: PUBLIC ENTRY PROC [ nextPage: PageNumber, version: LogRep.PageVersion, nextRecord: RecordID] = { -- nextRecord must be page-aligned. IF LogInline.WordInPageFromRecordID[nextRecord] # 0 THEN ERROR CallerProgrammingError; curPageRecordID ← nextRecord; curPageWordsUsed ← 0; curVersion ← version; [pagesLeft, curPagePtr] ← LogBasicInternal.OpenCoreForPut[nextPage, version, nextRecord]; LOOPHOLE[curPagePtr, LONG POINTER TO CARDINAL]↑ ← 0; -- ensure that first store into page clears valid bit. }; CloseForPut: PUBLIC ENTRY PROC [] = { LogBasicInternal.CloseCoreForPut[]; curPagePtr ← NIL; }; RecordIDOfNextPut: PUBLIC ENTRY PROC [] RETURNS [RecordID] = { RETURN [CurrentRecordID[]] }; Put: PUBLIC --EXTERNAL-- PROC [ from: Log.Block, force: BOOL, writeID: BOOL] RETURNS [thisRecord, followingRecord: RecordID] = { -- Compute the total length of the block to be written, and touch all pages in it. totalLen: INT ← 0; FOR b: Log.BlockPtr ← @from, b.rest UNTIL b = NIL DO len: INT = b.length; IF len > 0 THEN { IF b.base # NIL THEN { offset: INT ← - LOOPHOLE[Basics.BITAND[AlpineEnvironment.wordsPerPage-1, LOOPHOLE[b.base, Basics.LongNumber[num]].lowbits], INTEGER]; -- b.base+offset now points to first word of page containing b.base UNTIL offset >= len DO touch: CARDINAL ← LOOPHOLE[b.base + offset, LONG POINTER TO CARDINAL]↑; offset ← offset + AlpineEnvironment.wordsPerPage; ENDLOOP; }; totalLen ← totalLen + len; }; ENDLOOP; -- Total record length must be in correct range. IF totalLen NOT IN [LogBasic.minBlockLen .. LogBasic.maxBlockLen] THEN ERROR CallerProgrammingError; -- Append to log, then force to disk if necessary. -- Note: tail monitor is not held during force. [thisRecord, followingRecord] ← PutEntry[totalLen, from, force, writeID]; IF force THEN LogBasicInternal.ForceTo[followingRecord: followingRecord]; }; PutEntry: ENTRY PROC [ totalLen: CARDINAL, from: Log.Block, force: BOOL, writeID: BOOL] RETURNS [thisRecord, followingRecord: RecordID] = { isContinuation: BOOL ← FALSE; -- ! Log.Error[logFull] (with monitor locked; this is a server-crashing error). -- On entry, there is always room in the current page for a header plus 1 word --(i.e. curPageWordsUsed < wordsPerPage-SIZE[LogRep.Header]), so this record starts on the --current page. thisRecord ← CurrentRecordID[]; IF writeID THEN { -- There must be room for the RecordID. IF from.length < LogRep.CheckpointCompleteRecord.SIZE - LogRep.StrBody.SIZE THEN RETURN WITH ERROR CallerProgrammingError; LOOPHOLE[from.base, LONG POINTER TO LogRep.CheckpointCompleteRecord].thisRecordID ← thisRecord; }; DO -- Write one log block. wordsThisBlock: CARDINAL ← MIN[(wordsPerPage-SIZE[LogRep.Header])-curPageWordsUsed, totalLen]; wordsThisCopy: CARDINAL; totalLen ← totalLen-wordsThisBlock; --words remaining when this block finished -- Write a header for the log block. -- The first store to a page should be the one that sets valid ← FALSE. Header[curPagePtr+curPageWordsUsed]↑ ← [ valid: FALSE, version: curVersion, hasContinuation: (totalLen # 0), isContinuation: isContinuation, nWords: wordsThisBlock+SIZE[LogRep.Header]]; curPageWordsUsed ← curPageWordsUsed+SIZE[LogRep.Header]; DO -- Copy a run of words to the log block. wordsThisCopy ← MIN[from.length, wordsThisBlock]; PrincOpsUtils.LongCopy[ to: curPagePtr+curPageWordsUsed, from: from.base, nwords: wordsThisCopy]; curPageWordsUsed ← curPageWordsUsed+wordsThisCopy; IF wordsThisCopy = wordsThisBlock THEN EXIT; -- Assert wordsThisBlock > wordsThisCopy = from.length, from.rest # NIL. wordsThisBlock ← wordsThisBlock-wordsThisCopy; from ← from.rest↑; ENDLOOP; -- Log block is now full. Is log record all written? IF totalLen # 0 THEN { -- Log record not all written. -- Assert curPageWordsUsed = wordsPerPage. -- Advance page and loop. AdvancePage[]; from.base ← from.base+wordsThisCopy; from.length ← from.length-wordsThisCopy; isContinuation ← TRUE; } ELSE { -- Log record all written. -- Assert from.rest = NIL. -- Advance page if log force or if not enough room on page for a header plus one word. IF force OR (curPageWordsUsed >= wordsPerPage-SIZE[LogRep.Header]) THEN AdvancePage[]; RETURN [thisRecord, CurrentRecordID[]]; } ENDLOOP; }; Force: PUBLIC PROC [followingRecord: RecordID] = { -- LogBasic.Force. AdvanceTo: ENTRY PROC [followingRecord: RecordID] = { -- If the word before followingRecord is on the current page, skip the rest of --the current page. IF Log.Compare[followingRecord, curPageRecordID] # greater THEN RETURN; -- followingRecord must not be later than record about to be written. IF Log.Compare[followingRecord, CurrentRecordID[]] = greater THEN RETURN WITH ERROR CallerProgrammingError; AdvancePage[]; }; AdvanceTo[followingRecord]; -- Note: tail monitor is not held during force. LogBasicInternal.ForceTo[followingRecord]; }; AdvancePage: INTERNAL PROC [] = { -- If page is only partly full then terminate it with 0 word. IF curPageWordsUsed # wordsPerPage THEN { nPartFullPagesWritten ← nPartFullPagesWritten + 1; LOOPHOLE[curPagePtr+curPageWordsUsed, LONG POINTER TO CARDINAL]↑ ← 0 }; nPagesWritten ← nPagesWritten + 1; -- Mark current page "valid". Header[curPagePtr].valid ← TRUE; pagesLeft ← pagesLeft - 1; IF pagesLeft = 0 THEN [curVersion, pagesLeft, curPagePtr] ← LogBasicInternal.AdvanceChunk[] -- may raise LogFull ELSE curPagePtr ← curPagePtr + AlpineEnvironment.wordsPerPage; -- Mark new page "invalid" Header[curPagePtr].valid ← FALSE; curPageRecordID ← LogInline.AddC[curPageRecordID, wordsPerPage]; curPageWordsUsed ← 0; }; END. CHANGE LOG Created by MBrown on June 15, 1982 3:38 pm -- Introduce a separate monitor for log tail. Changed by MBrown on June 22, 1982 11:46 am -- Check for log full only when moving to new chunk. Force log without holding the tail --monitor. Changed by MBrown on August 9, 1982 5:54 pm -- Implement Force. Changed by MBrown on August 12, 1982 4:15 pm -- OpenForPut calls OpenCoreForPut, to avoid any calls from LogCoreImpl --to this module. Changed by MBrown on September 10, 1982 10:09 pm -- Bug: PutEntry loops with totalLen = wordsThisCopy = wordsThisBlock = 0. Changed by MBrown on September 21, 1982 2:40 pm -- Simplified by keeping curPageRecordID as one datum, instead of in two pieces. Changed by MBrown on October 3, 1982 8:30 pm -- Added CloseForPut, simplified ERRORs (all are now raised as a nonspecific --CallerProgrammingError, since clients won't be catching them). Changed by MBrown on October 11, 1982 4:21 pm -- Added RecordIDOfNextPut. Changed by MBrown on November 10, 1982 3:10 pm -- Added nPartFullPagesWritten.