The LogManager
provides an object which methods take a Pathname.T
and produce readers and writers for log and checkpoint files. It is
used by stable objects.
The default log mananger uses the nm
paramter as the name of a
directory in the filesystem. In this directory it will put the log and
checkpointfiles.
MODULE\subsection{produce filenames}; IMPORT Pathname, Thread, Wr, FileWr, Rd, FileRd, FS, OSError, Atom, AtomList, FSError; IMPORT Log; FROM Log IMPORT CrashPoint; REVEAL T = Public BRANDED OBJECT END; REVEAL Default = DefaultPublic BRANDED OBJECT METHODS (* produce the complete filenames: *) cpfn (nm: Pathname.T): Pathname.T RAISES {OSError.E} := Cpfn; newcpfn (nm: Pathname.T): Pathname.T RAISES {OSError.E} := NewCpfn; oldcpfn (nm: Pathname.T): Pathname.T RAISES {OSError.E} := OldCpfn; dummycp (nm: Pathname.T): Pathname.T RAISES {OSError.E} := DummyCp; logfn (nm: Pathname.T): Pathname.T RAISES {OSError.E} := Logfn; OVERRIDES init := Init; reOpenLog := ReOpenLog; beginCheckpoint := BeginCheckpoint; endCheckpoint := EndCheckpoint; recover := Recover; recoverable := Recoverable; emptyLog := EmptyLog; dispose := Dispose; END; LogManager
PROCEDURE\subsection{Public Methods}Logfn (<*UNUSED*> lm: Default; nm: Pathname.T): Pathname.T = (* log filename *) BEGIN RETURN Pathname.Join(nm, "log", NIL) END Logfn; PROCEDURECpfn (<*UNUSED*> lm: Default; nm: Pathname.T): Pathname.T = (* checkpoint filename *) BEGIN RETURN Pathname.Join(nm, "checkpoint", NIL) END Cpfn; PROCEDUREOldCpfn (lm: Default; nm: Pathname.T): Pathname.T RAISES {OSError.E} = (* old (backup) checkpoint filename *) BEGIN RETURN lm.cpfn(nm) & ".old" END OldCpfn; PROCEDURENewCpfn (lm: Default; nm: Pathname.T): Pathname.T RAISES {OSError.E} = (* new checkpoint filename *) BEGIN RETURN lm.cpfn(nm) & ".new" END NewCpfn; PROCEDUREDummyCp (lm: Default; nm: Pathname.T): Pathname.T RAISES {OSError.E} = (* dummy checkpoint filename for bootstrapping *) BEGIN RETURN lm.cpfn(nm) & ".dummy" END DummyCp;
PROCEDUREInit (lm: Default): Default =
No dynamic initialization needed:
BEGIN RETURN lm; END Init; PROCEDURE\subsection{Utilities}ReOpenLog (lm: Default; nm: Pathname.T): Wr.T RAISES {OSError.E} = BEGIN RETURN FileWr.OpenAppend(lm.logfn(nm)) END ReOpenLog; PROCEDUREBeginCheckpoint (lm: Default; nm: Pathname.T): Wr.T RAISES {OSError.E} = BEGIN Log.PutText("BeginCheckpoint: "); (**) CrashPoint(101); TestDir(nm); IF TestFile(lm.cpfn(nm)) THEN FS.Rename(lm.cpfn(nm), lm.oldcpfn(nm)); Log.PutText("renamed checkpointfile to " & lm.oldcpfn(nm)); Log.Nl(); ELSE TRY <*FATAL Thread.Alerted*> BEGIN Wr.Close(FileWr.Open(lm.dummycp(nm))); END EXCEPT Wr.Failure => RAISE OSError.E(AtomList.List1( Atom.FromText("error creating dummy checkpoint " & lm.dummycp(nm)))); END; Log.PutText("created dummy checkpoint " & lm.dummycp(nm)); Log.Nl(); END; Log.PutText("new checkpointfile " & lm.newcpfn(nm)); Log.Nl(); RETURN FileWr.Open(lm.newcpfn(nm)); END BeginCheckpoint; PROCEDUREEndCheckpoint (lm: Default; nm: Pathname.T): Wr.T RAISES {OSError.E} = BEGIN Log.PutText("EndCheckpoint: "); Log.PutText("renaming checkpoint " & lm.newcpfn(nm)); Log.Nl(); (**) CrashPoint(103); FS.Rename(lm.newcpfn(nm), lm.cpfn(nm)); (**) CrashPoint(104); VAR newLog := FileWr.Open(lm.logfn(nm)); BEGIN IF TestFile(lm.dummycp(nm)) THEN Log.PutText("deleting dummy checkpoint" & lm.dummycp(nm)); Log.Nl(); (**) CrashPoint(105); FS.DeleteFile(lm.dummycp(nm)); ELSE Log.PutText("deleting old checkpoint" & lm.oldcpfn(nm)); Log.Nl(); (**) CrashPoint(105); FS.DeleteFile(lm.oldcpfn(nm)); END; (**) CrashPoint(106); RETURN newLog; END; END EndCheckpoint; PROCEDURERecover (lm: Default; nm: Pathname.T; VAR log, checkp: Rd.T) RAISES {OSError.E} = (* Check possible leftovers from checkpoint-crashes: This procedure tolerates absence of logfiles since itself deletes logfiles when invalid. Usually there are always logfiles (with possible size zero) since there is no way of deleting logfiles using the default logmanager. *) BEGIN (* (1) renaming to final name is the commit for writing a new checkpoint. If there is a checkpoint with temporary name then it is invalid and the log file belongs to the old version. *) IF TestFile(lm.newcpfn(nm)) OR NOT TestFile(lm.cpfn(nm)) THEN Log.PutText("found valid backup"); Log.Nl(); FS.Rename(lm.oldcpfn(nm), lm.cpfn(nm)); (* must exist or fatal error *) IF TestFile(lm.newcpfn(nm)) THEN Log.PutText("removing new checkpoint"); Log.Nl(); FS.DeleteFile(lm.newcpfn(nm)) END; IF NOT TestFile(lm.logfn(nm)) THEN log := NIL ELSE log := FileRd.Open(lm.logfn(nm)); END; (* (2) there is a checkpoint with final name. Thus it is valid. If there is a backup checkpoint than the log is invalid. *) ELSIF TestFile(lm.cpfn(nm)) AND TestFile(lm.oldcpfn(nm)) THEN Log.PutText("found valid new checkpoint"); Log.Nl(); Log.PutText("deleting backup checkpoint " & lm.oldcpfn(nm)); FS.DeleteFile(lm.oldcpfn(nm)); (* must exist of fatal error *) IF TestFile(lm.logfn(nm)) THEN (* may or may not exist depending on the crash *) Log.PutText("removing log"); Log.Nl(); FS.DeleteFile(lm.logfn(nm)); END; log := NIL; (* (3) Finally if there is no temporary checkpoint there than the checkpoint and the log is valid. *) ELSE IF NOT TestFile(lm.logfn(nm)) THEN log := NIL ELSE log := FileRd.Open(lm.logfn(nm)); END; END; checkp := FileRd.Open(lm.cpfn(nm)); END Recover; PROCEDURERecoverable (lm: Default; nm: Pathname.T): BOOLEAN = BEGIN TRY (* is there such a directory? *) IF FS.Status(nm).type # FS.DirectoryFileType THEN RETURN FALSE END; (* Do not care about to logfile but there must be a version of the checkpointfile: *) RETURN TestFile(lm.cpfn(nm)) OR TestFile(lm.oldcpfn(nm)); EXCEPT OSError.E => RETURN FALSE (* nothing found *) END; END Recoverable; PROCEDUREEmptyLog (lm: Default; nm: Pathname.T): BOOLEAN RAISES {OSError.E} = VAR log: TEXT; BEGIN IF NOT lm.recoverable(nm) THEN RAISE OSError.E( AtomList.List1( Atom.FromText( "no checkpointfile for log in " & nm))); END; log := lm.logfn(nm); RETURN (NOT TestFile(log)) OR (FS.Status(log).size = 0L); END EmptyLog; PROCEDUREDispose (lm: Default; nm: Pathname.T) RAISES {OSError.E} = BEGIN IF TestFile(nm) THEN Log.PutText("Erasing directory " & nm); Log.Nl(); (**) CrashPoint(501); IF TestFile(lm.cpfn(nm)) THEN FS.DeleteFile(lm.cpfn(nm)) END; IF TestFile(lm.oldcpfn(nm)) THEN FS.DeleteFile(lm.oldcpfn(nm)) END; IF TestFile(lm.newcpfn(nm)) THEN FS.DeleteFile(lm.newcpfn(nm)) END; (**) CrashPoint(502); IF TestFile(lm.logfn(nm)) THEN FS.DeleteFile(lm.logfn(nm)) END; (**) CrashPoint(503); IF TestFile(lm.dummycp(nm)) THEN FS.DeleteFile(lm.dummycp(nm)) END; FS.DeleteDirectory(nm); END END Dispose;
PROCEDURETestFile (fname: Pathname.T): BOOLEAN RAISES {OSError.E} = (* Test for existence of a file named 'fname'. Returns TRUE iff it exists and is readable. Exceptions other than "file not found" are reraised. *) BEGIN TRY EVAL FS.Status(fname); RETURN TRUE; (* ok, passed the test *) EXCEPT OSError.E (err) => IF FSError.FileNotFound (err) THEN RETURN FALSE ELSE RAISE OSError.E(err) END END END TestFile; PROCEDURETestDir (dir: Pathname.T) RAISES {OSError.E} = (* This procedure assures that "dir" is a valid directory. If it does not exist it creates it. *) BEGIN TRY IF FS.Status(dir).type # FS.DirectoryFileType THEN RAISE OSError.E(AtomList.List1( Atom.FromText( "name of stable obj is not a directory: " & dir))); END EXCEPT OSError.E => Log.PutText("creating dir " & dir); Log.Nl(); FS.CreateDirectory(dir) END END TestDir; BEGIN default:= NEW(Default).init(); END LogManager.