-- Copyright (C) 1981, 1984 by Xerox Corporation. All rights reserved.
-- Log.mesa, Transport Mechanism Mail Server: disk log --
-- HGM, 3-Dec-84 22:38:43
-- Andrew Birrell September 14, 1982 2:03 pm --
-- Redell 20-May-81 17:13:35 --
-- M. D. Schroeder June 7, 1982 7:07 PM --
-- Hankins 13-Aug-84 16:27:50 (add ReleasePage in DoSpill)
-- Wobber 29-Aug-84 17:58:36 (fix above faulty fix)
DIRECTORY
Ascii USING [CR, SP],
LogDefs USING [],
LogWatchDefs USING [],
Process USING [DisableTimeout, SecondsToTicks, SetTimeout],
Storage USING [String],
String USING [
AppendChar, AppendDecimal, AppendString, AppendSubString, EqualSubString,
SubString, SubStringDescriptor],
Time USING [Append, Current, Unpack],
VMDefs USING [
AbandonFile, CantOpen, CantReadBackingStore, CantWriteBackingStore, CloseFile,
Error, FileHandle, FileSystem, Login, Logout, LookAheadCount, MarkStart,
OpenFile, Page, PageAddress, PageByteIndex, pageByteSize, PageNumber,
Position, ReadPage, Release, SetCreationTime, SetFileLength,
UnableToLogin, UsePage, Wait];
Log: MONITOR
IMPORTS Process, Storage, String, Time, VMDefs EXPORTS LogDefs, LogWatchDefs =
BEGIN OPEN Ascii, String, Time, VMDefs;
halfPagesInLogFile: CARDINAL = 60;
pagesInLogFile: CARDINAL = halfPagesInLogFile * 2;
endMarker: CHARACTER = '←;
dayLength: CARDINAL = 10; -- format is "dd-mmm-yy "
timeLength: CARDINAL = 8; --format is "hh:mm:ss"
spillLimit: CARDINAL = 40; -- cycle length for spilling log files
logSequence: [0..spillLimit); -- number for backing up this log file cycle
logFileHandle: FileHandle;
current: Position;
pageArray: POINTER TO PACKED ARRAY PageByteIndex OF CHARACTER ← NIL;
firstPage: Page ← NIL;
lastDay: STRING = [dayLength];
lastDaySSD: SubStringDescriptor ← [lastDay, 0, dayLength];
lastDaySS: SubString = @lastDaySSD;
logChange: CONDITION ← [timeout: Process.SecondsToTicks[2]];
-- procedure to write a log entry --
WriteLogEntry: PUBLIC ENTRY PROCEDURE [s: STRING] =
BEGIN
dayTimeString: STRING ← [dayLength + timeLength];
timeString: STRING ← [timeLength];
daySSD: SubStringDescriptor ← [dayTimeString, 0, dayLength];
daySS: SubString = @daySSD;
timeSSD: SubStringDescriptor ← [dayTimeString, dayLength, timeLength];
timeSS: SubString = @timeSSD;
front: STRING = " "L;
crString: STRING = "
"L;
Append[dayTimeString, Unpack[Current[]]];
IF NOT EqualSubString[daySS, lastDaySS] THEN
BEGIN
lastDay.length ← 0;
AppendSubString[lastDay, daySS];
LogLastDay[];
END;
AppendSubString[timeString, timeSS];
StartLogEntry[];
PutStringInLog[timeString];
PutStringInLog[front];
PutStringInLog[s];
PutStringInLog[crString];
FinishLogEntry[];
BROADCAST logChange;
END;
-- procedure to start a log entry
StartLogEntry: INTERNAL PROCEDURE =
BEGIN
Wait[LOOPHOLE[pageArray, VMDefs.Page]];
-- make sure previous write completed before updating page
END;
-- procedure to put a string into the log file
PutStringInLog: INTERNAL PROCEDURE [ls: STRING] =
BEGIN
c: CHARACTER;
Copy: PROC [offset: CARDINAL] RETURNS [done: CARDINAL] =
BEGIN
done ← MIN[pageByteSize - current.byte, ls.length - offset];
FOR i: CARDINAL IN [0..done) DO
pageArray↑[current.byte + i] ←
IF (c ← ls[i + offset]) = endMarker THEN SP ELSE c;
ENDLOOP;
current.byte ← current.byte + done;
END;
firstChunkLength: CARDINAL = Copy[0];
IF current.byte = pageByteSize --cross a page boundary--
AND firstPage = NIL --not at end of second page--
THEN
BEGIN
current.page ← current.page + 1;
SELECT TRUE FROM
(current.page >= pagesInLogFile) => {
current.page ← 0; SpillLog[second, logSequence]};
(current.page = halfPagesInLogFile) => SpillLog[first, logSequence];
ENDCASE => NULL;
firstPage ← LOOPHOLE[pageArray, VMDefs.Page]; --remember first page
GetCurrentLogPage[]; --get second page; sets current.byte
[] ← Copy[firstChunkLength];
END;
IF current.byte = pageByteSize THEN current.byte ← current.byte - 1 -- leave room for endMarker;
END;
-- procedure to complete a log entry
FinishLogEntry: INTERNAL PROCEDURE =
BEGIN
pageArray↑[current.byte] ← endMarker; --will be overwritten by next log entry
MarkStart[LOOPHOLE[pageArray, VMDefs.Page]];
--writes second page first if entry crosses page boundary
IF firstPage # NIL THEN
BEGIN -- wait for second page write to complete then write and release first page
Wait[LOOPHOLE[pageArray, VMDefs.Page]];
MarkStart[firstPage];
Release[firstPage];
firstPage ← NIL;
IF current.page = 0 OR current.page = halfPagesInLogFile THEN LogLastDay[];
-- write day at start of each half of file
END;
END;
-- procedure to write a log entry containing the last day value
LogLastDay: INTERNAL PROCEDURE =
BEGIN
binaryBit: STRING = [1];
binaryBit[0] ← 377C;
binaryBit.length ← 1;
lastDay[lastDay.maxlength - 1] ← CR;
StartLogEntry[];
PutStringInLog[binaryBit];
PutStringInLog[lastDay];
FinishLogEntry[];
SetCreationTime[logFileHandle];
lastDay[lastDay.maxlength - 1] ← SP;
END;
-- procedure to read the current log page --
GetCurrentLogPage: INTERNAL PROCEDURE =
BEGIN
pageArray ← LOOPHOLE[ReadPage[
addr: PageAddress[file: logFileHandle, page: current.page], lookAhead: 0]];
current.byte ← 0;
IF current.page = 0 THEN
BEGIN
logSequence ← (logSequence + 1) MOD spillLimit;
pageArray[0] ← LOOPHOLE[logSequence];
current.byte ← 1;
-- but don't mark it dirty yet --
END;
END;
-- procedures to implement the LogWatchDefs interface --
LogPosition: PUBLIC TYPE = VMDefs.Position;
GetLogPosition: PUBLIC ENTRY PROCEDURE RETURNS [LogPosition] = {
RETURN[current]};
PutLogChars: PUBLIC PROCEDURE [
p: LogPosition, put: PROCEDURE [CHARACTER], gap: PROCEDURE,
stop: PROCEDURE RETURNS [BOOLEAN]] RETURNS [LogPosition] =
BEGIN
e: Position;
page: POINTER TO PACKED ARRAY PageByteIndex OF CHARACTER;
WaitForNewChars: ENTRY PROCEDURE = INLINE {
IF p = current THEN WAIT logChange; e ← current};
CaughtUp: ENTRY PROCEDURE RETURNS [BOOLEAN] = INLINE {RETURN[p = current]};
DO
WaitForNewChars[];
IF p # e THEN
BEGIN
ENABLE UNWIND => Release[LOOPHOLE[page, Page]];
lastByte: PageByteIndex;
page ← LOOPHOLE[ReadPage[PageAddress[logFileHandle, p.page], 0]];
lastByte ← IF p.page = e.page THEN e.byte ELSE pageByteSize;
FOR i: PageByteIndex IN [p.byte..lastByte) DO
IF p.page # 0 OR i # 0 THEN put[page[i]]; -- First character in file is log file #
ENDLOOP;
p ← IF p.page = e.page THEN e ELSE [(p.page + 1) MOD pagesInLogFile, 0];
IF CaughtUp[] THEN gap[];
Release[LOOPHOLE[page, Page]];
END;
IF stop[] THEN RETURN[p];
ENDLOOP;
END; -- PutLogChars --
-- procedure to spill half of log to backing file server
SpillRequest: TYPE = {first, second};
spillRequest: SpillRequest; -- which half to spill this time --
spillSequence: [0..spillLimit); -- which file to spill to this time --
spillerWanted: BOOLEAN; -- spilling request has been set up --
spillerChange: CONDITION; -- used in both directions --
spillerSeconds: CARDINAL ← 5 * 60; -- delay between retries after failures --
spillerProcess: PROCESS; -- for debugging only --
myName: STRING ← NIL; -- credentials for spilling --
myPwd: STRING ← NIL;
server: STRING ← NIL; -- server name for spilling --
spillName: STRING ← NIL; -- spill file name --
spillPrefix: CARDINAL;
spillEnabled: BOOLEAN ← FALSE;
KeepString: INTERNAL PROC [from: STRING, extra: CARDINAL ← 0]
RETURNS [to: STRING] =
BEGIN
to ← Storage.String[from.length + extra];
String.AppendString[to, from];
END;
EnableLogSpilling: PUBLIC ENTRY PROC [name, pwd, host, path: STRING] =
BEGIN
IF spillEnabled THEN RETURN;
myName ← KeepString[name];
myPwd ← KeepString[pwd];
server ← KeepString[host];
spillName ← KeepString[path, myName.length + 5 -- "Cabernet-00!1" -- ];
spillPrefix ← path.length;
spillEnabled ← TRUE;
-- Assume last spill of previous run failed, and possibly re-try --
IF current.page < halfPagesInLogFile THEN -- previous request was for second half of previous cycle --
BEGIN
spillRequest ← second;
spillSequence ← (logSequence + spillLimit - 1) MOD spillLimit;
END
ELSE -- last spill request was for first half of this cycle --
BEGIN spillRequest ← first; spillSequence ← logSequence; END;
spillerWanted ← FALSE; -- Spiller may retry after normal delay --
spillerProcess ← FORK LogSpiller[];
END;
SpillLog: INTERNAL PROC [half: SpillRequest, cycle: [0..spillLimit)] =
BEGIN
IF spillEnabled THEN
BEGIN
-- don't wait for spiller if it isn't ready: it might take too long!
spillRequest ← half;
spillSequence ← cycle;
spillerWanted ← TRUE;
NOTIFY spillerChange;
END;
END;
WaitForSpillRequest: ENTRY PROC [completed: BOOLEAN]
RETURNS [SpillRequest, [0..spillLimit)] =
BEGIN
IF completed THEN Process.DisableTimeout[@spillerChange]
ELSE
Process.SetTimeout[@spillerChange, Process.SecondsToTicks[spillerSeconds]];
DO
IF NOT spillerWanted THEN WAIT spillerChange;
IF spillerWanted -- new request --
OR
( -- possible retry after timeout --
NOT completed
AND
((spillRequest = first
AND current.page < halfPagesInLogFile + halfPagesInLogFile / 2)
OR
(spillRequest = second
AND current.page < halfPagesInLogFile / 2))) THEN EXIT;
ENDLOOP;
spillerWanted ← FALSE;
RETURN[spillRequest, spillSequence]
END;
LogSpiller: PROC =
BEGIN
Behind: ENTRY PROC RETURNS [BOOLEAN] = INLINE {RETURN[spillerWanted]};
completed: BOOLEAN ← FALSE; -- assume last run failed --
DO
half: SpillRequest;
currentCycle: [0..spillLimit);
[half, currentCycle] ← WaitForSpillRequest[completed];
BEGIN
ENABLE
VMDefs.UnableToLogin, VMDefs.CantOpen, VMDefs.Error,
VMDefs.CantReadBackingStore, VMDefs.CantWriteBackingStore => GOTO crash;
DoSpill[half, currentCycle];
completed ← TRUE;
EXITS crash => completed ← FALSE;
END;
LogSpillEnded[half, spillName, completed, Behind[]];
ENDLOOP;
END;
LogSpillEnded: PROC [half: SpillRequest, file: STRING, ok, late: BOOLEAN] =
BEGIN
text: STRING = [128];
String.AppendString[text, "Log "L];
String.AppendString[text, IF ok THEN "spilled: ["L ELSE "spill failed: ["L];
String.AppendString[text, server];
String.AppendString[text, "]"L];
String.AppendString[text, file];
String.AppendString[text, IF half = first THEN ", first"L ELSE ", second"L];
String.AppendString[text, " half"L];
WriteLogEntry[text];
IF late THEN WriteLogEntry["Started next spill request late"L];
END;
DoSpill: --EXTERNAL-- PROC [half: SpillRequest, currentCycle: [0..spillLimit)] =
BEGIN
fs: VMDefs.FileSystem = VMDefs.Login[IFS, server, myName, myPwd];
spillName.length ← spillPrefix;
String.AppendString[spillName, myName];
FOR i: CARDINAL DECREASING IN [spillPrefix..spillName.length) DO
IF spillName[i] = '. THEN {spillName.length ← i; EXIT} ENDLOOP;
String.AppendChar[spillName, '-];
IF currentCycle < 10 THEN String.AppendChar[spillName, '0];
String.AppendDecimal[spillName, currentCycle];
String.AppendString[spillName, "!1"L];
BEGIN
ENABLE UNWIND => VMDefs.Logout[fs];
file: VMDefs.FileHandle ← VMDefs.OpenFile[
system: fs, name: spillName, options: oldOrNew];
from: VMDefs.Page ← NIL;
BEGIN
ENABLE UNWIND => VMDefs.AbandonFile[file];
base: VMDefs.PageNumber = IF half = first THEN 0 ELSE halfPagesInLogFile;
limit: VMDefs.PageNumber = base + halfPagesInLogFile;
VMDefs.SetFileLength[file, [page: limit, byte: 0]];
FOR p: VMDefs.PageNumber IN [base..limit) DO
to: VMDefs.Page = VMDefs.UsePage[[file, p]];
BEGIN
ENABLE UNWIND => VMDefs.Release[to];
from: VMDefs.Page ← VMDefs.ReadPage[[logFileHandle, p], 2];
to↑ ← from↑;
VMDefs.Release[from]; -- must be released first --
VMDefs.MarkStart[to];
--TEMP-- VMDefs.Wait[to]; -- avoid VM deadlock on connection failure --
--? just how temp is this latter? can it be removed in Klamath?
END; -- of enable unwind
VMDefs.Release[to];
ENDLOOP;
VMDefs.CloseFile[file];
END;
END;
VMDefs.Logout[fs];
END;
-- initialization procedure
InitializeLog: ENTRY PROCEDURE =
BEGIN
AppendString[lastDay, "01-Jan-01 "L]; -- forces day to be logged on startup
logFileHandle ← OpenFile[
name: "GV.log"L, options: oldOrNew, cacheFraction: 0];
SetFileLength[
file: logFileHandle, position: Position[page: pagesInLogFile, byte: 0]];
-- initialize "logSequence" --
current.page ← 0;
pageArray ← LOOPHOLE[ReadPage[
addr: PageAddress[file: logFileHandle, page: 0], lookAhead: 0]];
logSequence ← (LOOPHOLE[pageArray[0], CARDINAL]) MOD spillLimit;
current.byte ← 1;
-- search for endMarker --
DO
FOR i: CARDINAL IN [current.byte..pageByteSize) DO
IF pageArray↑[i] = endMarker THEN {current.byte ← i; GOTO found}; ENDLOOP;
Release[LOOPHOLE[pageArray, VMDefs.Page]];
current.page ← current.page + 1;
IF current.page = pagesInLogFile THEN
BEGIN
-- no endMarker found: start at 0 and increment logSequence --
current.page ← 0;
GetCurrentLogPage[];
EXIT
END;
GetCurrentLogPage[];
REPEAT found => NULL;
ENDLOOP;
END;
-- initialization code
InitializeLog[];
END.