Last Edited by: Teitelman, April 22, 1983 5:01 pm
DIRECTORY
CedarSnapshot USING [After, CheckpointProc, RollbackProc, Register],
FileIO USING [Open],
IO USING [CreateProcsStream, CreateRefStreamProcs, CreateDribbleStream, CreateBufferedOutputStream, Close, Flush, GetBlock, GetIndex, GetLength, noWhereStream, PutBlock, PutF, PutRope, rope, SetLength, SetIndex, STREAM, time],
List USING [Nconc1],
MessageWindow USING [Append, Clear],
Process USING [ GetCurrent],
Rope USING [Cat, Concat, Equal, ROPE],
Time USING [Current],
TiogaOps USING [FirstChild, ViewerDoc, GetRope],
UserExec USING [GetExecHandle, ExecHandle],
UserExecExtras USING [],
UserExecPrivate USING [ExecPrivateRecord],
UserProfile USING [Boolean, Token]
;
UserExecLogImpl: CEDAR MONITOR LOCKS data USING data: REF MonitoredStreamRecord
IMPORTS CedarSnapshot, FileIO, IO, List, MessageWindow, Process, Rope, Time, TiogaOps, UserExec, UserProfile
EXPORTS UserExec, UserExecExtras, UserExecPrivate
= BEGIN OPEN IO;
ExecPrivateRecord:
PUBLIC
TYPE = UserExecPrivate.ExecPrivateRecord;
so can access private.id
Session Log
sessionLog: IO.STREAM;
sessionLogFileStream: IO.STREAM;
each call to GetSessionLog returns a new buffered stream connected to the same monitored stream
GetSessionLog:
PUBLIC
PROCEDURE
RETURNS [
IO.
STREAM] = {
IF NOT UserProfile.Boolean["CreateSessionLog", TRUE] THEN RETURN[NIL];
IF sessionLog = NIL THEN CreateSessionLog[];
RETURN[IO.CreateBufferedOutputStream[stream: sessionLog, deliverWhen: NIL, bufferSize: 0]];
}; -- of GetSessionLog
CreateSessionLog:
PROCEDURE [] = {
sessionLogFileStream ← CreateLog["Session.Log"];
sessionLogFileStream.PutF["New Session began at %t\n", time[]];
sessionLog ← CreateMonitoredStream[out: sessionLogFileStream, proc: SessionLogProc];
}; -- of CreateSessionLog
lastLoggedExec: UserExec.ExecHandle;
SessionLogProc:
PROC[self:
IO.
STREAM] =
TRUSTED {
thisExec: UserExec.ExecHandle = UserExec.GetExecHandle[process: Process.GetCurrent[]];
IF self # sessionLogFileStream THEN ERROR;
IF thisExec # lastLoggedExec
THEN {
IF thisExec #
NIL
THEN {
private: REF UserExecPrivate.ExecPrivateRecord = thisExec.privateStuff;
sessionLogFileStream.PutF["\n\n----------------------Work Area %g----------------------\n", rope[private.id]];
}
ELSE sessionLogFileStream.PutRope["\n\n--------------------------------------------\n"]; -- e.g. somebody else, like Jam typescript, got a sessionlog stream.
lastLoggedExec ← thisExec;
};
}; -- of SessionLogPutBlock
Changes Log
changesLog: IO.STREAM;
changesLogFileStream: IO.STREAM;
GetChangesLog:
PUBLIC
PROCEDURE
RETURNS [
IO.
STREAM] = {
IF NOT UserProfile.Boolean["CreateChangesLog", TRUE] THEN RETURN[NIL];
IF changesLog = NIL THEN CreateChangesLog[];
RETURN[IO.CreateBufferedOutputStream[stream: changesLog, deliverWhen: NIL, bufferSize: 0]];
}; -- of GetChangesLog
CreateChangesLog:
PROCEDURE = {
MessageWindow.Append["Creating Changes Log...", TRUE];
changesLogFileStream ← CreateLog["Changes.Log"];
MessageWindow.Clear[];
changesLogFileStream.PutF["Changes log created at %t\n", time[]];
changesLog ← CreateMonitoredStream[out: changesLogFileStream];
};
Monitored Stream
A monitored stream supports only one operation, PutBlock. Furthermore, the call to putblock is monitored. The idea is that one can hook up several streams to the same monitored stream by using IO.CreateBufferedOutputStream. Characters will be sent to the monitored stream only as a result of a Flush operation on the buffered stream.
MonitoredStreamRecord:
TYPE =
MONITORED
RECORD[stream:
IO.
STREAM, proc:
PROC[self:
IO.
STREAM] ←
NIL];
CreateMonitoredStream:
PUBLIC
PROCEDURE [out:
IO.
STREAM, proc:
PROC[self:
IO.
STREAM] ←
NIL]
RETURNS [
IO.
STREAM] = {
RETURN[
IO.CreateProcsStream[
streamProcs: IO.CreateRefStreamProcs[putBlock: MonitoredPutBlock, name: "Monitored"],
streamData: NEW[MonitoredStreamRecord ← [stream: out, proc: proc]]
]];
}; -- of CreateMonitoredStream
MonitoredPutBlock:
PROC[self:
IO.
STREAM, block:
REF
READONLY
TEXT, startIndex:
NAT ← 0, stopIndexPlusOne:
NAT ←
LAST[
NAT]] = {
data: REF MonitoredStreamRecord = NARROW[self.streamData];
DoIt:
ENTRY
PROC [data:
REF MonitoredStreamRecord] = {
ENABLE UNWIND => NULL;
IF data.proc # NIL THEN data.proc[data.stream];
IO.PutBlock[data.stream, block, startIndex, stopIndexPlusOne];
IO.Flush[data.stream];
};
DoIt[data];
}; -- of MonitoredPutBlock
Creating log file for execs
CreateExecLogFile:
PUBLIC PROC [exec: UserExec.ExecHandle]
RETURNS[stream:
IO.STREAM] =
TRUSTED {
private: REF ExecPrivateRecord = exec.privateStuff;
name: Rope.ROPE = Rope.Cat["WorkArea", private.id, ".log"];
sessionLog: IO.STREAM = GetSessionLog[];
IF NOT UserProfile.Boolean["CreateWorkAreaLogs", FALSE] THEN RETURN[sessionLog];
IF NOT Rope.Equal[private.id, "A"] THEN MessageWindow.Append[Rope.Cat["Creating log for Work Area ", private.id, "..."], TRUE];
stream ← CreateLog[name, exec];
IF sessionLog # NIL THEN stream ← IO.CreateDribbleStream[stream, sessionLog];
stream.PutF["Work Area %g created at %t\n", rope[private.id], time[]];
IF NOT Rope.Equal[private.id, "A"] THEN MessageWindow.Clear[];
};
CloseExecLogFile:
PUBLIC PROC [exec: UserExec.ExecHandle] = {
FOR l:
LIST
OF
REF LogFileRecord ← activeLogs, l.rest
UNTIL l =
NIL
DO
IF l.first.exec = exec
THEN {
IF l.first.log # NIL THEN {IO.Close[l.first.log]; l.first.log ← NIL}; -- so won't try to reopen following a rollback.
EXIT;
};
ENDLOOP;
};
CheckPoint and rollback
activeLogs: PUBLIC LIST OF REF LogFileRecord ← NIL;
LogFileRecord: TYPE = RECORD[log: IO.STREAM, name: Rope.ROPE, length: INT ← 0, exec: UserExec.ExecHandle];
CreateLog:
PROC [name: Rope.
ROPE, exec: UserExec.ExecHandle ←
NIL, dontRegister:
BOOL ←
FALSE]
RETURNS[stream:
IO.
STREAM] = {
r: Rope.ROPE = UserProfile.Token["WhenLogFileExists", "Rename"];
IF Rope.Equal[r, "Append",
FALSE]
THEN {
stream ← FileIO.Open[fileName: name, accessOptions: append];
stream.PutRope["\n\n#######################################"];
}
ELSE
IF Rope.Equal[r, "OverWrite",
FALSE]
THEN {
stream ← FileIO.Open[fileName: name, accessOptions: write];
IO.SetLength[stream, 0];
}
ELSE {
stream ← FileIO.Open[fileName: name, accessOptions: write];
SaveOldLog[name, stream];
IO.SetLength[stream, 0];
};
IF NOT dontRegister THEN RegisterLog[log: stream, name: name, exec: exec];
};
SaveOldLog:
PROC [name:
Rope.ROPE, stream:
IO.
STREAM] = {
len: INT = IO.GetLength[stream];
dollarName: Rope.ROPE = Rope.Concat[name, "$"];
dollarStream: IO.STREAM = FileIO.Open[fileName: dollarName, accessOptions: overwrite, createLength: len];
bufferSize: INT = 512;
buffer: REF TEXT = NEW[TEXT[bufferSize]];
bytes: INT;
IO.SetIndex[stream, 0];
WHILE (bytes ← stream.GetBlock[buffer]) > 0
DO
dollarStream.PutBlock[buffer];
ENDLOOP;
dollarStream.Close[];
};
when monitored stream gets moved elsewhere, make RegisterLog monitored
RegisterLog:
PROC [log:
IO.
STREAM, name: Rope.
ROPE ←
NIL, exec: UserExec.ExecHandle ←
NIL] =
TRUSTED {
activeLogs ←
LOOPHOLE[List.Nconc1[
LOOPHOLE[activeLogs],
NEW[LogFileRecord ← [log: log, name: name, exec: exec]]
]];
};
CheckpointProc: CedarSnapshot.CheckpointProc = {AtCheckpoint[]};
RollbackProc: PROC [after: CedarSnapshot.After] -- CedarSnapshot.RollbackProc-- = {AtRollback[after]};
AtCheckpoint:
PROC = {
FOR l:
LIST
OF
REF LogFileRecord ← activeLogs, l.rest
UNTIL l =
NIL
DO
IF l.first.exec #
NIL
THEN {
private: REF ExecPrivateRecord = l.first.exec.privateStuff;
IF private.execState = destroyed THEN l.first.log ← NIL;
};
IF l.first.log #
NIL
THEN {
IO.Flush[l.first.log];
l.first.length ← IO.GetIndex[l.first.log];
IO.Close[l.first.log];
l.first.log^ ← IO.noWhereStream^;
};
ENDLOOP;
};
AtRollback:
PROC [after: CedarSnapshot.After] = {
FOR l:
LIST
OF
REF LogFileRecord ← activeLogs, l.rest
UNTIL l =
NIL
DO
IF l.first.log #
NIL
THEN {
name: Rope.ROPE = l.first.name;
log: IO.STREAM;
log ← CreateLog[name: name, exec: l.first.exec, dontRegister: TRUE];
IF Rope.Equal[UserProfile.Token["WhenLogFileExists", "Rename"], "Append", FALSE] THEN NULL -- this sessions log will be appended.
ELSE IF IO.GetLength[log] >= l.first.length THEN IO.SetLength[log, l.first.length] -- Truncate log back to the point of the checkpoint. If length < l.first.length, file was somehow clobbered. in case of exec, can recover from typescript
ELSE IF l.first.exec #
NIL
THEN {
private: REF ExecPrivateRecord = l.first.exec.privateStuff;
name: Rope.ROPE = Rope.Cat["WorkArea", private.id, ".log"];
log ← FileIO.Open[fileName: name, accessOptions: overwrite];
note that it is not necessary to do the dribble stream since we are going to smash this log into any existing streams that go to this file, and we have done the same for sessionLog. Therefore any outstanding dribble streams that point to both are automatically now valid
log.PutRope[TiogaOps.GetRope[TiogaOps.FirstChild[TiogaOps.ViewerDoc[l.first.exec.viewer]]]]; -- assumes only one node in typescript
}
ELSE
IF l.first.log = sessionLogFileStream
THEN {
IO.SetLength[log, 0];
sessionLogFileStream^ ← log^;
sessionLogFileStream.PutRope["****Previous session log was lost!\n"];
}
ELSE
IF l.first.log = changesLogFileStream
THEN {
IO.SetLength[log, 0];
changesLogFileStream^ ← log^;
changesLogFileStream.PutRope["****Previous changes log was lost!\n"];
}
ELSE ERROR; -- shouldn't happen
l.first.log^ ← log^; -- already done for last two cases, but safer to have it here anyway
};
ENDLOOP;
IF sessionLogFileStream #
NIL
THEN
sessionLogFileStream.PutF["\n%g at %t\n",
rope[IF after = checkpoint THEN "Returned from Checkpoint" ELSE "Rolled back"],
time[]
];
};
TRUSTED {CedarSnapshot.Register[c: CheckpointProc, r: RollbackProc]};
END. -- of UserExecLogImpl