-- File: AltoKD.mesa
-- Last edited by Levin:  30-Apr-81 13:19:28

DIRECTORY
  AltoFile USING [FreePageFileID],
  AltoFileDefs USING [KD, SN],
  AltoFilePrivate USING [DoDiskRequest, loggingEnabled],
  DiskIODefs USING [
    CompletionStatus, DiskError, DiskRequest, eofvDA, FID, InitiateDiskIO,
    ResetDiskShape, vDA, VerboseCompletionProcedure, XferSpec],
  DiskKDDefs USING [
    AssignDiskPage, CountFreeDiskPages, DiskFull, NewSN, ReleaseDiskPage, UpdateDiskKD],
  LogDefs USING [DisplayNumber],
  ProcessDefs USING [Seconds, SecondsToTicks],
  VMDefs USING [CloseFile, FileHandle, OpenFile, Page, ReadPage, Release],
  VMStorage USING [AllocatePage, FreePage];

AltoKD: MONITOR
  IMPORTS
    AltoFilePrivate, DiskIODefs, DiskKDDefs, LogDefs, ProcessDefs, VMDefs, VMStorage
  EXPORTS AltoFile, AltoFilePrivate
  SHARES DiskIODefs =

  BEGIN OPEN DiskIODefs;

  -- A note of caution:
  -- It is tempting to eliminate the dependency of this code on DiskKDDefs, since most
  -- of the work is being done here anyway.  However, it is important that only one
  -- copy of DiskDescriptor be in use, and Mesa got there first.  In principle, the
  -- bit table contained therein is only a hint, but it practice, everyone tends to
  -- believe the free page count, which would get screwed up if two autonomous modules
  -- were maintaining it.  We defer to Mesa here, with one exception.  Mesa should
  -- update the disk descriptor header (which contains the serial number generator)
  -- whenever a file is created, but it doesn't.  We do so in our version of NewSN.


  -- Types and Related Constants --

  WakeUpReason: TYPE = {timeOut, pageAllocated, goingAway};

  -- Global Variables --

  freeCount: CARDINAL;

  wakeUpReason: WakeUpReason;

  freeSpaceWatchInterval: ProcessDefs.Seconds = 60;

  timeToCheckAgain: CONDITION;


  -- Procedures and Signals Exported to AltoFile --

  AllocateDiskPage: PUBLIC PROCEDURE [vda: vDA] RETURNS [vDA] =
    BEGIN
    request: DiskRequest;
    xferSpec: ARRAY [0..1) OF XferSpec;
    readStatus: CompletionStatus;
    labelRead: CONDITION ← [timeout: 0];
    buffer: POINTER = VMStorage.AllocatePage[];
    AllocationState: TYPE = {unassigned, assigned, diskfull, diskerror};
    state: AllocationState ← unassigned;

    -- In principle, the following procedures should have a separate monitor lock all
    -- to themselves.  However, that is sufficiently inconvenient that we "borrow" the
    -- KD lock.  It would also be nice to be able to use DoDiskRequest, but we need
    -- to look at the label during the completion procedure.

    ReadLabel: ENTRY PROCEDURE = INLINE
      -- initiates the disk read and waits for completion.
      BEGIN
      DO
	readStatus ← noStatus;
	InitiateDiskIO[@request];
	WHILE readStatus = noStatus DO WAIT labelRead; ENDLOOP;
	IF readStatus ~= neverStarted THEN EXIT;
	ENDLOOP;
      END;

    CheckFreePage: ENTRY VerboseCompletionProcedure =
      -- invoked at completion of read to test that page is actually free.
      BEGIN
      readStatus ← status;
      IF readStatus = ok AND label.fileID = AltoFile.FreePageFileID THEN
	state ← assigned;
      NOTIFY labelRead;
      END;

    WHILE state = unassigned DO
      vda ← AssignPage[vda ! DiskKDDefs.DiskFull => {state ← diskfull; EXIT}];
      xferSpec[0] ← [buffer, vda, 0];
      request ← DiskRequest[
	firstPage:, fileID:, firstPagevDA:, pagesToSkip: 0, nonXferID:,
	xfers: DESCRIPTOR[@xferSpec, 1], proc: [verbose[CheckFreePage]],
	noRestore: FALSE, command: ReadLD[]];
      ReadLabel[];
      IF readStatus ~= ok THEN state ← diskerror;
      ENDLOOP;
    VMStorage.FreePage[buffer];
    SELECT state FROM
      assigned => AlertWaiters[pageAllocated];
      diskfull => ERROR DiskFull;
      diskerror => ERROR DiskError[readStatus];
      ENDCASE;
    RETURN[vda]
    END;

  DiskFull: PUBLIC ERROR = CODE;

  FreeDiskPage: PUBLIC PROCEDURE [vda: vDA] =
    BEGIN
    buffer: POINTER = VMStorage.AllocatePage[];
    request: DiskRequest;
    xferSpec: ARRAY [0..1) OF XferSpec ← [[buffer, vda, 0]];
    status: CompletionStatus;

    request ← DiskRequest[
      firstPage: 0, fileID: AltoFile.FreePageFileID, firstPagevDA:,
      pagesToSkip: 0, nonXferID:, xfers: DESCRIPTOR[@xferSpec, 1],
      proc: , noRestore: FALSE,
      command: WriteLD[next: eofvDA, prev: eofvDA, lastByteCount: 0]];
    [status, , ] ← AltoFilePrivate.DoDiskRequest[req: @request, signalError: FALSE];
    VMStorage.FreePage[buffer];
    IF status = ok THEN DeassignPage[vda]
    ELSE ERROR DiskError[status];
    END;

  WaitForSpaceCrunch: PUBLIC ENTRY PROCEDURE [pages: CARDINAL] RETURNS [BOOLEAN] =
    BEGIN
    DO
      IF freeCount <= pages THEN RETURN[TRUE];
      wakeUpReason ← timeOut;
      WAIT timeToCheckAgain;
      SELECT wakeUpReason FROM
	timeOut => freeCount ← DiskKDDefs.CountFreeDiskPages[];
	pageAllocated => NULL;
	goingAway => RETURN[FALSE];
	ENDCASE;
      ENDLOOP;
    END;

  NewSN: PUBLIC ENTRY PROCEDURE RETURNS [sn: AltoFileDefs.SN] =
    BEGIN
    sn ← DiskKDDefs.NewSN[];
    DiskKDDefs.UpdateDiskKD[];  -- DiskKDDefs.NewSN should have done this.
    END;


  -- Procedures and Signals Exported to AltoFilePrivate --

  InitializeKD: PUBLIC PROCEDURE =
    -- initializes disk descriptor.
    BEGIN
    freeCount ← DiskKDDefs.CountFreeDiskPages[];
    timeToCheckAgain.timeout ← ProcessDefs.SecondsToTicks[freeSpaceWatchInterval];
    IF AltoFilePrivate.loggingEnabled THEN
      LogDefs.DisplayNumber["Free Disk Pages"L, [short[@freeCount]]];
    BEGIN OPEN VMDefs;
    kdFile: FileHandle ← OpenFile[name: "DiskDescriptor"L];
    page: Page = ReadPage[addr: [kdFile, 0]];
    kd: POINTER TO AltoFileDefs.KD = LOOPHOLE[page];
    ResetDiskShape[kd.disk];
    Release[page];
    CloseFile[kdFile];
    END;
    END;

  FinalizeKD: PUBLIC PROCEDURE =
    {AlertWaiters[goingAway]};


  -- Internal Procedures --

  AssignPage: ENTRY PROCEDURE [nearvDA: vDA] RETURNS [vDA] =
    BEGIN
    RETURN[DiskKDDefs.AssignDiskPage[nearvDA ! UNWIND => NULL]]
    END;

  DeassignPage: ENTRY PROCEDURE [vda: vDA] =
    BEGIN
    DiskKDDefs.ReleaseDiskPage[vda];
    freeCount ← freeCount + 1;
    END;

  AlertWaiters: ENTRY PROCEDURE [why: WakeUpReason] =
    -- wakes up any processes blocked on TimeToCheckAgain, passing the specified reason.
    BEGIN
    IF (wakeUpReason ← why) = pageAllocated THEN freeCount ← freeCount - 1;
    BROADCAST timeToCheckAgain;
    END;

  
  END.