-- Transport Mechanism Filestore - heap restart --

-- [Indigo]<Grapevine>MS>HeapRestart.mesa

-- Andrew Birrell  September 9, 1982 3:25 pm --

DIRECTORY
BitMapDefs USING [Create, MapIndex, Set],
HeapFile,  -- Beware of re-compilation constraints --
Inline			USING[ COPY ],
ObjectDir, -- Beware of re-compilation constraints --
HeapDefs	USING[ ObjectOffset, objectStart ],
HeapXDefs	USING[ PageHeader, ObjectHeader ],
LogDefs		USING[ WriteLine, WriteLogEntry ],
ObjectDirDefs--EXPORT only--,
ObjectDirXDefs	USING[ ObjectNumber, gapObjectNumber ],
Storage		USING[ Node ],
VMDefs   	USING[ CantOpen, DestroyFile, FileHandle, FullAddress, GetFileLength, MarkStart,
                       OpenFile, Page, PageAddress,
                       PageIndex, PageNumber, pageSize, ReadPage,
                       Release, SetFileLength, UsePage, WaitFile ];

HeapRestart: MONITOR RETURNS [ initHeap: BOOLEAN ]
   LOCKS ObjectDir.LOCK --for RestartObject--
   IMPORTS BitMapDefs, HeapFile, Inline, LogDefs, ObjectDir, Storage, VMDefs
   EXPORTS ObjectDirDefs --RestartObject--, HeapDefs --HeapRestart--
   SHARES  HeapFile, ObjectDir, ObjectDirDefs =
BEGIN


-- Opens files concerned with heap:
--   "Heap.ObjectDir"  The object directory; initialised on every restart
--                     and extended dynamically as needed during run.
--   "Heap.Data"       The file containing the heap objects is considered
--                     as a sequence of separate segments, numbered
--                        [FIRST[HeapFile.Segment]..LAST[HeapFile.Segment]]
--                     These segments are of equal size.  Heap.Data should
--                     be pre-allocated on consecutive pages of physical
--                     disk.
--   "Heap.Segments"      Ordering for segments of Heap.Data; two pages only.  If
--                     this file is size 0 on entry, then 'initHeap' is set
--                     to TRUE and all files are initialised to a state
--                     corresponding to there being no message bodies.
-- The object directory and ancillary variables are set to correspond to
-- the heap objects found to exist in the heap file, read in the
-- order given by "Heap.Chain", with their reference counts set to zero
-- pending restart of steering list queues and mailboxes.  After restart of
-- steering list queues and mailboxes, the Compactor should be STARTed, and
-- will then run as an asynchronous activity.

RestartObject: PUBLIC ENTRY PROCEDURE[ obj: ObjectDirXDefs.ObjectNumber ] =
   BEGIN -- executes inside the ObjectDir monitor --
   OPEN ObjectDir;
   either: POINTER TO DirData = FindData[obj];
   WITH data: either SELECT FROM
     used => BEGIN
             IF data.count = LAST[ObjectCount] THEN ERROR ObjectCountTooBig;
             data.count ← data.count + 1;
             END;
   ENDCASE => ERROR ObjectNotInUse[];
   ReleaseData[dirty];
   END;


BadChainSize: ERROR = CODE;
NoWrittenHeapSegment: ERROR = CODE;
InitialisingHeap: SIGNAL = CODE; -- resume if you really mean it!--
HeapDataTooSmall: ERROR = CODE;

ReadChain: PROCEDURE =
   BEGIN
   OPEN HeapFile, client;
   chainPages: CARDINAL;
   handle ← VMDefs.OpenFile[options: old,
                                     name: "Heap.Data"L, cacheFraction: 40];
   IF VMDefs.GetFileLength[handle].page = 0 THEN ERROR HeapDataTooSmall[];
   segmentCount ← VMDefs.GetFileLength[handle].page / segmentSize;
   chainPages ← (segmentCount+segmentsPerPage-1) / segmentsPerPage;
   segmentCeiling ← segmentCount + chainPages*headerSize - 1;
   chainHandle ← VMDefs.OpenFile[
     options: oldOrNew, name: "Heap.Segments"L, cacheFraction: 0];
   chain ← Storage.Node[(segmentCeiling+1)*SIZE[SegmentIndex]];
   TranslateOldFormat[chainPages];
   IF VMDefs.GetFileLength[chainHandle].page = 0
   THEN BEGIN
        SIGNAL InitialisingHeap[];
        InitChain[chainPages]; InitData[];
        END
   ELSE BEGIN
        initHeap ← FALSE;
        IF VMDefs.GetFileLength[chainHandle].page # chainPages*2
        THEN ERROR BadChainSize;
        FOR page: CARDINAL IN [0..chainPages)
        DO seg: SegmentIndex = page*VMDefs.pageSize;
          chain0: POINTER TO SerialAndHead
            = LOOPHOLE[VMDefs.ReadPage[[chainHandle, page*2], 3]];
          chain1: POINTER TO SerialAndHead
            = LOOPHOLE[VMDefs.ReadPage[[chainHandle, page*2+1], 3]];
          from: POINTER TO SerialAndHead =
            IF chain1.serialNumber > chain0.serialNumber
            THEN chain1
            ELSE chain0;
          Inline.COPY[from: from, to: @chain.header[page],
            nwords: MIN[1+segmentCeiling-seg, VMDefs.pageSize] ];
          VMDefs.Release[LOOPHOLE[chain0]];
          VMDefs.Release[LOOPHOLE[chain1]];
        ENDLOOP;
        END;
   IF chain.header[0].chainHead = noSegment THEN ERROR NoWrittenHeapSegment[];
   InitFreeMap[chainPages];
   END;

