-- Transport Mechanism Filestore - heap file management --

-- [Idun]<WServices>MS>StableStorageImpl.mesa

-- Andrew Birrell	10-Jun-81 10:18:09
-- Randy Gobbel		29-Jun-82 16:38:42
-- Mark Johnson		19-May-81 13:30:37

DIRECTORY
  Bitmap USING [Clear, MapIndex, Set, Test],
  Inline USING [LowHalf],
  ObjectDir,
  ObjectDirInternal,
  Policy USING [AmountOfFreeHeap, GapExists],
  Process USING [DisableTimeout],
  StableStorage,
  VMDefs USING [FileHandle, FullAddress, MarkStartWait, Page, PageAddress,
    PageIndex, PageNumber, pageSize, RemapPage];

StableStorageImpl: MONITOR
  IMPORTS Bitmap, Inline, Policy, Process, VMDefs EXPORTS ObjectDir--.Client--,
    StableStorage =
BEGIN OPEN ObjectDirInternal, StableStorage;

Client: TYPE = ObjectDirInternal.Client;
ClientObject: PUBLIC TYPE = ObjectDirInternal.ClientObject;

notClaimed: CONDITION;
segmentBecomesFree: CONDITION;

 -- Variables for use in debugger
 debugClient: Client;
 debugSeg: SegmentIndex;
 debugPage: VMDefs.PageNumber;

Address: PROCEDURE [of: SegmentIndex, client: Client] RETURNS [VMDefs.FullAddress] =
  BEGIN OPEN client;
  RETURN[[
    page: [
      file: handle,
      page: (of - ((of+VMDefs.pageSize)/VMDefs.pageSize)*headerSize)*segmentSize],
    word: FIRST[VMDefs.PageIndex]]];
  END;

Segment: PROCEDURE [page: VMDefs.PageNumber]
  RETURNS [seg: SegmentIndex] =
  {segmentOrdinal: CARDINAL = Inline.LowHalf[page / segmentSize];
  seg ←
    segmentOrdinal +
    ((segmentOrdinal+segmentsPerPage)/segmentsPerPage)*headerSize};

FindSegment: INTERNAL PROCEDURE [near: SegmentIndex, client: Client]
  RETURNS [new: SegmentIndex] =
  BEGIN OPEN client;
  -- find 'nearest' free segment;
  high: Bitmap.MapIndex ← MIN[
    near+(IF near+1 MOD VMDefs.pageSize = 0 THEN headerSize ELSE 1),
    segmentCeiling];
  low: Bitmap.MapIndex ←
    (IF near > headerSize+1 THEN
      near - (IF (near-headerSize) MOD VMDefs.pageSize = 0 THEN headerSize+1 ELSE 1)
    ELSE headerSize+1);
  DO
    IF ~Bitmap.Test[freeMap, high] THEN {new ← high; EXIT};
    IF ~Bitmap.Test[freeMap, low] THEN {new ← low; EXIT};
    high ← MIN[
      high + (IF high+1 MOD VMDefs.pageSize = 0 THEN headerSize ELSE 1),
      segmentCeiling];
    low ←
      (IF low > headerSize+1 THEN
        low -
	  (IF (low-headerSize) MOD VMDefs.pageSize = 0 THEN headerSize+1 ELSE 1)
      ELSE headerSize+1);
    ENDLOOP;
  Bitmap.Set[freeMap, new];
  freeCount ← freeCount - 1;
  Policy.AmountOfFreeHeap[(freeCount*100 + segmentCount/2)/segmentCount];
  END;

RecordAllocation: INTERNAL PROCEDURE [client: Client, seg: SegmentIndex] =
  BEGIN OPEN client;
  page: CARDINAL = seg/VMDefs.pageSize;
  chainPage: VMDefs.PageNumber =
    page*2 + (IF chain.header[page].serialNumber MOD 2 = 0 THEN 0 ELSE 1);
  chain.header[page].serialNumber ← chain.header[page].serialNumber + 1;
  VMDefs.RemapPage[
    LOOPHOLE[@chain.header[page], VMDefs.Page],
    [chainHandle, chainPage]];
  VMDefs.MarkStartWait[LOOPHOLE[@chain.header[page], VMDefs.Page]];
  END;

