InscriptImpl.mesa  -  Manage Inscript file
Stone June 14, 1982 4:32 pm
McGregor, 14-Sep-81 16:38:27
Swinehart, April 29, 1982 5:13 pm
Paul Rovner, August 10, 1983 5:46 pm
Russ Atkinson, September 23, 1983 10:32 am
Birrell, August 23, 1983 9:33 am
 
<< This version requires that the Inscript file be resident.  Its default size has therefore been made much smaller than previous versions (16 pages, of which at least 8 have predictable contents (steady state)>>
<< This version also assumes (and ensures) that there is but one inscript per module instance.>> 
DIRECTORY
ClassInscript,
Interminal USING [ terminal ],
Intime USING [ EventTime, EventTimeDifference, MsTicks, ReadEventTime ],
Loader USING [ MakeProcedureResident, MakeGlobalFrameResident ],
PrincOpsUtils USING [ BITAND, BITSHIFT, LongCOPY ],
Process USING [ EnableAborts, MsecToTicks, SecondsToTicks, Ticks ],
Terminal USING [ RegisterNotifier, SwapAction, SwapNotifier ],
VM USING [ Allocate, Free, Interval, AddressForPageNumber, Pin, Unpin, wordsPerPage ]
;
 
<<Denotes variables that would have to move into object for multiple files.>>
InscriptImpl: MONITOR -- invariant: File contents consistent
IMPORTS
Interminal, Intime, Loader, PrincOpsUtils, Process, Terminal, VM
 
EXPORTS ClassInscript
SHARES ClassInscript =
BEGIN OPEN ClassInscript, Intime;
InscriptData: TYPE = LONG POINTER TO InscriptState;
InscriptObject: PUBLIC TYPE = InscriptState;
 
InscriptState: TYPE = RECORD [refCount: INT];
<< One inscript per instance; state entirely contained in frame.
NewStdInscript ups ref count; destroy downs it.>>
file information
inscriptSpace: VM.Interval;
 
mapping information
earliestPage: InscriptPageNumber ← 0;
latestRecordedD: InscriptPageDescBody←nullDescBody;
subspaceSize: CARDINAL; -- # of pages of inscr. file mapped as a unit to insc. VM
spaceBase: InscriptPage;
spaceLimit: InscriptPage;
 
output page information:
writeDescriptor: PageDescriptor=@writeD; -- all entries into file use this
writeD: InscriptPageDescBody←nullDescBody;
 
 
maintProcess: PROCESS;  background, writes dirty pages occasionally
this and that
actualPageMask: WORD; --<<>>
subspaceMask: WORD; --<<>>
hintPageNumber: ActualIPN; --<<>>
hintPage: InscriptPage;
minPagesInUse: ActualIPN;   --<<]>>;
<<Everything is built in, for now; possibly multiple simultaneous inscripts using this code wouldn't work.>>
stdInscriptState: InscriptState;
stdInscript: InscriptData=@stdInscriptState;
InscriptError: PUBLIC ERROR [code: InscriptErrorCode] = CODE;
 
Types, specifications for the low-level Ring-buffer inscript
file implemented here
Internal: ERROR[code: CARDINAL] = CODE;
pageSize: INTEGER=VM.wordsPerPage;
PageIndex: TYPE = [0..pageSize);
PageDescriptor: TYPE = LONG POINTER TO InscriptPageDescBody;
<<Must be 4 words long, or must change opaque type in ClassInscript>>
InscriptPageDescBody: 
PUBLIC 
TYPE = 
RECORD [
pageNumber: InscriptPageNumber,
iP: InscriptPage, -- base of current page.
index: PageIndex ];
 
