YggLogBasicWriteImpl.mesa
Copyright Ó 1985, 1987, 1989 by Xerox Corporation. All rights reserved.
Implements log writing. Separate from YggLogBasicImpl to have its own monitor. Derived from Alpine.
Last edited by
Bob Hagmann June 26, 1989 3:54:13 pm PDT
DIRECTORY
Camelot,
PBasics,
YggEnvironment,
YggLog,
YggLogBasic,
YggLogInline,
YggLogBasicInternal,
YggLogRep,
VM;
YggLogBasicWriteImpl: CEDAR MONITOR
IMPORTS
PBasics, YggLog, YggLogBasic, YggLogBasicInternal, YggLogInline
EXPORTS
YggLogBasic =
BEGIN
PageNumber: TYPE = YggEnvironment.PageNumber;
RecordID: TYPE = YggLog.RecordID;
CallerProgrammingError: ERROR = CODE;
Nonspecific error that clients are not expected to catch.
Monitored state:
curPageRecordID: YggLog.RecordID;
curPageWordsUsed: CARD; -- in machine dependent words
curPagePtr: LONG POINTERNIL;
Points to first word of current page buffer.
pagesLeft: INT;
Number of pages in buffer represented by curPagePtr.
curVersion: YggLogRep.PageVersion;
Version bit for page now being written.
nPagesWritten: INT ← 0;
nPartFullPagesWritten: INT ← 0;
for curiosity only
Starting and stopping logging
OpenForPut: PUBLIC ENTRY PROC [
nextPage: PageNumber, version: YggLogRep.PageVersion, nextRecord: RecordID] = {
nextRecord must be page-aligned.
IF YggLogInline.WordInPageFromRecordID[nextRecord, VM.wordsPerPage] # 0 THEN
ERROR CallerProgrammingError;
curPageRecordID ← nextRecord;
curPageWordsUsed ← 0;
curVersion ← version;
TRUSTED {
[pagesLeft, curPagePtr] ← YggLogBasicInternal.OpenBasicForPut[nextPage, version, nextRecord];
LOOPHOLE[curPagePtr, LONG POINTER TO CARD32]^ ← 0;
ensure that first store into page clears valid bit.
};
};
CloseForPut: PUBLIC ENTRY PROC [] = {
YggLogBasicInternal.CloseBasicForPut[];
curPagePtr ← NIL;
};
Public writing
Put: PUBLIC PROC [ from: YggLog.Block, optr: Camelot.optrT, force: BOOL, writeID: BOOL]
RETURNS [thisRecord, followingRecord: RecordID, thisWordNumber: YggEnvironment.WordNumber] = TRUSTED {
 Compute the total length of the block to be written, and touch all pages in it.
totalLen: INT ← 0; -- in 32 bit words
FOR b: YggLog.BlockPtr ← @from, b.rest UNTIL b = NIL DO
len: CARD = b.length;
IF len > 0 THEN {
IF b.base # NIL THEN {
offset: CARD ← 0;
UNTIL offset >= len*WordsINWORDS DO
touch: CARDINALLOOPHOLE[b.base + offset*SIZE[INT32], LONG POINTER TO CARDINAL]^;
IF offset = len - 1 THEN EXIT;
offset ← offset + VM.wordsPerPage/WORDS[CARD32];
IF offset >= len THEN offset ← len - 1;
ENDLOOP;
};
totalLen ← totalLen + len;
};
ENDLOOP;
 Total record length must be in correct range.
IF totalLen NOT IN [YggLogBasic.minBlockLen .. YggLogBasic.maxBlockLen] THEN
ERROR CallerProgrammingError;
 Append to log, then force to disk if necessary.
 Note: tail monitor is not held during force.
