WalnutOpsBasicImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Willie-Sue, April 1, 1986 5:03:10 pm PST
Donahue, July 16, 1985 2:25:35 pm PDT
Woosh, April 1, 1986 2:19:18 pm PST
Implementation of basic operations of WalnutOps
Last Edited by: Willie-sue, January 10, 1985 3:55:08 pm PST
Last Edited by: Donahue, December 11, 1984 9:01:10 pm PST
DIRECTORY
BasicTime USING [GMT, nullGMT, Now],
FS USING [StreamOpen],
GVBasics USING [RName],
IO,
Process USING [Detach, SecondsToTicks],
Rope,
UserCredentials USING [Get],
ViewerClasses USING [Viewer],
ViewerIO USING [CreateViewerStreams, GetViewerFromStream],
ViewerOps USING [FindViewer],
WalnutDB --using lots-- ,
WalnutDefs USING [Error, SchemaMismatch, Segment, VersionMismatch],
WalnutLog --using lots-- ,
WalnutLogExpunge USING [Shutdown],
WalnutMiscLog USING [Shutdown],
WalnutOps,
WalnutOpsInternal,
WalnutOpsMonitorImpl,
WalnutRegistryPrivate USING [NotifyForEvent],
WalnutRoot USING [AbortTransaction, CloseTransaction, CommitAndContinue, EraseDB, Open, RegisterStatsProc, Shutdown, StartTransaction, UnregisterStatsProc];
WalnutOpsBasicImpl:
CEDAR
MONITOR LOCKS walnutOpsMonitorImpl
IMPORTS
FS, IO, Process, Rope, UserCredentials, ViewerIO, ViewerOps,
walnutOpsMonitorImpl: WalnutOpsMonitorImpl,
WalnutDB, WalnutDefs,
WalnutLog, WalnutLogExpunge, WalnutMiscLog,
WalnutOpsInternal, WalnutRegistryPrivate, WalnutRoot
EXPORTS WalnutOps, WalnutOpsInternal
SHARES WalnutOpsMonitorImpl
= BEGIN OPEN WalnutOps, WalnutOpsInternal;
Types
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
GMT: TYPE = BasicTime.GMT;
Signals
Error: PUBLIC SIGNAL [who, code: ATOM, explanation: ROPE ← NIL];
Variables
Active: PUBLIC ROPE ← "Active";
Deleted: PUBLIC ROPE ← "Deleted";
reporterList: LIST OF IO.STREAM← NIL;
checkActivityCondition: CONDITION ← [timeout: Process.SecondsToTicks[5*60]];
started: BOOL ← FALSE;
replayInProgress: BOOL ← FALSE;
interference: BOOL ← FALSE;
walnutRootFile: ROPE;
recentActivity: BOOL ← FALSE;
isShutdown: PUBLIC BOOL ← FALSE;
errorInProgress: PUBLIC BOOL ← FALSE;
walnutSegment: PUBLIC WalnutDefs.Segment;
systemIsReadOnly: PUBLIC BOOL ← FALSE;
rootFileName: PUBLIC ROPE;
newMailSomewhere: PUBLIC BOOL ← FALSE;
statsStream, statsProgressStream: STREAM ← NIL;
heavyhandedDebugging: BOOL ← FALSE;
statsProgressTS: ViewerClasses.Viewer ← NIL;
mailStream: PUBLIC STREAM;
Procedures
These procedures are the primitive atomic actions out of which Walnut is built. More complex operations will be found in WalnutClientOps (someday).
Starting and stopping Walnut
Startup:
PUBLIC
ENTRY
PROC [rootFile:
ROPE, wantReadOnly:
BOOL ←
FALSE]
RETURNS[isReadOnly, newMailExists:
BOOL, mailFor: GVBasics.RName, key:
ROPE] = {
Start up Walnut with a reference to a Walnut log file. This may involve replaying any actions specified in the "tail" of the log (that portion beyond the lastCommit) so that the database and log agree (in fact, if the database does not exist, it may involve reconstruction of the database in its entirety). If fullScavenge is TRUE, the database is emptied and reconstructed using information from the log. If wantReadOnly is TRUE, the log and the database are opened for reading only. If the log or the database cannot be written, readOnly is TRUE upon return.
already started ($AlreadyStarted).
database or log has become inaccessible ($DatabaseInaccessible, $LogInaccessible)
cannot write the log or the database (return with isReadOnly TRUE).
ENABLE {
WalnutDefs.Error => { errorInProgress ← TRUE; REJECT};
UNWIND => NULL;
};
rootFileCreateDate: GMT;
schemaInvalid: BOOL ← TRUE;
InitialCheck:
INTERNAL
PROC = {
rootCreateDate: GMT;
rootFileKey, mailForFromDB: ROPE;
IF key.Length[] # 0
THEN {
[rootCreateDate, rootFileKey, mailForFromDB] ← WalnutDB.GetRootInfo[];
IF rootCreateDate = BasicTime.nullGMT
OR
(rootCreateDate = rootFileCreateDate
AND
rootFileKey.Length[] = 0 AND
mailForFromDB.Length[] = 0)
THEN {
WalnutDB.SetRootInfo[rootFileCreateDate, key, mailFor];
WalnutRoot.CommitAndContinue[];
}
ELSE {
IF ~rootFileKey.Equal[key,
FALSE]
THEN
WalnutDefs.Error[$db, $WrongRootFile,
IO.PutFR["RootFile has key %g, database says %g",
IO.rope[key], IO.rope[rootFileKey]]];
IF ~mailForFromDB.Equal[mailFor,
FALSE]
THEN
WalnutDefs.Error[$db, $WrongRootFile,
IO.PutFR["RootFile says mailFor %g, database says %g",
IO.rope[mailFor], IO.rope[mailForFromDB]]];
IF rootCreateDate # rootFileCreateDate
THEN
WalnutDefs.Error[$db, $WrongRootFile,
IO.PutFR["RootFile has date %g, database says %g",
IO.time[rootFileCreateDate], IO.time[rootCreateDate]]];
};
};
IF (WalnutOpsInternal.newMailSomewhere ← WalnutDB.GetNewMailLogLength[] # 0)
THEN RETURN;
check for unaccepted mail
BEGIN
serverList: LIST OF ServerInfo ← WalnutDB.EnumerateServers[];
FOR sL:
LIST
OF ServerInfo ← serverList, sL.rest
UNTIL sL =
NIL
DO
IF (WalnutOpsInternal.newMailSomewhere ← sL.first.num#0) THEN RETURN;
ENDLOOP;
END;
};
IF started THEN RETURN WITH ERROR WalnutDefs.Error[$db, $AlreadyStarted];
StartStatsReporting[];
[key, mailFor, rootFileCreateDate, walnutSegment, systemIsReadOnly] ←
WalnutRoot.Open[rootName: rootFile, readOnly: wantReadOnly];
rootFileName ← rootFile;
BEGIN
exp: ROPE;
BEGIN
schemaInvalid ← WalnutRoot.StartTransaction[ ! WalnutDefs.Error => {
IF code # $MismatchedSegment THEN REJECT;
exp ← explanation;
GOTO mismatched;
}];
EXITS
mismatched => RETURN WITH ERROR WalnutDefs.Error[$db, $SchemaMismatch, exp];
END;
END;
schemaInvalid ← WalnutRoot.StartTransaction[]
;
WalnutLog.OpenLogStreams[];
walnutRootFile ← rootFile;
isReadOnly ← systemIsReadOnly;
BEGIN
exp: ROPE;
BEGIN
WalnutDB.DeclareDB[walnutSegment, schemaInvalid !
WalnutDefs.SchemaMismatch => { exp ← explanation; GOTO mismatch}];
StatsReport[IO.PutFR["\n\n ***** Startup called with rootFile: %g", IO.rope[rootFile]]];
EXITS
mismatch => RETURN WITH ERROR WalnutDefs.Error[$db, $SchemaMismatch, exp];
END;
END;
started ← TRUE;
errorInProgress ← FALSE;
CarefullyApply[InitialCheck, FALSE];
CheckInProgress[];
newMailExists ← WalnutOpsInternal.newMailSomewhere;
WalnutRegistryPrivate.NotifyForEvent[started];
};
Shutdown:
PUBLIC ENTRY PROC = {
Save the Walnut state and shutdown Walnut.
ENABLE {
WalnutDefs.Error => { errorInProgress ← TRUE; REJECT};
UNWIND => NULL;
};
WalnutRoot.UnregisterStatsProc[StatsReport];
WalnutLogExpunge.Shutdown[]; -- clear its variables
WalnutMiscLog.Shutdown[]; -- clear its variables
WalnutLog.ShutdownLog[];
WalnutRoot.Shutdown[]; -- takes care of database
StatsReport["\n *** Shutdown"];
IF statsProgressTS # NIL THEN { statsProgressTS.inhibitDestroy ← FALSE };
IF statsStream # NIL THEN { statsStream.Close[]; statsStream ← NIL };
IF statsProgressStream #
NIL
THEN
{ statsProgressStream.Close[]; statsProgressStream ← NIL };
started ← FALSE;
recentActivity← FALSE;
isShutdown ← FALSE;
errorInProgress ← FALSE;
mailStream ← NIL;
WalnutRegistryPrivate.NotifyForEvent[stopped];
};
Scavenge:
PUBLIC
ENTRY
PROC[rootFile:
ROPE]
RETURNS[newMailExists: BOOL, mailFor: GVBasics.RName, key: ROPE] = {
ENABLE {
WalnutDefs.Error => { errorInProgress ← TRUE; REJECT};
UNWIND => NULL;
};
rootFileCreateDate: GMT;
WalnutLog.ShutdownLog[];
WalnutRoot.Shutdown[];
StartStatsReporting[];
StatsReport["\n *** Scavenge"];
[key, mailFor, rootFileCreateDate, walnutSegment, systemIsReadOnly] ←
WalnutRoot.Open[rootName: rootFile, readOnly: FALSE, newSegmentOk: TRUE];
IF systemIsReadOnly
THEN
ERROR WalnutDefs.Error[$DB, $IsReadOnly, "Can't erase a readonly database"];
rootFileName ← rootFile;
[] ← WalnutRoot.StartTransaction[openDB: FALSE]; -- don't care if schema is invalid
WalnutLog.OpenLogStreams[];
WalnutLog.AcquireWriteLock[];
started ← TRUE;
errorInProgress ← FALSE;
WalnutRoot.EraseDB[];
WalnutDB.InitSchema[walnutSegment];
WalnutDB.SetRootInfo[rootFileCreateDate, key, mailFor];
WalnutDB.SetParseLogInProgress[TRUE];
WalnutDB.SetParseLogPos[0];
WalnutRoot.CommitAndContinue[];
WalnutOpsInternal.newMailSomewhere ← FALSE;
[] ← WalnutOpsInternal.ParseLog[TRUE];
WalnutDB.SetTimeOfLastScavenge[BasicTime.Now[]];
newMailExists ← WalnutOpsInternal.newMailSomewhere;
};
CleanupAfterCopy:
PUBLIC
PROC = {
WalnutLog.ForgetLogStreams[];
WalnutLog.OpenLogStreams[];
};
-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
StartStatsReporting:
PROC = {
statsFile: ROPE ← Rope.Cat["///Users/", UserCredentials.Get[].name, "/WalnutStats.log"];
IF statsStream # NIL THEN statsStream.Close[ ! IO.Error => CONTINUE];
statsStream ← FS.StreamOpen[fileName: statsFile, accessOptions: $create, keep: 4];
IF heavyhandedDebugging
THEN {
name: ROPE = "Walnut Stats";
statsProgressTS ← ViewerOps.FindViewer[name];
statsProgressStream ← ViewerIO.CreateViewerStreams[name, statsProgressTS].out;
IF statsProgressTS =
NIL
THEN
statsProgressTS ← ViewerIO.GetViewerFromStream[statsProgressStream];
statsProgressTS.inhibitDestroy ← TRUE;
};
WalnutRoot.RegisterStatsProc[StatsReport];
};
StatsReport:
PUBLIC
PROC[msg:
ROPE] = {
r: ROPE;
IF statsStream = NIL THEN RETURN;
statsStream.PutRope[r ← IO.PutFR["\n %g @ %g", IO.rope[msg], IO.time[]]];
IF statsProgressStream # NIL THEN statsProgressStream.PutRope[r];
};
RegisterReporter:
PUBLIC
ENTRY
PROC[reportStream:
IO.
STREAM] = {
ENABLE UNWIND => NULL;
FOR rL:
LIST
OF
IO.
STREAM← reporterList, rL.rest
UNTIL rL=
NIL
DO
IF rL.first = reportStream THEN RETURN;
ENDLOOP;
reporterList← CONS[reportStream, reporterList];
};
UnregisterReporter:
PUBLIC
ENTRY
PROC[reportStream:
IO.
STREAM] = {
ENABLE UNWIND => NULL;
prev: LIST OF IO.STREAM;
IF reporterList = NIL OR reportStream = NIL THEN RETURN;
IF reporterList.first = reportStream THEN {reporterList← reporterList.rest; RETURN};
prev← reporterList;
FOR rL:
LIST
OF
IO.
STREAM← reporterList, rL.rest
UNTIL rL=
NIL
DO
IF rL.first = reportStream THEN { prev.rest← rL.rest; RETURN};
prev← rL;
ENDLOOP;
};
CheckReport:
PUBLIC PROC[msg1, msg2, msg3:
ROPE← NIL] = {
IF reporterList = NIL THEN RETURN;
FOR rL:
LIST
OF
IO.
STREAM← reporterList, rL.rest
UNTIL rL=
NIL
DO
IF msg1 # NIL THEN rL.first.PutRope[msg1];
IF msg2 # NIL THEN rL.first.PutRope[msg2];
IF msg3 # NIL THEN rL.first.PutRope[msg3];
ENDLOOP;
};
Utilities
CarefullyApply:
PUBLIC
INTERNAL
PROC[proc:
PROC[], didUpdate:
BOOL] = {
reTryCount: INT ← 2;
schemaInvalid: BOOL ← TRUE;
recentActivity ← TRUE;
IF ~started
THEN
ERROR WalnutDefs.Error[$root, $NotStarted, "Must do WalnutOps.Startup"];
DO
BEGIN
ENABLE
WalnutDefs.Error => {
IF code = $TransactionAbort
THEN {
StatsReport[" **** TransactionAbort"];
IF ( reTryCount ← reTryCount - 1) > 0 THEN GOTO retry;
errorInProgress ← TRUE;
StatsReport[" *** Too many TransactionAbort's during WalnutOps call"];
REJECT
};
errorInProgress ← TRUE;
StatsReport[IO.PutFR[" *** WalnutDefs.Error: %g", IO.atom[code]]];
REJECT
};
proc[];
IF didUpdate THEN WalnutRoot.CommitAndContinue[];
RETURN;
END;
WalnutLog.ForgetLogStreams[];
WalnutRoot.AbortTransaction[];
IF (schemaInvalid ← WalnutRoot.StartTransaction[])
THEN
WalnutDB.InitSchema[walnutSegment];
WalnutLog.OpenLogStreams[];
};
LongRunningApply:
PUBLIC
INTERNAL
PROC[proc:
PROC[inProgress:
BOOL]] = {
reTryCount: INT ← 2;
alreadyCalled: BOOL ← FALSE;
schemaInvalid: BOOL ← TRUE;
recentActivity ← TRUE;
isShutdown ← FALSE;
IF ~started
THEN
ERROR WalnutDefs.Error[$root, $NotStarted, "Must do WalnutOps.Startup"];
DO
BEGIN
ENABLE
WalnutDefs.Error => {
IF code = $TransactionAbort
THEN {
StatsReport[" **** TransactionAbort"];
IF ( reTryCount ← reTryCount - 1) > 0 THEN GOTO retry;
errorInProgress ← TRUE;
StatsReport[" *** Too many TransactionAbort's during LongRunning op"];
REJECT
};
errorInProgress ← TRUE;
StatsReport[IO.PutFR[" *** WalnutDefs.Error: %g", IO.atom[code]]];
REJECT
};
WalnutLog.AcquireWriteLock[];
proc[alreadyCalled ! WalnutDefs.VersionMismatch => { ReleaseWriteLock[]; REJECT} ];
ReleaseWriteLock[]; -- also does commit
RETURN;
END;
WalnutLog.ForgetLogStreams[];
WalnutRoot.AbortTransaction[];
IF (schemaInvalid ← WalnutRoot.StartTransaction[])
THEN
WalnutDB.InitSchema[walnutSegment];
WalnutLog.OpenLogStreams[];
alreadyCalled ← WalnutDB.GetOpInProgressPos[] > 0;
ENDLOOP;
};
Restart:
PUBLIC INTERNAL
PROC = {
-- need to re-open the transaction
schemaInvalid: BOOL = WalnutRoot.StartTransaction[];
IF schemaInvalid THEN WalnutDB.InitSchema[walnutSegment];
WalnutLog.OpenLogStreams[];
isShutdown ← FALSE;
};
CheckForRecentActivity:
ENTRY
PROC = {
ENABLE UNWIND => recentActivity ← FALSE;
DO
WAIT checkActivityCondition;
IF started
THEN {
IF recentActivity =
TRUE
THEN recentActivity ←
FALSE
ELSE
IF ~isShutdown
THEN {
isShutdown ← TRUE;
WalnutLog.ForgetLogStreams[];
WalnutRoot.CloseTransaction[];
isShutdown ← TRUE;
StatsReport[" $$ Close transactions due to inactivity"];
};
};
ENDLOOP;
};
ReleaseWriteLock:
INTERNAL
PROC = {
WalnutRoot.CommitAndContinue[];
WalnutLog.ReleaseWriteLock[];
};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
recentActivity← FALSE;
TRUSTED {Process.Detach[FORK CheckForRecentActivity] };
END.