stable/src/StableRep.mg


 Copyright (C) 1994, Digital Equipment Corporation           
 All rights reserved.                                        
 See the file COPYRIGHT for a full description.              
                                                             
 Created by Carsten Weich                                    
                                                             
 Last modified on Thu Jan 19 13:14:53 PST 1995 by kalsow     
      modified on Thu Jan 12 09:08:46 PST 1995 by chaiken    
      modified on Tue Sep 27 19:16:26 PDT 1994 by weich      

This generic module provides the part of the implementation of stable objects that is independent of {\tt Data.T}.

GENERIC MODULE StableRep(StableData);

IMPORT StableError, LogManager, Pathname, Pickle;
IMPORT OSError, Rd, Wr, RdUtils, Thread, Atom, AtomList;

IMPORT Log;

<*FATAL Thread.Alerted*>
\subsection{Initialization Procedures}

PROCEDURE ReOpenLog (self: StableData.T) =
  BEGIN
    TRY
      IF self.log = NIL THEN
        self.log := self.lm.reOpenLog(self.nm)
      END
    EXCEPT
      OSError.E (err) =>
        StableError.Halt("cannot reopen log file: "
                           & RdUtils.FailureText(err))
    END
  END ReOpenLog;

PROCEDURE FlushLog (self: Public) RAISES {StableError.E} =
  BEGIN
    TRY
      IF self.log # NIL THEN Wr.Flush(self.log) END
    EXCEPT
      Wr.Failure (err) => RAISE StableError.E(err);
    END
  END FlushLog;

PROCEDURE FreeLog (self: Public) RAISES {StableError.E} =
  BEGIN
    TRY
      IF self.log # NIL THEN Wr.Close(self.log) END;
      self.log := NIL;
    EXCEPT
      Wr.Failure (err) => RAISE StableError.E(err);
    END;
  END FreeLog;
