InscriptImpl.mesa - Manage Inscript file
Copyright © 1985 by Xerox Corporation. All rights reserved.
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
Doug Wyatt, April 24, 1985 10:12:54 pm PST
<< 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,
InterminalBackdoor 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, Virtual ],
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 InterminalBackdoor, Intime, Loader, PrincOpsUtils, Process, Terminal, VM
EXPORTS ClassInscript
SHARES ClassInscript
= BEGIN OPEN ClassInscript, Intime;
InscriptData: TYPE = REF InscriptObject;
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
terminal: Terminal.Virtual ~ InterminalBackdoor.terminal;
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.>>
stdInscript: InscriptData ~ NEW[InscriptState ← [refCount: 0]];
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: BOOLFALSE, lnFileSize: INTEGER ← 4, lnGroupSize: INTEGER ← 2
] 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[177777B, lnFileSize-16]; --//
subspaceMask ← PrincOpsUtils.BITSHIFT[177777B, lnGroupSize]; --//
minPagesInUse ← fileLength-2-2*subspaceSize; --//
hintPageNumber ← fileLength-2; --//
spaceBase ← AccessIP[0];
spaceLimit ← hintPage ← AccessIP[hintPageNumber];
earliestPage ← 0; writeD ← nullDescBody; writeD.pageNumber ← 0;
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[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[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: BOOL] = {
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: BOOLFALSE] RETURNS [success: BOOLTRUE] = {
IF ~IsValidPd[descriptor] OR descriptor.pageNumber # pageNumber THEN {
iP: InscriptPage;
IF earliestPage > pageNumber OR writeD.pageNumber < pageNumber THEN RETURN[FALSE];
descriptor.iP←iP�ssIP[ 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: BOOL] = {
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.iP�scriptor.iP+pageSize; IF LC[descriptor.iP]>=LC[spaceLimit] THEN descriptor.iP←spaceBase;
descriptor.index𡤀
RETURN[TRUE]; };
SetWritePage: PUBLIC ENTRY PROC [self: InscriptData] RETURNS [success: BOOL] = {
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: BOOL] = {
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: BOOL] = {
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;
RETURN[TRUE];
};
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: BOOL] 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.earliestPage�rliestPage;
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 [BOOL] = 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: BOOLFALSE;
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[terminal, InscriptNotifier];
END.