InitChain: PROCEDURE[chainPages: CARDINAL] =
   BEGIN OPEN HeapFile, client;
   initHeap ← TRUE;
   LogDefs.WriteLogEntry["Initialising Heap.segments"L];
   LogDefs.WriteLine["Initialising Heap.segments"L];
   VMDefs.SetFileLength[chainHandle, [chainPages*2, 0]];
   FOR index: CARDINAL IN [0..chainPages)
   DO chain.header[index].serialNumber ← 0;
       IF index = 0 -- set up one-page written list --
       THEN { chain.header[index].chainHead ← headerSize;
             chain.next[headerSize] ← noSegment };
       RecordAllocation[index*VMDefs.pageSize];
       RecordAllocation[index*VMDefs.pageSize];
   ENDLOOP;
   END;

InitData: PROCEDURE =
   BEGIN OPEN HeapFile, client;
   LogDefs.WriteLogEntry["Initialising Heap.data"L];
   LogDefs.WriteLine["Initialising Heap.data"L];
   FOR page: VMDefs.PageNumber DECREASING IN [0..VMDefs.GetFileLength[handle].page)
   DO EmptyPage[ [handle, page] ] ENDLOOP;
   VMDefs.WaitFile[handle];
   END;
    
InitFreeMap: PROCEDURE[chainPages: CARDINAL] =
  BEGIN OPEN HeapFile, client;
  freeCount ← segmentCount;
  freeMap ← BitMapDefs.Create[segmentCeiling+1];
  FOR index: CARDINAL IN [0..chainPages) DO
    -- make nonexistent seg indices look 'busy'
    firstBit: BitMapDefs.MapIndex = index * VMDefs.pageSize;
    FOR j: CARDINAL IN [0..headerSize) DO
      BitMapDefs.Set[freeMap, firstBit+j] ENDLOOP;
    ENDLOOP;
  FOR s: CARDINAL ← chain.header[0].chainHead, chain.next[s]
    UNTIL s = noSegment DO
    BitMapDefs.Set[freeMap, s]; freeCount ← freeCount - 1;
    ENDLOOP;
  NotifyFreeCount[];
  END;


HardLuck: ERROR = CODE; -- incorrect old segment size --

