-- File: CedarSnapshotCork.mesa
-- last edited by Levin:  November 9, 1982 4:16 pm

DIRECTORY
  CedarSnapshotPrivate USING [],
  Environment USING [Base],
  Process USING [
    DisableTimeout, GetPriority, InitializeCondition, InitializeMonitor, Priority,
    SetPriority --, Yield--],
  ProcessInternal USING [DisableInterrupts],
  ProcessOperations USING [EnableAndRequeue, ReadPSB],
  ProcessPriorities USING [priorityPageFaultLow],
  PSB USING [PDA],
  ResidentHeap USING [first64K, FreeNode, MakeNode],
  SpecialSpace USING [MakeProcedureResident, MakeProcedureSwappable],
  Zone USING [Status];

CedarSnapshotCork: MONITOR LOCKS corkHandle.LOCK USING corkHandle: CorkHandle
  IMPORTS Process, ProcessInternal, ProcessOperations, ResidentHeap, SpecialSpace
  EXPORTS CedarSnapshotPrivate =

BEGIN

-- We would like to keep the variables associated with the corking in the global frame and
-- protect them in the obvious way with a module monitor.  However, since 'state' and
-- 'stateChange' must be resident (since the whole purpose of the Cork is to be runnable
-- always and therefore never fault), the global frame would have to be pinned.  We could
-- arrange to package the CedarSnapshotImpl configuration to permit just this global frame
-- to be pinned, but that's a considerable aggravation.  (If we forge ahead fearlessly and
-- call SpecialSpace.MakeGlobalFrameResident[CedarSnapshotCork], we will pin the entire
-- configuration in which CedarSnapshotImpl is bound, because of the way MakeBoot and the
-- space machinery work.)  So, we instead allocate a small hunk of storage from ResidentHeap.
-- If this configuration is ever packaged for other reasons, the code should be reworked to
-- use variables in the global frame and a simple module monitor.

CorkHandle: TYPE = LONG POINTER TO CorkObject;
CorkRP: TYPE = Environment.Base RELATIVE POINTER TO CorkObject;

CorkObject: PUBLIC TYPE = MONITORED RECORD [
  oldPriority: Process.Priority,
  state: {initial, corked, uncork} ← initial,
  stateChange: CONDITION ← [timeout: 0],
  cork: PROCESS ← NULL,
  self: CorkRP
  ];

CorkRestOfWorld: PUBLIC PROC RETURNS [corkHandle: CorkHandle] = {
  EnsureCorked: ENTRY PROC [corkHandle: CorkHandle] = INLINE {
    corkHandle.cork ← FORK Cork[corkHandle];
    UNTIL corkHandle.state = corked DO WAIT corkHandle.stateChange; ENDLOOP;
    };
  corkRP: CorkRP;
  status: Zone.Status;
  [corkRP, status] ← ResidentHeap.MakeNode[SIZE[CorkObject]];
  IF status ~= okay THEN ERROR;
  corkHandle ← @ResidentHeap.first64K[corkRP];
  -- compiler won't permit: corkHandle↑ ← [oldPriority: Process.GetPriority[], self: corkRP];
  Process.InitializeMonitor[@corkHandle.LOCK];
  corkHandle.oldPriority ← Process.GetPriority[];
  corkHandle.state ← initial;
  Process.InitializeCondition[@corkHandle.stateChange, 0];
  Process.DisableTimeout[@corkHandle.stateChange];
  corkHandle.self ← corkRP;
  Process.SetPriority[ProcessPriorities.priorityPageFaultLow];
  EnsureCorked[corkHandle];
  -- The cork is now at the head of the ready list for priorityPageFaultLow,
  -- and since it never faults, it yields only to its own priority level,
  -- so nothing below priorityPageFaultLow will run.  This is assumed to
  -- include the Ethernet driver(s), Cedar runtime processes, and anything
  -- else that uses the Space machinery.  Note: we are careful not to use
  -- Heap.systemZone when the Cork is operational, since there is a small
  -- chance that one of the processes preempted by the Cork is holding the
  -- monitor for this zone.  (Of course, some preempted processes may be
  -- holding other Pilot monitors, e.g., those in the FileMgr, but we
  -- can't do anything about that ... except hope for the best.)
  };

UncorkRestOfWorld: PUBLIC PROC [corkHandle: CorkHandle] = {
  IF corkHandle = NIL THEN RETURN;
  corkHandle.state ← uncork;
  JOIN corkHandle.cork;
  Process.SetPriority[corkHandle.oldPriority];
  IF ResidentHeap.FreeNode[corkHandle.self] ~= okay THEN ERROR;
  };

Cork: PROC [corkHandle: CorkHandle] = {
  AnnounceCorked: ENTRY PROC [corkHandle: CorkHandle] = INLINE {
    corkHandle.state ← corked; NOTIFY corkHandle.stateChange;
    };
  Yield: PROC = {
    -- stolen from Processes to ensure residency
    ProcessInternal.DisableInterrupts[];
    ProcessOperations.EnableAndRequeue[@PSB.PDA.ready, @PSB.PDA.ready, ProcessOperations.ReadPSB[]];
    };
  -- implicit in FORK: Process.SetPriority[ProcessPriorities.priorityPageFaultLow];
  SpecialSpace.MakeProcedureResident[Cork];
  AnnounceCorked[corkHandle];
  UNTIL corkHandle.state = uncork DO --Process.--Yield[]; ENDLOOP;
  SpecialSpace.MakeProcedureSwappable[Cork];
  };

END.