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:
BOOL ←
FALSE, 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: BOOL←FALSE] RETURNS [success: BOOL←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: 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.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:
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.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 [
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: BOOL←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[terminal, InscriptNotifier];
END.