TranslateOldFormat: PROC[chainPages: CARDINAL] =
   BEGIN
   -- Translate from Heap.chain (fixed segment size) format to new (6 page) format --
   NewSeg: PROC[oldSeg: SegmentPtr] RETURNS[HeapFile.SegmentIndex] =
     { RETURN[ HeapFile.Segment[oldSeg * oldSize] ] };
   Succ: PROC[a: HeapFile.SegmentIndex] RETURNS[HeapFile.SegmentIndex] =
     { RETURN[ IF (a+1) MOD VMDefs.pageSize < HeapFile.headerSize
         THEN a + HeapFile.headerSize+1 ELSE a+1] };
   segmentCount: CARDINAL = 200;
   Segment:      TYPE = [0..segmentCount);
   SegmentPtr:   TYPE = [ FIRST[Segment] .. 1+LAST[Segment] ];
   noSegment:    SegmentPtr = LAST[SegmentPtr];
   PageType: TYPE = RECORD[
     written: ARRAY[0..0]OF SegmentPtr --for alignment--,
     free:    ARRAY[0..0]OF SegmentPtr --for alignment--,
     ser:     LONG INTEGER,
     next:    ARRAY Segment OF SegmentPtr];
   oldFile: VMDefs.FileHandle = VMDefs.OpenFile[
     options: old, name: "Heap.chain"L, cacheFraction: 0 ! VMDefs.CantOpen =>
     IF reason = notFound THEN GOTO newOnly];
   oldSize: CARDINAL =
     VMDefs.GetFileLength[HeapFile.client.handle].page / segmentCount;
   page0: POINTER TO PageType = LOOPHOLE[VMDefs.ReadPage[[oldFile,0],0]];
   page1: POINTER TO PageType = LOOPHOLE[VMDefs.ReadPage[[oldFile,1],0]];
   page: POINTER TO PageType = IF page0.ser > page1.ser THEN page0 ELSE page1;
   IF VMDefs.GetFileLength[oldFile].page # 2 THEN ERROR;
   IF oldSize # HeapFile.segmentSize * 4 THEN ERROR HardLuck[];
   InitChain[chainPages];
   BEGIN
     prev: HeapFile.SegmentIndex ←
       HeapFile.client.chain.header[0].chainHead ← NewSeg[page.written[0]];
     THROUGH [1..3]
     DO HeapFile.client.chain.next[prev] ← Succ[prev];
        prev ← HeapFile.client.chain.next[prev];
     ENDLOOP;
     FOR oldSeg: SegmentPtr ← page.next[page.written[0]], page.next[oldSeg]
     UNTIL oldSeg = noSegment
     DO new: HeapFile.SegmentIndex = NewSeg[oldSeg];
        HeapFile.client.chain.next[prev] ← new; prev ← new;
        THROUGH [1..3]
        DO HeapFile.client.chain.next[prev] ← Succ[prev];
           prev ← HeapFile.client.chain.next[prev];
        ENDLOOP;
     ENDLOOP;
     HeapFile.client.chain.next[prev] ← HeapFile.noSegment;
   END;
   FOR index: CARDINAL IN [0..chainPages)
   DO HeapFile.RecordAllocation[index*VMDefs.pageSize] ENDLOOP;
   VMDefs.Release[LOOPHOLE[page0]];
   VMDefs.Release[LOOPHOLE[page1]];
   FOR index: VMDefs.PageNumber
   IN [segmentCount*oldSize..
       VMDefs.GetFileLength[HeapFile.client.handle].page)
   DO EmptyPage[[HeapFile.client.handle, index]] ENDLOOP;
   VMDefs.DestroyFile[oldFile];
   EXITS newOnly => NULL;
   END;

EmptyPage: PROCEDURE[ where: VMDefs.PageAddress ] =
   BEGIN
   OPEN VMDefs, HeapXDefs;
   page: Page = ReadPage[where,6]; -- faster than UsePage --
   header: POINTER TO PageHeader = LOOPHOLE[page,POINTER]+FIRST[PageIndex];
   obj: POINTER TO ObjectHeader =
             LOOPHOLE[page,POINTER]+FIRST[PageIndex]+SIZE[PageHeader];
   header.offset ← HeapDefs.objectStart;
   obj.number ← ObjectDirXDefs.gapObjectNumber;
   obj.size ← 1+LAST[PageIndex]-
                     (FIRST[PageIndex]+SIZE[PageHeader]+SIZE[ObjectHeader]);
   MarkStart[page];
   Release[page];
   END;


InitObjectDir: ENTRY PROCEDURE =
   BEGIN -- after InitHeapFile, to get HeapFile.handle --
   OPEN ObjectDir;
   handle ← VMDefs.OpenFile[options:oldOrNew,
                            name: "Heap.ObjectDir", cacheFraction: 10];
   ObjectDir.heapHandle ← HeapFile.client.handle;
   nextVirginPage ← VMDefs.GetFileLength[handle].page;
   IF nextVirginPage = FIRST[VMDefs.PageNumber]
   THEN nextVirginPage ← nextVirginPage+1;
   -- initialize each page with unchained "free" indexes,
   -- and put each page on free page chain.
   firstFreePage ← endOfChain;
   FOR reqd: VMDefs.PageNumber IN [ 0 .. nextVirginPage )
   DO IF dpPage # NIL THEN VMDefs.Release[LOOPHOLE[dpPage,VMDefs.Page]];
      dpPage ← LOOPHOLE[VMDefs.UsePage[[handle,dpNumber←reqd]]];
      FOR index: DirIndex IN DirIndex
      DO dpPage.data[index] ← [free[next:0,dopc:0]] ENDLOOP;
      dpPage.header ←
           [nextFreePage:  unchained,
            nextFreeIndex: noFreeIndex];
      VMDefs.MarkStart[LOOPHOLE[dpPage,VMDefs.Page]];--may cause file extension for page 0--
      ReleaseData[dirty];
      ENDLOOP;
   END;