[thisRecord, followingRecord, thisWordNumber] ← PutEntry[totalLen, from, force, writeID];
IF force THEN YggLogBasicInternal.ForceTo[followingRecord: followingRecord];
};
WordsINWORDS: CARD = WORDS[CARD32];
PutEntry: ENTRY PROC [
totalLen: CARDINAL, from: YggLog.Block, force: BOOL, writeID: BOOL]
RETURNS [thisRecord, followingRecord: RecordID, thisWordNumber: YggEnvironment.WordNumber] = {
isContinuation: BOOLFALSE;
! YggLog.WriteFailed (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[YggLogRep.Header]), so this record starts on the current page.
thisRecord ← CurrentRecordID[];
thisWordNumber ← YggLogBasic.WordNumberFromRecordID[thisRecord];
IF writeID THEN TRUSTED {
LOOPHOLE[from.base,
LONG POINTER TO YggLogRep.CheckpointCompleteRecord].thisRecordID ← thisRecord;
};
DO
Write one log block.
wordsThisBlock: CARD
MIN[(YggLog.wordsPerPage-WORDS[YggLogRep.Header])-curPageWordsUsed, totalLen*WordsINWORDS]; -- in machine dependent words
wordsThisCopy: CARD;
totalLen ← totalLen-wordsThisBlock/WordsINWORDS; -- 32 bit words remaining to be logged when this block is finished
 Write a header for the log block.
 The first store to a page should be the one that sets valid ← FALSE.
TRUSTED {
pageNumber: INT;
pageNumber ← ConstArith.ToInt[ConstArith.Div[curPageRecordID, ConstArith.FromCard[YggLog.wordsPerPage]]];
Header[curPagePtr+(curPageWordsUsed*UNITS[WORD])]^ ← [
valid: FALSE, version: curVersion,
hasContinuation: (totalLen # 0), isContinuation: isContinuation,
nWords: wordsThisBlock+SIZE[YggLogRep.Header],
unused1: pageNumber];
};
curPageWordsUsed ← curPageWordsUsed+WORDS[YggLogRep.Header];
DO
Copy a run of words to the log block.
wordsThisCopy ← MIN[from.length*WordsINWORDS, wordsThisBlock];
TRUSTED {PBasics.Copy[
to: curPagePtr+(curPageWordsUsed*UNITS[WORD]), from: from.base, nwords: wordsThisCopy/WordsINWORDS]; };
curPageWordsUsed ← curPageWordsUsed+wordsThisCopy;
IF wordsThisCopy = wordsThisBlock THEN EXIT;
Assert wordsThisBlock > wordsThisCopy = from.length, from.rest # NIL.
wordsThisBlock ← wordsThisBlock-wordsThisCopy;
TRUSTED {from ← from.rest^;};
ENDLOOP;
YggLog block is now full. Is log record all written?
IF totalLen # 0 THEN { -- YggLog record not all written.
Assert curPageWordsUsed = wordsPerPage.
Advance page and loop.
AdvancePage[];
from.base ← from.base+wordsThisCopy;
from.length ← from.length-wordsThisCopy/WordsINWORDS;
isContinuation ← TRUE;
}
ELSE { -- YggLog 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.
wpp: CARD = YggLog.wordsPerPage;
IF force OR (curPageWordsUsed >= wpp-WORDS[YggLogRep.Header]) THEN
AdvancePage[];
RETURN [thisRecord, CurrentRecordID[], thisWordNumber];
}
ENDLOOP;
};
Force: PUBLIC PROC [followingRecord: RecordID] = {
YggLogBasic.Force.
AdvanceTo: ENTRY PROC [followingRecord: RecordID] = {
If the word before followingRecord is on the current page, skip the rest of
the current page.
TRUSTED {IF ConstArith.Compare[followingRecord, curPageRecordID] # greater
THEN RETURN; };
followingRecord must not be later than record about to be written.
TRUSTED {IF ConstArith.Compare[followingRecord, CurrentRecordID[]] = greater THEN
RETURN WITH ERROR CallerProgrammingError; };
AdvancePage[];
};
AdvanceTo[followingRecord];
Note: tail monitor is not held during force.
YggLogBasicInternal.ForceTo[followingRecord];
};
Internal procedures and utilities
Header: PROC [p: LONG POINTER] RETURNS [LONG POINTER TO YggLogRep.Header] = INLINE {
RETURN [LOOPHOLE[p]] };
RecordIDOfNextPut: PUBLIC ENTRY PROC [] RETURNS [RecordID] = {
RETURN [CurrentRecordID[]]
};
CurrentRecordID: INTERNAL PROC [] RETURNS [RecordID] = INLINE {
RETURN [YggLogInline.AddC[curPageRecordID, curPageWordsUsed]] };
AdvancePage: INTERNAL PROC [] = {
If page is only partly full then terminate it with 0 word.
IF curPageWordsUsed # YggLog.wordsPerPage THEN TRUSTED {
nPartFullPagesWritten ← nPartFullPagesWritten + 1;
LOOPHOLE[curPagePtr+(curPageWordsUsed*UNITS[WORD]), LONG POINTER TO CARD32]^ ← 0 };
nPagesWritten ← nPagesWritten + 1;
Mark current page "valid".
TRUSTED {Header[curPagePtr].valid ← TRUE;};
pagesLeft ← pagesLeft - 1;
IF pagesLeft = 0 THEN
[curVersion, pagesLeft, curPagePtr] ← YggLogBasicInternal.AdvanceChunk[]
may raise WriteFailed because log is full
ELSE
curPagePtr ← curPagePtr + YggLog.wordsPerPage * UNITS[WORD];
Mark new page "invalid"
TRUSTED {Header[curPagePtr].valid ← FALSE;};
curPageRecordID ← YggLogInline.AddC[curPageRecordID, YggLog.wordsPerPage];
curPageWordsUsed ← 0;
};
END.