\subsection{Procedure Init} Initialize the nm and lm fields for the stable object first (this is needed for the Recover() procedure.

Recover if possible and set recovered to TRUE. Call Checkpoint if Init is called the first time (i.e. if recoverable=FALSE) or if recovered from a crash (i.e. if emptyLog=FALSE). This might be saver than necessary. I assume that the Recover procedure might consume the log file, so I test emptyLog before running Recover().

Set the forceToDisk field after a possible recover. This is to set the field to a fresh value -- do not leave as it was when the stable object was saved the last time.

PROCEDURE Init (    self       : Public;
                    nm        : Pathname.T;
                VAR recovered  : BOOLEAN;
                    forceToDisk               := TRUE;
                    lm         : LogManager.T := NIL   ): StableData.T
  RAISES {StableError.E} =
  BEGIN
    IF self.lm # NIL THEN
      RAISE StableError.E(
          AtomList.List1(
            Atom.FromText(
              "attempted to restabilize without intervening dispose")))
    END;
    TRY
      self.nm := nm;
      IF lm = NIL THEN
        self.lm := LogManager.default
      ELSE
        self.lm := lm
      END;
      VAR emptyLog:= TRUE;
      BEGIN
        IF self.lm.recoverable(nm) THEN
          emptyLog:= self.lm.emptyLog(nm);
          self := Recover(self);
          recovered := TRUE;
        ELSE
          recovered := FALSE;
        END;
        IF NOT recovered OR NOT emptyLog THEN Checkpoint(self); END;
        self.forceToDisk := forceToDisk;
        RETURN NARROW(self, StableData.T)
      END
    EXCEPT
      OSError.E (err) => RAISE StableError.E(err);
    | Wr.Failure (err) => RAISE StableError.E(err);
    END
  END Init;

PROCEDURE Dispose (self: Public) RAISES {StableError.E} =
  BEGIN
    IF (self.lm # NIL) THEN
      TRY
        self.freeLog();
        self.lm.dispose(self.nm);
        self.lm := NIL;
      EXCEPT
        OSError.E (err) => RAISE StableError.E(err);
      END
    END
  END Dispose;
\subsection{Procedure Recover} t already has a nm value, a log manager and a valid readCheckpoint method. Recover will use those to recover from t.nm.

PROCEDURE Recover (t: StableData.T): StableData.T
  RAISES {StableError.E, OSError.E} =

  VAR log, cp: Rd.T;
  BEGIN
    t.lm.recover(t.nm, log, cp); (* might open log *)
    TRY
      t := t.readCheckpoint(cp);
      Rd.Close(cp);
    EXCEPT
    | Rd.Failure (err) =>
        CloseLog(log);  (* close the log if it's opened *)
        RAISE StableError.E(
                AtomList.Cons(
                  Atom.FromText("error reading checkpoint"),
                  err));
    | StableError.E (err) =>
        CloseLog(log);
        RAISE StableError.E(err);
    END;
    IF log # NIL THEN
      t.replayLog(log);
      CloseLog(log);
    END;
    RETURN t;
  END Recover;

PROCEDURE CloseLog (log: Rd.T) =
  BEGIN
    IF (log = NIL) THEN RETURN; END;
    TRY Rd.Close(log); (* get rid of the file handle *)
    EXCEPT Rd.Failure, Thread.Alerted => (* well, it's not the end of the world *)
    END;
  END CloseLog;
\subsection{Procedure Checkpoint} Free the log and use the protocoll described in LogManager.i3 to do a checkpoint.

PROCEDURE Checkpoint (t: StableData.T)
  RAISES {StableError.E} =
  BEGIN
    TRY
      t.freeLog();
      VAR cp := t.lm.beginCheckpoint(t.nm);
      BEGIN
        Log.CrashPoint(102);
        t.writeCheckpoint(cp);
        Wr.Close(cp);
        t.log := t.lm.endCheckpoint(t.nm);
      END;
    EXCEPT
**
      OSError.E (err) => RAISE StableError.E(err);
**
    | Wr.Failure (err) =>
        RAISE StableError.E(
                AtomList.Cons(
                  Atom.FromText("checkpoint error truncating logfile"),
                  err));
    END;
  END Checkpoint;
\subsection{Procedures Read- and WriteCheckpoint} These are the default procedures for reading and writing a checkpoint. They use the pickle package.

PROCEDURE ReadCheckpoint (self: Public; cp: Rd.T):
  StableData.T RAISES {StableError.E} =
  BEGIN
    TRY
      TYPECASE Pickle.Read(cp) OF
        StableData.T (d) => RETURN d;
      ELSE
        RAISE StableError.E(
            AtomList.List1(
              Atom.FromText(
                "Checkpoint in " & self.nm
                  & " does not contain data of the correct type")))
      END;
    EXCEPT
    | Rd.EndOfFile =>
        RAISE StableError.E(
            AtomList.List1(
              Atom.FromText(
                "unexpected EOF encountered reading checkpointfile")))
    | Rd.Failure (err) =>
        RAISE StableError.E(
                AtomList.Cons(
                  Atom.FromText(
                    "error reading checkpointfile"), err))
    | Pickle.Error (msg) =>
        RAISE StableError.E(
                AtomList.List1(
                  Atom.FromText(
                    "pickle-error (" & msg
                      & ") reading checkpointfile")))
    END;
  END ReadCheckpoint;

PROCEDURE WriteCheckpoint (self: Public; wr: Wr.T)
  RAISES {StableError.E} =
  BEGIN
    TRY
      Pickle.Write(wr, self);
    EXCEPT
    | Wr.Failure (err) =>
        RAISE StableError.E(
                AtomList.Cons(
                  Atom.FromText(
                    "error writing checkpointfile"), err))
    | Pickle.Error (msg) =>
        RAISE StableError.E(
                AtomList.List1(
                  Atom.FromText(
                    "pickle-error (" & msg
                      & ") writing checkpointfile")))
    END
  END WriteCheckpoint;

BEGIN
END StableRep.