SpyLogImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
John Maxwell November 28, 1983 2:49 pm
Bob Hagmann November 21, 1986 3:03:10 pm PST
Russ Atkinson (RRA) March 31, 1986 7:29:10 pm PST
Mike Spreitzer June 26, 1986 5:59:41 pm PDT
DIRECTORY
FS USING [Close, Create, Error, GetInfo, nullOpenFile, OpenFile, Read, SetPageCount, Write],
Loader USING [MakeGlobalFrameResident, MakeGlobalFrameSwappable, MakeProcedureResident, MakeProcedureSwappable],
PrincOps USING [BytePC, FrameHandle, GlobalFrameHandle, PsbHandle, wordsPerPage],
PrincOpsUtils USING [GetClockPulses, GetReturnFrame, LongCopy, ReadPSB],
Process USING [Detach, GetCurrent, GetPriority, MsecToTicks, Pause, Priority, priorityForeground, SetPriority, Ticks],
Rope USING [ROPE],
SpyLog,
VM USING [AddressForPageNumber, Allocate, Free, Interval, nullInterval, PageNumber, PageNumberForAddress, Pin, Unpin];
SpyLogImpl: MONITOR
IMPORTS FS, Loader, PrincOpsUtils, Process, VM
EXPORTS SpyLog = {
OPEN SpyLog;
active: PUBLIC BOOLFALSE; -- true iff we are writing to the log.
Error: PUBLIC ERROR = CODE;
bufferReady: CONDITION;
Log: TYPE = REF LogDesc; -- want it pinned in the global frame
LogDesc: TYPE = RECORD [
base: EntryBasePointer ← NIL,
index: EntryPointer ← FIRST[EntryPointer],
file: FS.OpenFile ← FS.nullOpenFile,
fileName: Rope.ROPENIL,
filePage: INT ← 0,
pages: INT ← 0,
doneWriting: BOOLFALSE,
buffer: VM.Interval ← VM.nullInterval,
next: VM.Interval ← VM.nullInterval,
bufferProcess: PROCESSNIL,
bufferProcessDone: BOOLFALSE,
extendProcess: PROCESSNIL];
EntryBasePointer: TYPE = LONG ORDERED BASE POINTER;
EntryPointer: TYPE = EntryBasePointer RELATIVE POINTER [0..177777B] TO Entry;
control
bufferPages: CARDINAL = 40;
buffersAhead: INT = 8;
limit: EntryPointer = LOOPHOLE[bufferPages*PrincOps.wordsPerPage];
log1: Log = NEW[LogDesc ← [fileName: "///Spy/Spy.data"]];
log2: Log = NEW[LogDesc ← [fileName: "///Spy/SpyingOnSpy.data"]];
Init: PROC = {
Pin: PROC[ref: REF, length: CARDINAL] = {
page1: VM.PageNumber = VM.PageNumberForAddress[LOOPHOLE[LOOPHOLE[ref, LONG CARDINAL]-2]];
page2: VM.PageNumber = VM.PageNumberForAddress[LOOPHOLE[LOOPHOLE[ref, LONG CARDINAL]+length-1]];
VM.Pin[[page1, page2-page1+1]];
};
Pin[log1, LogDesc.SIZE];
Pin[log2, LogDesc.SIZE];
OpenLog[log1];
};
InvisibleProcesses: PUBLIC ENTRY PROC RETURNS [PROCESS, PROCESS] = {
RETURN[IF writeLog = NIL THEN NIL ELSE writeLog.bufferProcess,
IF writeLog = NIL THEN NIL ELSE writeLog.extendProcess];
};
OpenForRead: PUBLIC ENTRY PROC = {
IF readLog # NIL THEN RETURN; -- already have a log
IF writeLog # NIL THEN { -- read from the write log
FinalizeWriteLog[writeLog];
readLog ← writeLog;
writeLog ← NIL};
IF readLog = NIL AND log1.file # FS.nullOpenFile THEN readLog ← log1; -- use the last log
IF readLog = NIL THEN { -- create a dummy log (the one on the disk isn't valid)
InitializeWriteLog[readLog ← log1];
FinalizeWriteLog[readLog]};
MakeSwappable[];
readLog.filePage ← 0;
readLog.index ← limit;
};
OpenForWrite: PUBLIC ENTRY PROC[spyOnSpyLog: BOOL] = {
IF ~spyOnSpyLog THEN writeLog ← log1 ELSE {
readLog ← log1;
readLog.filePage ← 0;
readLog.index ← limit;
writeLog ← log2};
InitializeWriteLog[writeLog];
MakeResident[];
};
Close: PUBLIC ENTRY PROC = {
don't close the alternate log
IF readLog # NIL THEN readLog ← NIL;
IF writeLog = log1 THEN {FinalizeWriteLog[writeLog]; writeLog ← NIL};
IF writeLog = NIL THEN MakeSwappable[];
};
InitializeWriteLog: PROC[log: Log] = {
initialize the log for writing
OpenLog[log];
VM.Pin[log.buffer];
VM.Pin[log.next];
log.base ← LOOPHOLE[VM.AddressForPageNumber[log.buffer.page]];
log.index ← FIRST[EntryPointer];
log.filePage ← 0;
};
FinalizeWriteLog: INTERNAL PROC[log: Log] = {
endWords: NAT = SIZE[endOfLog SpyLog.Entry];
end: SpyLog.Entry ← [endOfLog[0]];
IF log.bufferProcess # NIL THEN {
WHILE ~log.bufferProcessDone DO
WAIT bufferReady; -- drop monitor to avoid deadlock
ENDLOOP;
JOIN log.bufferProcess;
log.bufferProcess ← NIL;
log.bufferProcessDone ← FALSE;
};
PrincOpsUtils.LongCopy[from: @end, nwords: endWords, to: @log.base[log.index]];
log.doneWriting ← TRUE;
Process.Pause[11];
FS.SetPageCount[log.file, log.filePage + bufferPages];
FS.Write[log.file, log.filePage, bufferPages, log.base];
VM.Unpin[log.buffer];
VM.Unpin[log.next];
};
OpenLog: PROC [log: Log] = {
IF log.file = FS.nullOpenFile THEN {
log.file ← FS.Create[name: log.fileName, pages: 4*bufferPages];
log.buffer ← VM.Allocate[bufferPages];
log.next ← VM.Allocate[bufferPages];
}
ELSE {
FS.SetPageCount[file: log.file, pages: 4*bufferPages];
};
log.pages ← 4*bufferPages;
log.doneWriting ← FALSE;
TRUSTED {Process.Detach[log.extendProcess ← FORK LogExtender[log]];};
};
CloseLog: PROC [log: Log] = {
IF log.file = FS.nullOpenFile THEN RETURN;
FS.Close[log.file]; log.file ← FS.nullOpenFile;
VM.Free[log.buffer]; log.buffer ← VM.nullInterval;
VM.Free[log.next]; log.next ← VM.nullInterval;
};
MakeResident: PROC = {
IF ~active THEN {
Loader.MakeGlobalFrameResident[WriteData];
Loader.MakeProcedureResident[WriteData];
Loader.MakeProcedureResident[WriteTrace];
Loader.MakeProcedureResident[NextWriteBuffer];
active ← TRUE};
};
MakeSwappable: PROC = {
IF active THEN {
active ← FALSE;
Loader.MakeGlobalFrameSwappable[WriteData];
Loader.MakeProcedureSwappable[WriteData];
Loader.MakeProcedureSwappable[WriteTrace];
Loader.MakeProcedureSwappable[NextWriteBuffer]};
};
reading and writing
readLog, writeLog: Log ← NIL;
NextEntry: PUBLIC ENTRY PROC RETURNS [entry: LONG POINTER TO Entry] = {
IF readLog = NIL OR readLog.base = NIL THEN RETURN [NIL];
IF LOOPHOLE[readLog.index, CARDINAL] >= LOOPHOLE[limit, CARDINAL] THEN {
FS.Read[readLog.file, readLog.filePage, bufferPages, readLog.base];
readLog.filePage ← readLog.filePage + bufferPages;
readLog.index ← FIRST[EntryPointer];
IF readLog.base[readLog.index].type = nullEntry THEN ERROR};
entry ← @readLog.base[readLog.index];
move the index to the next entry
WITH entry: entry SELECT FROM
endOfLog => NULL;
trace => readLog.index ← readLog.index + SIZE[trace Entry];
data => readLog.index ← readLog.index + SIZE[data Entry] + entry.size;
nullEntry => readLog.index ← limit;
ENDCASE => ERROR;
RETURN[entry];
};
WriteData: PUBLIC ENTRY PROC [data: LONG POINTER, size: [0..8000], type: CARDINAL] = {
entrySizeWithoutData: CARDINAL = SIZE[data Entry];
entrySizeWithData: CARDINAL = entrySizeWithoutData + size;
IF writeLog = NIL THEN RETURN;
IF writeLog.bufferProcess # NIL AND writeLog.bufferProcess = Process.GetCurrent[] THEN RETURN; -- deadlock avoidance — FilePackage can do allocates during buffer write
are we at the end of the buffer?
IF LOOPHOLE[writeLog.index+entrySizeWithData, CARDINAL] >= LOOPHOLE[limit, CARDINAL] THEN {
NextWriteBuffer[writeLog];
writeLog.index ← FIRST[EntryPointer];
};
{
write out the entry.
ent: SpyLog.Entry ← [data[size: size, rttype: type, timestamp: PrincOpsUtils.GetClockPulses[], data: NULL]];
PrincOpsUtils.LongCopy[from: @ent, nwords: entrySizeWithoutData, to: @writeLog.base[writeLog.index]];
WITH e: writeLog.base[writeLog.index] SELECT FROM
data => PrincOpsUtils.LongCopy[from: data, nwords: size, to: @e.data];
ENDCASE => ERROR;
writeLog.index ← writeLog.index + entrySizeWithData;
};
};
WriteTrace: PUBLIC ENTRY PROC [gfh: PrincOps.GlobalFrameHandle, pc: PrincOps.BytePC] = {
entrySize: CARDINAL = SIZE[trace SpyLog.Entry];
IF writeLog = NIL THEN RETURN;
are we at the end of the buffer?
IF LOOPHOLE[writeLog.index+SIZE[trace Entry], CARDINAL] >= LOOPHOLE[limit, CARDINAL] THEN {
NextWriteBuffer[writeLog];
writeLog.index ← FIRST[EntryPointer];
};
write out the entry.
IF gfh = NIL AND pc = 0 THEN {
frame: PrincOps.FrameHandle = PrincOpsUtils.GetReturnFrame[];
gfh ← frame.accesslink;
pc ← frame.pc;
};
{
ent: SpyLog.Entry ← [trace[fill: 0, gfh: gfh, pc: pc, process: PrincOpsUtils.ReadPSB[], timestamp: PrincOpsUtils.GetClockPulses[]]];
PrincOpsUtils.LongCopy[from: @ent, nwords: entrySize, to: @writeLog.base[writeLog.index]];
writeLog.index ← writeLog.index + entrySize;
};
};
NextWriteBuffer: INTERNAL PROCEDURE [log: Log] = {
entrySize: CARDINAL = SIZE[nullEntry SpyLog.Entry];
temp: VM.Interval;
ent: SpyLog.Entry ← [nullEntry[limit-log.index]];
PrincOpsUtils.LongCopy[from: @ent, nwords: entrySize, to: @log.base[log.index]];
wait until next buffer is written, then fork a write
IF log.bufferProcess # NIL THEN {
WHILE ~log.bufferProcessDone DO
WAIT bufferReady; -- drop monitor to avoid deadlock
ENDLOOP;
JOIN log.bufferProcess;
log.bufferProcess ← NIL;
log.bufferProcessDone ← FALSE;
};
IF log.bufferProcess # NIL THEN {JOIN log.bufferProcess; log.bufferProcess ← NIL};
log.bufferProcess ← FORK WriteBuffer[log, log.filePage, log.base];
update the indices
temp ← log.buffer; log.buffer ← log.next; log.next ← temp;
log.base ← LOOPHOLE[VM.AddressForPageNumber[log.buffer.page]];
log.filePage ← log.filePage + bufferPages;
};
WriteBuffer: PROC [log: Log, filePage: INT, base: EntryBasePointer] = {
writeTries: INT ← 0;
writeDone: ENTRY PROC = {
log.bufferProcessDone ← TRUE;
BROADCAST bufferReady;
};
priority: Process.Priority ← Process.GetPriority[];
IF priority > Process.priorityForeground THEN Process.SetPriority[Process.priorityForeground];
FS.SetPageCount[log.file, filePage + bufferPages];
The MIN with GetInfo is an attempt to fix a bug in writing past EOF. It shouldn't be needed, but ...
WHILE MIN[log.pages, FS.GetInfo[log.file].pages] < filePage + bufferPages DO
Process.Pause[1]; -- wait for room
ENDLOOP;
FS.Write[log.file, filePage, bufferPages, base ! FS.Error => {
This is another attempt to fix a bug in writing past EOF. It (also) shouldn't be neededed, but ...
IF error.code = $unknownPage AND writeTries < 10 THEN {
nowPages: INT;
hundredMilliseconds: Process.Ticks = Process.MsecToTicks[100];
writeTries ← writeTries + 1;
FOR i:INT IN [0..99) DO
nowPages ← FS.GetInfo[log.file].pages;
IF nowPages >= filePage + bufferPages THEN EXIT;
Process.Pause[hundredMilliseconds];
ENDLOOP;
RETRY;
};
};
];
writeDone[];
};
LogExtender: PROC [log: Log] = {
The LogExtender process is a temporary way to get the FS.SetPageCount call out of WriteBuffer. The idea is to keep enough pages in the file so the FS.Write succeeds. Letting WriteBuffer do the SetPageCount can cause a deadlock in the BTree package.
WHILE ~log.doneWriting DO
newPages: INT;
newPages ← log.filePage + (buffersAhead * bufferPages);
IF (log.pages - log.filePage)/bufferPages < buffersAhead THEN {
FS.SetPageCount[log.file, MAX[log.pages, newPages]];
log.pages ← FS.GetInfo[log.file].pages;
};
Process.Pause[5];
ENDLOOP;
};
Init[];
}. . .
Bob Hagmann June 3, 1985 4:15:39 pm PDT
fix off by one in Pin - if unlucky you would pin an extra page
changes to: Pin (local of Init)
Russ Atkinson (RRA) June 6, 1985 5:47:30 pm PDT
added error recovery if the log cannot be opened
Bob Hagmann October 24, 1986 10:31:52 am PDT
Fixed to avoid deadlock while using the labelless file system