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: ROPENIL];
Variables
Active: PUBLIC ROPE ← "Active";
Deleted: PUBLIC ROPE ← "Deleted";
reporterList: LIST OF IO.STREAMNIL;
checkActivityCondition: CONDITION ← [timeout: Process.SecondsToTicks[5*60]];
started: BOOL FALSE;
replayInProgress: BOOL FALSE;
interference: BOOL FALSE;
walnutRootFile: ROPE;
recentActivity: BOOL FALSE;
isShutdown: PUBLIC BOOLFALSE;
errorInProgress: PUBLIC BOOLFALSE;
walnutSegment: PUBLIC WalnutDefs.Segment;
systemIsReadOnly: PUBLIC BOOLFALSE;
rootFileName: PUBLIC ROPE;
newMailSomewhere: PUBLIC BOOLFALSE;
statsStream, statsProgressStream: STREAMNIL;
heavyhandedDebugging: BOOLFALSE;
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: BOOLFALSE]
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: BOOLTRUE;
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: BOOLTRUE;
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;
EXITS
retry => NULL;
END;
WalnutLog.ForgetLogStreams[];
WalnutRoot.AbortTransaction[];
IF (schemaInvalid ← WalnutRoot.StartTransaction[]) THEN
  WalnutDB.InitSchema[walnutSegment];
WalnutLog.OpenLogStreams[];
ENDLOOP;
};
LongRunningApply: PUBLIC INTERNAL PROC[proc: PROC[inProgress: BOOL]] = {
reTryCount: INT ← 2;
alreadyCalled: BOOLFALSE;
schemaInvalid: BOOLTRUE;
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;
EXITS
retry => NULL;
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.