nullDescBody: InscriptPageDescBody =[pageNumber:-1,iP:NIL,index:NULL];
InscriptPage: TYPE = LONG POINTER TO InscriptPageValue;
InscriptPageValue: 
TYPE = 
MACHINE 
DEPENDENT 
RECORD [
seal: CARDINAL, -- and this one --
data: 
SELECT 
OVERLAID * 
FROM
entryPage=> [ 
length: CARDINAL, -- excludes this word --
entries: ARRAY [0..nInscriptPageIndex) OF WORD],
 
hintPage=> [
earliestPage: 
CARDINAL,
latestPage: CARDINAL,
subspaceSize: CARDINAL,
rest: ARRAY [0..nInscriptPageIndex-2) OF WORD ],
 
 
ENDCASE];
 
 
nInscriptPageIndex: PageIndex = pageSize - 2;
inscriptPageSeal: CARDINAL = 154275B;
ActualIPN: TYPE = INTEGER;
nullAIPN: ActualIPN=-1;
Time0: OrderKey=[0,0,0];
 
NewStdInscript: 
PUBLIC 
PROC [
KeyProc: KEyProc, ComProc: COmProc, initializeFile: BOOLEAN ← FALSE,
lnFileSize: INTEGER𡤄, lnGroupSize: INTEGER𡤂] RETURNS [Inscript] = {
ENABLE InscriptError => { IF code=invalidInscriptFile THEN VM.Free[inscriptSpace]; };
Complain: PROC={ IF ~initializeFile THEN ERROR InscriptError[invalidInscriptFile]; };
refCount: INT=stdInscript.refCount+1;
fileLength: ActualIPN=TwoToThe[lnFileSize]+2; --1 for leader page, 1 for my trailer
IF refCount#1 THEN ERROR Internal[6]; -- not now
IF lnGroupSize>=lnFileSize 
OR lnGroupSize < 2 
THEN
ERROR InscriptError[invalidInscriptSpecs];
 
inscriptSpace←VM.Allocate[count: fileLength-1];
subspaceSize←TwoToThe[lnGroupSize];
actualPageMask←PrincOpsUtils.BITSHIFT[-1,lnFileSize-16]; --//
subspaceMask←PrincOpsUtils.BITSHIFT[-1,lnGroupSize]; --//
minPagesInUse𡤏ileLength-2-2*subspaceSize; --//
hintPageNumber𡤏ileLength-2; --//
spaceBasessIP[0];
spaceLimit←hintPagessIP[hintPageNumber];
earliestPage𡤀 writeD←nullDescBody; writeD.pageNumber𡤀
IF initializeFile THEN []←SetPage[stdInscript, writeDescriptor, 0, TRUE]
ELSE { 
-- file init: old
pD: PageDescriptor=@latestRecordedD; -- use as temp here
KeyFrom: 
PROC[pN: InscriptPageNumber] 
RETURNS [OrderKey] = {
IF SetPage[stdInscript, pD, pN] 
THEN
RETURN[KeyProc[stdInscript, pD !
InscriptError=>IF code=invalidPageKey THEN CONTINUE]];
 
 
RETURN[Time0]};
 
latestTime, nextTime: OrderKey; latestPage, maxLP: InscriptPageNumber;
IF hintPage.seal#inscriptPageSeal OR hintPage.subspaceSize#subspaceSize THEN Complain[];
earliestPage←PrincOpsUtils.BITAND[hintPage.earliestPage,actualPageMask]; -- normalize
latestPage←writeD.pageNumber←hintPage.latestPage-(hintPage.earliestPage-earliestPage);
maxLP←latestPage+fileLength-3; -- search max distance
latestTime←KeyFrom[latestPage];
IF latestTime=Time0 THEN ERROR InscriptError[invalidInscriptFile];
FOR pageNumber: InscriptPageNumber 
IN [latestPage+1..maxLP) 
DO
nextTime←KeyFrom[pageNumber];
IF ComProc[latestTime, nextTime] THEN EXIT
ELSE { latestTime←nextTime; latestPage←pageNumber; };
ENDLOOP;
 
IF ~SetPage[stdInscript, writeDescriptor, latestPage] THEN ERROR Internal[1];
writeD.index ← writeD.iP.length; };
 
Final init
latestRecordedD←writeD;
InscriptNotifier[Interminal.terminal, coming, NIL]; -- lock inscript space, enable recording.
 
maintProcess ← FORK PageMaintainer[];
Process.Detach[maintProcess];
stdInscript.refCount←refCount;
RETURN[stdInscript]; };
 