ReadSegments: PROCEDURE =
   BEGIN
   OPEN HeapFile, client;

   page:        VMDefs.Page;
   usedSegment: SegmentIndex ← lastChained ← chain.header[0].chainHead;
   pos:         VMDefs.FullAddress ← Address[lastChained];
   usedPos:     VMDefs.FullAddress ← pos;
   offset:      HeapDefs.ObjectOffset;
   current:     ObjectDirXDefs.ObjectNumber ← ObjectDirXDefs.gapObjectNumber;

   DefineObject: ENTRY PROCEDURE[ obj: ObjectDirXDefs.ObjectNumber ] =
      BEGIN
      OPEN ObjectDir;
      data: POINTER TO DirData = FindData[obj];
      data↑ ← [used[page:  pos.page.page,
                    word:  pos.word,
                    type:  obj.type,
                    count: zeroCount ] ];
      ReleaseData[dirty];
      END;

   DefineObject[ObjectDirXDefs.gapObjectNumber];

   DO -- Consider any page header --
      IF pos.word = FIRST[VMDefs.PageIndex]
      THEN BEGIN
           pageHead: POINTER TO HeapXDefs.PageHeader;
           page ← VMDefs.ReadPage[pos.page,2];
           pageHead ← LOOPHOLE[page,POINTER]+pos.word;
           pos.word ← pos.word+SIZE[HeapXDefs.PageHeader];
           offset ← pageHead.offset;
           END
      ELSE offset ← HeapDefs.objectStart;
      BEGIN
      object: POINTER TO HeapXDefs.ObjectHeader =
                            LOOPHOLE[page,POINTER]+pos.word;

      -- check for non-empty segment --
      IF object.number # ObjectDirXDefs.gapObjectNumber
      THEN BEGIN usedPos ← pos; usedSegment ← lastChained; END;

      IF ( current # ObjectDirXDefs.gapObjectNumber
           --Inside an object, looking for continuation sub-object--
           -- If a duplicate start is found for an object, then the
           -- later start is chosen, so that all earlier starts are
           -- overwritten before the object number is freed by the
           -- compactor.  Otherwise, confusion could arise by the
           -- object number being re-used and a restart occuring
           -- before the duplicate start has been overwritten.
           AND object.number = current AND offset=HeapDefs.objectStart )
      OR ( object.number # ObjectDirXDefs.gapObjectNumber
           AND offset = HeapDefs.objectStart )
      THEN BEGIN -- start of a new object --
           DefineObject[object.number]; current←object.number;
           END
   -- ELSE we have one of:
   --         continuation of ignorable object,
   --         imbedded object which should be ignored,
   --         unexpected partial object,
   --         gap object --;
      pos.word ← pos.word + SIZE[HeapXDefs.ObjectHeader];
      pos.word ← pos.word + object.size;
      END;
      IF pos.word + SIZE[HeapXDefs.ObjectHeader] > LAST[VMDefs.PageIndex]
      THEN BEGIN
           VMDefs.Release[page];
           -- similar to HeapFileDefs.NextPage, but different --
           IF pos.page.page MOD segmentSize < segmentSize-1
           THEN BEGIN
                pos.page.page ← pos.page.page+1;
                pos.word ← FIRST[VMDefs.PageIndex];
                END
           ELSE IF chain.next[lastChained] # noSegment
                THEN BEGIN
                     old: SegmentIndex = lastChained;
                     pos ← Address[lastChained←chain.next[old]];
                     IF usedSegment # old
                     THEN BEGIN -- 'old' is entirely empty --
                          chain.next[usedSegment] ← lastChained;
                          AddToFreeList[old];
                          END
                     END
                ELSE EXIT -- note "pos" is last page in chain.written--;
           END
      ELSE -- end of any current object --
           current ← ObjectDirXDefs.gapObjectNumber;
   ENDLOOP;

   lastPage ← pos.page;
   lastWritten ← lastChained;

   END;


TidyObjectDir: ENTRY PROCEDURE =
   -- Construct object directory page free chains --
   -- Add pages with non-empty free chains to page free chain
   BEGIN
   OPEN ObjectDir;
   FOR p: ObjDirPageNumber DECREASING IN [ 0 .. nextVirginPage )
   DO GetDirPage[p];
      dpPage.header.nextFreeIndex ← noFreeIndex;
      FOR index: DirIndex IN DirIndex
      DO WITH data: dpPage.data[index] SELECT FROM
           free => BEGIN
                   dpPage.data[index] ←
                      [free[ next: dpPage.header.nextFreeIndex,
                             dopc: lastDofpc ]];
                   dpPage.header.nextFreeIndex ← index;
                   END;
         ENDCASE => NULL;
      ENDLOOP;
      IF dpPage.header.nextFreeIndex # noFreeIndex
      THEN{ dpPage.header.nextFreePage ← firstFreePage;
            firstFreePage ← dpNumber };
      ReleaseData[dirty];
   ENDLOOP;
   END;


Go: PROCEDURE =
   BEGIN
   LogDefs.WriteLine["Restarting heap storage"L];
   START HeapFile;  ReadChain[];
   START ObjectDir; InitObjectDir[];
   ReadSegments[];  TidyObjectDir[];
   END;

Go[];


END.