Last edited by:
Taft on April 10, 1983 3:16 pm
MBrown on January 30, 1984 1:48:20 pm PST
Hauser, March 7, 1985 2:57:05 pm PST
Carl Hauser, October 15, 1987 3:30:05 pm PDT
Bob Hagmann May 13, 1988 8:36:03 am PDT
DIRECTORY
Basics USING [bytesPerWord],
CardTab USING [Create, Fetch, Ref, Store],
SymTab USING [Create, Fetch, Ref, Store],
YggEnvironment,
YggDID,
YggLog,
YggOpenDoc,
YggFileLockFormat,
YggOpenDocLog,
YggDIDMap,
YggFilePageMgr,
YggLogControl,
Rope USING [InlineFlatten, ROPE, Text];
YggOpenDocLogImpl:
MONITOR
IMPORTS CardTab, SymTab, YggEnvironment, YggOpenDoc, YggOpenDocLog, YggDIDMap, YggFilePageMgr, YggLog, YggLogControl, Rope
EXPORTS YggDID, YggOpenDocLog
SHARES Rope =
BEGIN OPEN YggOpenDocLog;
YggOpenDocLog.
LogCreate:
PUBLIC
PROCEDURE [openDoc: YggOpenDoc.OpenDoc, initialSize: PageCount, owner: YggEnvironment.OwnerName] =
BEGIN
textOwner: Rope.Text = Rope.InlineFlatten[owner];
did: YggEnvironment.DID;
length: CARDINAL = textOwner.length;
textBlock: YggLog.Block ← [ base: BASE[DESCRIPTOR[textOwner.text]], length: (length+Basics.bytesPerWord-1)/Basics.bytesPerWord];
record: FileLogRecord[create] ← [ fileID: 0, specifics: create [initialSize: initialSize, owner: [length: length, text: NULL]]];
did ← YggOpenDoc.GetDID[openDoc];
record.fileID ← StoreDIDInMaps[did: did, id: 0];
[] ← YggLog.Write[trans: YggOpenDoc.GetTransHandle[openDoc], logRecordPhaseType: redo, recordType: create, recordData: [base: @record, length: SIZE[FileLogRecord[create]], rest: @textBlock], force: TRUE];
END;
LogDelete:
PUBLIC
PROCEDURE [openDoc: YggOpenDoc.OpenDoc] =
BEGIN
did: YggEnvironment.DID;
record: FileLogRecord[delete] ← [fileID: NULL, specifics: delete []];
record.fileID ← LookupIDInMap[did: YggOpenDoc.GetDID[openDoc]];
IF record.fileID = 0 THEN record.fileID ← StoreDIDInMaps[did: did, id: 0];
[] ← YggLog.Write[trans: YggOpenDoc.GetTransHandle[openDoc], logRecordPhaseType: redo, recordType: delete, recordData: [base: @record, length: SIZE[FileLogRecord[delete]]]];
END;
LogSetSize:
PUBLIC
PROCEDURE [openDoc: YggOpenDoc.OpenDoc, old, new: PageCount] =
BEGIN
did: YggEnvironment.DID;
record: FileLogRecord[setSize] ← [fileID: NULL, specifics: setSize [old: old, new: new]];
record.fileID ← LookupIDInMap[did: YggOpenDoc.GetDID[openDoc]];
IF record.fileID = 0 THEN record.fileID ← StoreDIDInMaps[did: did, id: 0];
[] ← YggLog.Write[trans: YggOpenDoc.GetTransHandle[openDoc], logRecordPhaseType: redo, recordType: setSize, recordData: [base: @record, length: SIZE[FileLogRecord[setSize]]]];
END;
LogWritePages:
PUBLIC
PROCEDURE [openDoc: YggOpenDoc.OpenDoc, where:
LONG
POINTER, pageRun: PageRun, referencePattern: ReferencePattern]
RETURNS [recordID: LogRecordID] =
BEGIN
did: YggEnvironment.DID;
dataBlock: YggLog.Block ← [base: where, length: pageRun.count*YggEnvironment.wordsPerPage];
record: FileLogRecord[writePages] ← [fileID: NULL, specifics: writePages [pageRun: pageRun, referencePattern: referencePattern, data: NULL]];
record.fileID ← LookupIDInMap[did: YggOpenDoc.GetDID[openDoc]];
IF record.fileID = 0 THEN record.fileID ← StoreDIDInMaps[did: did, id: 0];
[thisRecord: recordID] ← YggLog.Write[trans: YggOpenDoc.GetTransHandle[openDoc], logRecordPhaseType: redo, recordType: writePages, recordData: [base: @record, length: SIZE[FileLogRecord[writePages]], rest: @dataBlock]];
END;
LogReadPages:
PUBLIC
PROCEDURE [openDoc: YggOpenDoc.OpenDoc, where:
LONG
POINTER, pageRun: PageRun, recordID: LogRecordID]
RETURNS [referencePattern: ReferencePattern] =
BEGIN
did: YggEnvironment.DID;
header: FileLogRecord[writePages];
status: YggLog.ReadProcStatus;
[status: status] ← YggLog.Read[thisRecord: recordID, to: [base: @header, length: SIZE[FileLogRecord[writePages]]]];
IF status=sourceExhausted THEN ERROR; -- wrong length log record
did ← YggOpenDoc.GetDID[openDoc];
IF LookupIDInMap[did: did] # header.fileID THEN ERROR; -- wrong file
IF pageRun.firstPage<header.pageRun.firstPage
OR pageRun.firstPage+pageRun.count>header.pageRun.firstPage+header.pageRun.count
THEN ERROR; -- pageRun is not a subinterval of the one contained in the log record
[status: status] ← YggLog.Read[thisRecord: recordID, wordsToSkip: SIZE[FileLogRecord[writePages]] + CARDINAL[pageRun.firstPage-header.pageRun.firstPage] * YggEnvironment.wordsPerPage, to: [base: where, length: pageRun.count * YggEnvironment.wordsPerPage]];
IF status=sourceExhausted THEN ERROR; -- log record not as long as it says it is
RETURN [header.referencePattern];
END;
LogFileLock:
PUBLIC
PROCEDURE [openDoc: YggOpenDoc.OpenDoc] =
BEGIN
did: YggEnvironment.DID;
lockSubID: YggFileLockFormat.FileLockSubID = [file[]];
record: FileLogRecord[lock] ← [fileID: NULL, specifics: lock [lockSubID: LOOPHOLE[lockSubID]]];
record.fileID ← LookupIDInMap[did: YggOpenDoc.GetDID[openDoc]];
IF record.fileID = 0 THEN record.fileID ← StoreDIDInMaps[did: did, id: 0];
[] ← YggLog.Write[trans: YggOpenDoc.GetTransHandle[openDoc], logRecordPhaseType: redo, recordType: lock, recordData: [base: @record, length: SIZE[FileLogRecord[lock]]]];
END;
Internal procedures
FileAnalysisProc: YggLogControl.AnalysisProc
--[record, type, trans]-- =
TRUSTED BEGIN
header: FileLogRecord;
wordsRead: CARDINAL;
exists: BOOLEAN ← TRUE;
status: YggLog.ReadProcStatus;
IF type NOT IN DefinedFileRecord THEN ERROR;
Attempt to read the longest possible FileLogRecord, and make sure it is at least as long as the shortest possible FileLogRecord.
[status, wordsRead] ← YggLog.ReadForRecovery[
thisRecord: record, to: [base: @header, length: SIZE[FileLogRecord]]];
IF status = destinationFull THEN ERROR;
IF wordsRead<SIZE[FileLogRecord[delete]] THEN ERROR; -- log record too short
WITH r: header
SELECT type
FROM
setSize =>
BEGIN
fileHandle: YggDIDMap.Document ← YggDIDMap.Register[did: LookupDIDInMap[header.fileID]];
ssl: SetSizeList ← CONS[[r.old, r.new, trans, record, fileHandle], NARROW[YggDIDMap.GetRecoveryData[fileHandle]]];
IF wordsRead<SIZE[FileLogRecord[setSize]] THEN ERROR; -- log record too short
YggDIDMap.SetRecoveryData[fileHandle, ssl];
END;
ENDCASE => NULL;
END;
FileRecoveryProc: YggLog.RecoveryProc
--[record, type, trans, outcome]-- =
TRUSTED BEGIN
openDoc: YggOpenDoc.OpenDoc;
header: FileLogRecord;
wordsRead: CARDINAL;
exists: BOOLEAN ← TRUE;
IF type NOT IN DefinedFileRecord THEN ERROR;
Attempt to read the longest possible FileLogRecord, and make sure it is at least as long as the shortest possible FileLogRecord.
[wordsRead: wordsRead] ← YggLog.ReadForRecovery[thisRecord: record, to: [base: @header, length: SIZE[FileLogRecord]]];
IF wordsRead<SIZE[FileLogRecord[delete]] THEN ERROR; -- log record too short
openDoc ← YggOpenDoc.Register[trans: trans, did: LookupDIDInMap[header.fileID]];
Recover the update only if the file still exists, under the assumption that if it does not exist then it must have been deleted by some later committed transaction. GetSize is an efficient way to check for the existence of a file because YggFilePageMgr caches the file's existence and size.
[] ← YggFilePageMgr.GetSize[YggOpenDoc.GetDocHandle[openDoc] !
YggFilePageMgr.NoSuchFile => {exists ← FALSE; CONTINUE};
Crock
File.Error => {IF type=delete THEN {exists ← FALSE; CONTINUE} ELSE REJECT}
];
IF exists
THEN
WITH r: header
SELECT type
FROM
create =>
BEGIN
owner is not actually needed for recovery, so omit this:
owner: OwnerName;
Fetch: SAFE PROCEDURE RETURNS [c: CHAR] = TRUSTED
{c ← r.owner.text[index]; index ← index+1};
index: CARDINAL ← 0;
IF wordsRead<
SIZE[FileLogRecord[create]]
THEN
ERROR;
-- log record too short
owner ← Rope.FromProc[len: r.owner.length, p: Fetch];
RecoverCreate[openDoc: openDoc, initialSize: r.initialSize, outcome: outcome];
END;
delete =>
BEGIN
IF wordsRead<SIZE[FileLogRecord[delete]] THEN ERROR; -- log record too short
RecoverDelete[openDoc: openDoc, outcome: outcome];
END;
setSize =>
BEGIN
IF wordsRead<SIZE[FileLogRecord[setSize]] THEN ERROR; -- log record too short
RecoverSetSize[openDoc: openDoc, old: r.old, new: r.new, recordID: record, outcome: outcome];
END;
writePages =>
BEGIN
IF wordsRead<SIZE[FileLogRecord[writePages]] THEN ERROR; -- log record too short
RecoverWritePages[openDoc: openDoc, recordID: record, pageRun: r.pageRun, outcome: outcome];
END;
lock =>
BEGIN
IF wordsRead<SIZE[FileLogRecord[lock]] THEN ERROR; -- log record too short
RecoverLock[openDoc: openDoc, lockSubID: r.lockSubID, outcome: outcome];
END;
ENDCASE => ERROR;
Unregister to balance the above Register, so the openDoc use count will be zero when recovery finished. Pretend that an update has occurred (even if it hasn't) so that the openDoc will stay around.
YggOpenDoc.SetMaxDeltaVersion[openDoc, 1];
YggOpenDoc.Unregister[openDoc];
END;
DID <-> log ID mapping.
Use the DID when we get off of UNIX/Cedar file systems. Remove this code then.
ROPE: TYPE ~ Rope.ROPE;
DID: PUBLIC TYPE ~ REF DIDRep;
DIDRep:
PUBLIC TYPE ~
ROPE;
For Phase 0, the DIDRep is just a string that names a directory (without the trailing /).
didToIDMap: SymTab.Ref ← NIL;
idToDIDMap: CardTab.Ref ← NIL;
nextID: CARD ← 1000;
LookupIDInMap:
ENTRY
PROC [did:
DID]
RETURNS [id:
CARD ← 0] ~ {
Given a DID, return the log id. A 0 return means not found.
found: BOOL;
val: REF;
DO
[found, val] ← SymTab.Fetch[x: didToIDMap, key: did^];
IF found
THEN {
entry: REF CARD;
entry ← NARROW[val];
RETURN[entry^];
};
ENDLOOP;
};
LookupDIDInMap:
ENTRY
PROC [id:
CARD]
RETURNS [did:
DID ← NIL] ~ {
Given an ID, return the DID. A NIL return means not found.
found: BOOL;
val: REF;
DO
[found, val] ← CardTab.Fetch[x: idToDIDMap, key: id];
IF found
THEN {
entry: DIDRep;
entry ← NARROW[val];
did ← NEW[DIDRep];
did^ ← entry;
};
ENDLOOP;
};
StoreDIDInMaps:
ENTRY PROC [did:
DID, id:
CARD]
RETURNS [idUsed:
CARD]~ {
Add this did and id.
IF id = 0 THEN {idUsed ← nextID; nextID ← nextID + 1;} ELSE idUsed ← id;
[] ← SymTab.Store[x: didToIDMap, key: did^, val: NEW[CARD ← idUsed]];
[] ← CardTab.Store[x: idToDIDMap, key: idUsed, val: NEW[ROPE ← did^]];
};