FirstSegment: PUBLIC ENTRY PROCEDURE [client: Client]
  RETURNS [VMDefs.FullAddress] =
  BEGIN RETURN[Address[client.chain.header[0].chainHead, client]] END;

InsertFirstSegment: PUBLIC ENTRY PROCEDURE [client: Client]
  RETURNS [VMDefs.FullAddress] =
  BEGIN OPEN client;
  new: SegmentIndex = FindSegment[chain.header[0].chainHead, client];
  chain.next[new] ← chain.header[0].chainHead;
  RecordAllocation[client, new];
  chain.header[0].chainHead ← new;
  RecordAllocation[client, 0];
  RETURN[Address[new, client]];
  END;

NoMorePages: PUBLIC ERROR = CODE;

NextPage: PUBLIC ENTRY PROCEDURE [given: VMDefs.FullAddress, client: Client]
  RETURNS [VMDefs.FullAddress] =
  BEGIN OPEN client;
  ENABLE UNWIND => NULL;
  IF given.page.page = lastPage.page THEN ERROR NoMorePages[]
  ELSE
    IF given.page.page MOD segmentSize < segmentSize - 1 THEN
      BEGIN
      given.page.page ← given.page.page + 1;
      given.word ← FIRST[VMDefs.PageIndex];
      RETURN[given]
      END
    ELSE
      BEGIN
      seg: SegmentIndex = Segment[given.page.page];
      IF chain.next[seg] # noSegment THEN RETURN[Address[chain.next[seg], client]]
      ELSE ERROR UnexpectedChaining[];
      END;
  END;


-- Writer page allocation --

LastPageWrong: ERROR = CODE;

NewWriterPage: PUBLIC ENTRY PROCEDURE [client: Client]
  RETURNS [new: VMDefs.FullAddress] =
  BEGIN OPEN client;  -- see comment beside 'FreeSegment' --
  UNTIL freeCount >= 2 DO WAIT segmentBecomesFree ENDLOOP;
  lastWritten ← FindSegment[lastWritten, client];
  chain.next[lastWritten] ← noSegment;
  new ← Address[lastWritten, client];
  IF new.page.page = lastPage.page THEN ERROR LastPageWrong[];
  END;

UnexpectedChaining: ERROR = CODE;

NextWriterPage: PUBLIC ENTRY PROCEDURE [given: VMDefs.FullAddress, client: Client]
  RETURNS [new: VMDefs.FullAddress] = {new ← InnerNextWriterPage[given, client]};

InnerNextWriterPage: INTERNAL PROCEDURE [given: VMDefs.FullAddress, client: Client]
  RETURNS [new: VMDefs.FullAddress] =
  BEGIN OPEN client;
  ENABLE UNWIND => NULL;
  IF given.page.page MOD segmentSize < segmentSize - 1 THEN
    BEGIN
    given.page.page ← given.page.page + 1;
    given.word ← FIRST[VMDefs.PageIndex];
    new ← given;
    END
  ELSE
    BEGIN
    current: SegmentIndex = Segment[given.page.page];
    IF chain.next[current] # noSegment THEN ERROR UnexpectedChaining[]
    ELSE
      BEGIN  -- see comment beside 'FreeSegment' --
      UNTIL freeCount >= 2 DO WAIT segmentBecomesFree ENDLOOP;
      lastWritten ← FindSegment[current, client];
      chain.next[lastWritten] ← noSegment;
      chain.next[current] ← lastWritten;
      RecordAllocation[client, current];
      new ← Address[lastWritten, client];
      END;
    END;
  IF new.page.page = lastPage.page THEN ERROR LastPageWrong[];
  END;

CommitObject: PUBLIC ENTRY PROCEDURE
  [start, end: VMDefs.PageAddress, client: Client] =
  BEGIN OPEN client;
  CheckForUnwrittenAllocation[client];  --beware of single-page writers--
  chain.next[lastChained] ← Segment[start.page];
  RecordAllocation[client, lastChained];
  lastChained ← Segment[end.page];
  lastPage ← end;
  Policy.GapExists[];
  END;

ObjectAbandoned: PUBLIC ENTRY PROCEDURE
  [start: VMDefs.PageAddress, client: Client] =
  BEGIN OPEN client;
  head: SegmentIndex ← Segment[start.page];
  WHILE head # noSegment DO
    BEGIN
    old: SegmentIndex = head;
    head ← chain.next[head];
    AddToFreeList[old, client];
    END;
    ENDLOOP;
  BROADCAST segmentBecomesFree;
  END;