Release: 
PUBLIC 
ENTRY 
PROC [self: InscriptData] 
RETURNS [nilInscript: Inscript] = {
ENABLE UNWIND => NULL;
IF(self.refCount←self.refCount-1)#0 THEN RETURN[NIL[Inscript]];
Process.Abort[maintProcess];
InscriptNotifier[Interminal.terminal, going, NIL]; -- <<superfluous?>>
VM.Free[inscriptSpace];
RETURN[NIL[Inscript]];
};
 
GetPageLimits: 
PUBLIC 
ENTRY 
PROC [self: InscriptData]
RETURNS [earliestPageNo: InscriptPageNumber, latestPageNo: InscriptPageNumber] = {
RETURN[earliestPageNo: earliestPage, latestPageNo: writeD.pageNumber]; };
 
 
ResetPageDescriptor: 
PUBLIC 
ENTRY 
PROC [self: InscriptData, descriptor: PageDescriptor] = {
IF descriptor#NIL THEN descriptor^←nullDescBody}; -- now invalid
 
CopyPageDescriptor: 
PUBLIC 
ENTRY 
PROC [
self: InscriptData, dest: PageDescriptor, source: PageDescriptor] = {
dest^ ← source^; };
 
 
WaitForEntry: 
PUBLIC 
ENTRY 
PROC [
self: InscriptData, waitMode: WaitMode, waitInterval: MsTicks ← waitALongTime,
descriptor: PageDescriptor, waitStartTime: LONG POINTER TO EventTime ← NIL]
RETURNS [moreEntries: BOOLEAN] = {
ENABLE UNWIND => NULL;
t0, t1: EventTime;
waitTime: MsTicks ← waitInterval;
IF waitMode = timed 
AND waitStartTime = 
NIL 
THEN {
t0 ← ReadEventTime[]; waitStartTime ← @t0; };
 
DO
--   IF descriptor.pageNumber > writeD.pageNumber THEN ERROR Internal[5] --
--ELSE-- 
IF descriptor.pageNumber < writeD.pageNumber 
OR
descriptor.index<writeD.index THEN RETURN[TRUE];
 
SELECT waitMode 
FROM
dontWait => RETURN[FALSE];
forever => waitTime←waitALongTime;
timed => {
dif: MsTicks;
t1 ← ReadEventTime[];
dif ← EventTimeDifference[@t1, waitStartTime];
IF dif >= waitInterval THEN RETURN[FALSE];
waitTime ← waitInterval - dif; };
 
ENDCASE;
 
Wait[Process.MsecToTicks[waitTime]];
ENDLOOP; };
 
 
Cleans up descriptor, if necessary
Reads the indicated pages
success: page available, has seal; failure: page unavailable, or does not have seal (uninit.)
If init is TRUE, assumes page contents is valueless, initializes page
 
SetPage: 
PUBLIC 
PROC [
self: InscriptData, descriptor: PageDescriptor,
pageNumber: InscriptPageNumber, init: BOOLEAN←FALSE] RETURNS [success: BOOLEAN←TRUE] = {
IF ~IsValidPd[descriptor] 
OR descriptor.pageNumber # pageNumber 
THEN {
iP: InscriptPage;
IF earliestPage > pageNumber OR writeD.pageNumber < pageNumber THEN RETURN[FALSE];
descriptor.iP←iPssIP[  IPNToActual[ descriptor.pageNumber←pageNumber ]  ];
IF init THEN { iP.length𡤀 iP.seal ← inscriptPageSeal; }
-- Next test useful only during initialization, in FindTimeSeam --
ELSE IF iP.seal # inscriptPageSeal THEN success←FALSE; };
 
descriptor.index ← 0; };
 
Read the next page, set descriptor.
Failure: same as SetPage
Raises InscriptError[descriptorUninitialized] if there's no current page.
AdvancePage: 
PUBLIC 
PROC [self: InscriptData, descriptor: PageDescriptor]
RETURNS [success: BOOLEAN] = {
pageNumber: InscriptPageNumber=descriptor.pageNumber+1;
IF ~IsValidPd[descriptor] THEN ERROR InscriptError[descriptorUninitialized];
IF earliestPage > pageNumber OR writeD.pageNumber < pageNumber THEN RETURN[FALSE];
descriptor.pageNumber←pageNumber;
descriptor.iPscriptor.iP+pageSize; IF LC[descriptor.iP]>=LC[spaceLimit] THEN descriptor.iP←spaceBase;
descriptor.index𡤀
RETURN[TRUE]; };
 
SetWritePage: 
PUBLIC 
ENTRY 
PROC [self: InscriptData] 
RETURNS [success: 
BOOLEAN] = {
writeD.pageNumber←writeD.pageNumber+1;
writeD.iP←writeD.iP+pageSize; IF LC[writeD.iP]>=LC[spaceLimit] THEN writeD.iP←spaceBase;
writeD.iP.seal←inscriptPageSeal;
writeD.index←writeD.iP.length𡤀
earliestPage ← PrincOpsUtils.BITAND[MAX[earliestPage, writeD.pageNumber-minPagesInUse], subspaceMask];
NOTIFY pageDone;
RETURN[TRUE]; };
 
ReadEntry: 
PUBLIC 
ENTRY 
PROC [
self: InscriptData, descriptor: PageDescriptor,
destination: LONG POINTER TO UNSPECIFIED, LengthProc: LengthProcType]
RETURNS [success: BOOLEAN] = {
ENABLE UNWIND => NULL;
index: PageIndex ← descriptor.index;
len: CARDINAL;
entry: LONG POINTER TO UNSPECIFIED;
pageNumber: InscriptPageNumber = descriptor.pageNumber;
inscriptPage: InscriptPage=descriptor.iP;
IF ~IsValidPd[descriptor] THEN ERROR InscriptError[descriptorUninitialized];
IF earliestPage > pageNumber 
-- OR pageNumber > writeD.pageNumber -- 
THEN 
ERROR InscriptError[entryOutOfBounds];
 
IF index >= inscriptPage.length THEN RETURN[FALSE];
entry ← @(inscriptPage.entries[index]);
PrincOpsUtils.LongCOPY[from: entry, to: destination, nwords: (len←LengthProc[entry])];
IF index+len > inscriptPage.length THEN RETURN[FALSE];
descriptor.index ← index + len;
RETURN[TRUE]; };
 
 
Put entry in a buffer (indicate if page filled up).  See MaintainPage for actual file write
WriteEntry: 
PUBLIC 
ENTRY 
PROC [
self: InscriptData, entry: LONG DESCRIPTOR FOR ARRAY OF WORD]
RETURNS [success: BOOLEAN] = {
ENABLE UNWIND => NULL;
newIndex: CARDINAL = writeD.index+LENGTH[entry];
IF newIndex > nInscriptPageIndex THEN RETURN[FALSE];
PrincOpsUtils.LongCOPY[BASE[entry], LENGTH[entry], @writeD.iP.entries[writeD.index]];
writeD.index ← writeD.iP.length ← newIndex;
maintDue←FALSE; -- non-preemptive notify for page maintainer
BROADCAST entriesWaiting;
 
Utilities, Cleanup process, mapping functions, machine extensions
PageMaintainer: PROC = {
Monitored only where required.   Says here.
For the current page, this function merely keeps the inscript file
tracking reality approximately; for pages that are finished, no other process will
be changing them again.  Says here.
DO  forever
ENABLE Process.Aborted => EXIT;  (almost forever)
SELECT MaintainPage[FALSE] FROM
dirty=> Space.ForceOut[inscriptSpace];
aborted=>EXIT;
ENDCASE; ENDLOOP; };
dirtyThreshhold: INTEGER=2;
MaintainPage: 
ENTRY 
PROC[forceDirty: 
BOOLEAN] 
RETURNS [{dirty, aborted}] = { 
DO
ENABLE ABORTED => EXIT;
IF ~forceDirty 
THEN {
dif: INTEGER;
WAIT pageDone;
dif←writeD.pageNumber-latestRecordedD.pageNumber;
IF latestRecordedD=writeD 
OR (dif<dirtyThreshhold 
AND ~maintDue) 
THEN {
maintDue←TRUE; LOOP }; };
 
 
maintDue←FALSE;
latestRecordedD←writeD;
hintPage.seal←inscriptPageSeal;
hintPage.earliestPagerliestPage;
hintPage.latestPage←latestRecordedD.pageNumber;
hintPage.subspaceSize←subspaceSize;
RETURN[dirty]; ENDLOOP;
 
RETURN[aborted];};
 
AccessIP: 
PROC[actualIPN: ActualIPN]
RETURNS[page: InscriptPage] = INLINE
{RETURN[LOOPHOLE[VM.AddressForPageNumber[inscriptSpace.page+actualIPN]]];};
 
IPNToActual: 
PROC [pN: InscriptPageNumber]
RETURNS [ActualIPN] = INLINE -- Assert 0<=pN
{RETURN[PrincOpsUtils.BITAND[pN, actualPageMask]]; };
 
IsValidPd: 
PROC[pD: PageDescriptor] 
RETURNS [
BOOLEAN] = 
INLINE
{ RETURN[pD.iP#NIL]};
 
LC:
PROC[iP:InscriptPage]
RETURNS[
LONG 
CARDINAL]=
INLINE{
RETURN[
LOOPHOLE[iP]]};
 
Wait on entriesWaiting for specified time
Wait: 
INTERNAL 
PROC [t: 
CARDINAL] = 
INLINE {
entriesWaiting.timeout←t; WAIT entriesWaiting};
 
TwoToThe: 
PROC[ln: 
CARDINAL] 
RETURNS [
CARDINAL] = 
INLINE {
RETURN[PrincOpsUtils.
BITSHIFT[1, ln]]};
 
entriesWaiting: CONDITION;
pageDone: CONDITION;
maintTicks: Process.Ticks=Process.SecondsToTicks[6];
maintDue: BOOLEAN←FALSE;
-- Initialization --
 
inscriptCurrent: INTEGER ← 0;
InscriptNotifier: Terminal.SwapNotifier = 
TRUSTED {
[vt: Virtual, action: SwapAction, clientData: REF ANY]
SELECT action 
FROM
coming => {
IF inscriptCurrent = 0 THEN VM.Pin[inscriptSpace]; 
inscriptCurrent ← inscriptCurrent + 1; };
 
going => {
inscriptCurrent ← inscriptCurrent - 1;
IF inscriptCurrent = 0 
THEN {
VM.Unpin[inscriptSpace];
[]←MaintainPage[TRUE];
}};
 
 
here, gone => NULL;
ENDCASE=>ERROR;
};
 
Loader.MakeProcedureResident[--e.g.--InscriptNotifier];
Loader.MakeGlobalFrameResident[--e.g.--InscriptNotifier];
pageDone.timeout ← maintTicks;
-- NOTE: Must be locked because of known planned use by other abstractions! --
[]←Intime.ReadEventTime[]; -- make sure Time starts first.
Process.EnableAborts[@pageDone];
Process.EnableAborts[@entriesWaiting];
Terminal.RegisterNotifier[Interminal.terminal, InscriptNotifier];
 
END.