CheckForUnwrittenAllocation: INTERNAL PROCEDURE [client: Client] = INLINE
  BEGIN OPEN client;
  -- wait if single-page writer hasn't put its data into the single page --
  WHILE unwrittenAllocation DO WAIT notClaimed ENDLOOP;
  END;

ClaimSinglePage: PUBLIC ENTRY PROCEDURE [client: Client]
  RETURNS [next: VMDefs.PageAddress] =
  BEGIN OPEN client;
  -- single-page writer wants a single page --
  WHILE claimed DO WAIT notClaimed ENDLOOP;
  claimed ← TRUE;
  next ← claimedPage ← InnerNextWriterPage[[page: lastPage, word: 0], client].page;
  -- don't set "unwrittenAllocation" before here, to avoid deadlock with
  -- compactor if InnerNextWriterPage needs to wait to allocate a page
  unwrittenAllocation ← TRUE;
  END;

CommitedSinglePage: PUBLIC ENTRY PROCEDURE [client: Client] =
  BEGIN OPEN client;
  IF claimedPage.page MOD segmentSize = 0 THEN  -- claimedPage was chained onto lastChained --
    BEGIN
    lastChained ← Segment[claimedPage.page];
    RecordAllocation[client, lastChained];
    END;
  lastPage ← claimedPage;
  claimed ← FALSE;
  unwrittenAllocation ← FALSE;
  BROADCAST notClaimed;
  END;


-- management of the free list --

FreeSegment: PUBLIC ENTRY PROCEDURE
  [from, to: VMDefs.FullAddress, client: Client] =
  BEGIN OPEN client;
  -- For the correctness of the compactor, it is necessary that its
  -- reading and writing pointers should always be on separate pages. 
  -- This would imply that we can free a segment if 'from' and 'to' would
  -- be separated by at least a page after removal of the segment. 
  -- However, if at the end of a cycle of the compactor there is a gap of
  -- less than one segment between the reading and writing pointers, and
  -- there is only one segment on the free list, and the writer is waiting
  -- for a segment (the writer can never use the last free segment, since
  -- this would stop the next cycle of the compactor starting), then it is
  -- possible that at the end of the next cycle of the compactor no
  -- segment would have been placed in the free list and so neither the
  -- compactor nor the writer could ever run again.  Accordingly, we must
  -- ensure that the reading and writing pointers are always at least one
  -- segment apart. Note that under such circumstances, the writer might
  -- wait for a long time for a segment to become available for it. --
  fromSegment: SegmentIndex = Segment[from.page.page];
  toSegment: SegmentIndex = Segment[to.page.page];
  IF fromSegment # toSegment AND chain.next[fromSegment] # toSegment THEN
    BEGIN
    ptr: SegmentIndex =
      IF from.page.page MOD segmentSize < to.page.page MOD segmentSize
      OR
        (from.page.page MOD segmentSize = to.page.page MOD segmentSize
          AND from.word <= to.word) THEN fromSegment
      ELSE chain.next[fromSegment];
    head: SegmentIndex ← chain.next[ptr];
    chain.next[ptr] ← toSegment;
    WHILE head # toSegment DO
      BEGIN
      old: SegmentIndex = head;
      head ← chain.next[head];
      AddToFreeList[old, client];
      IF head > LAST[SegmentIndex] THEN
        BEGIN SegmentCorrupt: ERROR = CODE; ERROR SegmentCorrupt END;
      END;
      ENDLOOP;
    CheckForUnwrittenAllocation[client];  -- single-page writers --
    RecordAllocation[client, ptr];
    BROADCAST segmentBecomesFree;
    END;
  END;

AddToFreeList: PROCEDURE [old: SegmentIndex, client: Client] =
  BEGIN OPEN client;
  Bitmap.Clear[freeMap, old];
  freeCount ← freeCount + 1;
  Policy.AmountOfFreeHeap[(freeCount*100 + segmentCount/2)/segmentCount];
  END;


Process.DisableTimeout[@segmentBecomesFree];
Process.DisableTimeout[@notClaimed];
debugClient ← NIL; debugSeg ← noSegment; debugPage ← 0;

END.