-- InscriptImpl.mesa
-- Manage Inscript file
-- Last Edited by Stone June 14, 1982 4:32 pm
-- Last Edited by McGregor, 14-Sep-81 16:38:27
-- Last Edited by Swinehart, April 29, 1982 5:13 pm
-- << 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
CedarSnapshot USING [ Register, RollbackProc ],
ClassInscript,
Directory USING [ CreateFile, DeleteFile, Error, LookupUnlimited ],
File USING [ Capability, GetSize, MakePermanent, nullCapability ],
DCSFileTypes USING [ tLeaderPage ],
Inline USING [ BITAND, BITSHIFT, LongCOPY, LowHalf ],
Intime USING [ EventTime, EventTimeDifference, MsTicks, ReadEventTime ],
Process USING [
Abort, Aborted, Detach, EnableAborts, MsecToTicks, SecondsToTicks, Ticks ],
Rope USING [ ROPE, ToRefText ],
Space USING [ Create, --CreateUniformSwapUnits,-- Delete, ForceOut,
--GetHandle,-- Handle, --Kill,-- LongPointerFromPage, Map,
--PageFromLongPointer, -- virtualMemory, VMPageNumber, wordsPerPage ],
SpecialSpace USING [
MakeCodeResident, MakeGlobalFrameResident,
MakeResident, MakeSwappable ],
TerminalMultiplex USING [ RegisterNotifier, SwapAction ]
;
-- <<Denotes variables that would have to move into object for multiple files.>>
InscriptImpl: MONITOR -- invariant: File contents consistent
IMPORTS
CedarSnapshot, Inline, Intime, Process, Rope, Dir: Directory, File,
Space, SpecialSpace, TerminalMultiplex
EXPORTS ClassInscript
SHARES ClassInscript =
BEGIN OPEN ClassInscript, Intime;
InscriptData: TYPE = LONG POINTER TO InscriptState;
InscriptObject: PUBLIC TYPE = InscriptState;
ROPE:TYPE=Rope.ROPE;
InscriptState: TYPE = RECORD [refCount: INT];
-- << One inscript per instance; state entirely contained in frame.
-- NewStdInscript ups ref count; destroy downs it.>>
-- file information
inscriptFileName: Rope.ROPE←NIL; -- OK, but . . .
inscriptFile: File.Capability;
inscriptSpace: Space.Handle;
-- 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=Space.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 PROCEDURE [fileName: Rope.ROPE,
KeyProc: KEyProc, ComProc: COmProc, initializeFile: BOOLEAN ← FALSE,
lnFileSize: INTEGER←4, lnGroupSize: INTEGER←2] RETURNS [Inscript] = {
ENABLE InscriptError => { IF code=invalidInscriptFile THEN Space.Delete[inscriptSpace]; };
Complain: PROC={ IF ~initializeFile THEN ERROR InscriptError[invalidInscriptFile]; };
refCount: INT=stdInscript.refCount+1;
fN: LONG STRING=LOOPHOLE[Rope.ToRefText[fileName]];
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];
inscriptFileName←fileName;
inscriptSpace←Space.Create[size: fileLength-1, parent: Space.virtualMemory];
inscriptFile←File.nullCapability;
inscriptFile←Dir.LookupUnlimited[fileName: fN!
Dir.Error=>IF type=fileNotFound THEN CONTINUE ELSE ERROR Internal[4]];
DO
IF inscriptFile=File.nullCapability THEN { -- new file
Complain[];
inscriptFile←Dir.CreateFile[fileName: fN,
size: fileLength-1, fileType: DCSFileTypes.tLeaderPage];
File.MakePermanent[inscriptFile]; EXIT; }
ELSE {
IF Inline.LowHalf[File.GetSize[inscriptFile]]=fileLength THEN EXIT;
Complain[]; Dir.DeleteFile[fileName: fN]; inscriptFile←File.nullCapability; };
ENDLOOP;
-- Correct-sized file exists.
Space.Map[space: inscriptSpace, window: [file: inscriptFile, base: 1]];
-- Space.CreateUniformSwapUnits[parent: inscriptSpace, size: 1];
-- File calculations
subspaceSize←TwoToThe[lnGroupSize];
actualPageMask←Inline.BITSHIFT[-1,lnFileSize-16]; --//
subspaceMask←Inline.BITSHIFT[-1,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←Inline.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[coming]; -- lock inscript space, enable recording.
maintProcess ← FORK PageMaintainer[];
Process.Detach[maintProcess];
stdInscript.refCount←refCount;
RETURN[stdInscript]; };
Release: PUBLIC ENTRY PROCEDURE [self: InscriptData] RETURNS [nilInscript: Inscript] =
BEGIN
ENABLE UNWIND => NULL;
IF (self.refCount←self.refCount-1)#0 THEN RETURN[NIL[Inscript]];
Process.Abort[maintProcess];
InscriptNotifier[going]; -- <<superfluous?>>
Space.Delete[inscriptSpace];
inscriptFileName←NIL;
RETURN[NIL[Inscript]];
END;
GetPageLimits: PUBLIC ENTRY PROCEDURE [self: InscriptData]
RETURNS [earliestPageNo: InscriptPageNumber, latestPageNo: InscriptPageNumber] = {
RETURN[earliestPageNo: earliestPage, latestPageNo: writeD.pageNumber]; };
ResetPageDescriptor: PUBLIC ENTRY PROCEDURE [self: InscriptData, descriptor: PageDescriptor] = {
IF descriptor#NIL THEN descriptor↑←nullDescBody}; -- now invalid
CopyPageDescriptor: PUBLIC ENTRY PROCEDURE [
self: InscriptData, dest: PageDescriptor, source: PageDescriptor] = {
dest↑ ← source↑; };
WaitForEntry: PUBLIC ENTRY PROCEDURE [
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 PROCEDURE [
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←iP←AccessIP[ IPNToActual[ descriptor.pageNumber←pageNumber ] ];
IF init THEN { iP.length←0; 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 PROCEDURE [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.iP←descriptor.iP+pageSize; IF LC[descriptor.iP]>=LC[spaceLimit] THEN descriptor.iP←spaceBase;
descriptor.index←0;
RETURN[TRUE]; };
SetWritePage: PUBLIC ENTRY PROCEDURE [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←0;
earliestPage ← Inline.BITAND[MAX[earliestPage, writeD.pageNumber-minPagesInUse], subspaceMask];
NOTIFY pageDone;
RETURN[TRUE]; };
ReadEntry: PUBLIC ENTRY PROCEDURE [
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]);
Inline.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 PROCEDURE [
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];
Inline.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: BOOLEAN] RETURNS [{dirty, aborted}] = {
DO
ENABLE Process.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←earliestPage;
hintPage.latestPage←latestRecordedD.pageNumber;
hintPage.subspaceSize←subspaceSize;
RETURN[dirty]; ENDLOOP;
RETURN[aborted];};
AccessIP: PROCEDURE[actualIPN: ActualIPN]
RETURNS[page: InscriptPage] = INLINE
{RETURN[LOOPHOLE[Space.LongPointerFromPage[Space.VMPageNumber[inscriptSpace]+actualIPN]]];};
IPNToActual: PROCEDURE [pN: InscriptPageNumber]
RETURNS [ActualIPN] = INLINE -- Assert 0<=pN
{RETURN[Inline.BITAND[pN, actualPageMask]]; };
IsValidPd: PROCEDURE[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 PROCEDURE [t: CARDINAL] = INLINE {
entriesWaiting.timeout←t; WAIT entriesWaiting};
TwoToThe: PROC[ln: CARDINAL] RETURNS [CARDINAL] = INLINE {
RETURN[Inline.BITSHIFT[1, ln]]};
entriesWaiting: CONDITION;
pageDone: CONDITION;
maintTicks: Process.Ticks=Process.SecondsToTicks[6];
maintDue: BOOLEAN←FALSE;
-- Initialization --
inscriptCurrent: INTEGER ← 0;
InscriptNotifier: PROC[action: TerminalMultiplex.SwapAction] = {
SELECT action FROM
coming => {
IF inscriptCurrent = 0 THEN SpecialSpace.MakeResident[inscriptSpace];
inscriptCurrent ← inscriptCurrent + 1; };
going => {
inscriptCurrent ← inscriptCurrent - 1;
IF inscriptCurrent = 0 THEN {
SpecialSpace.MakeSwappable[inscriptSpace];
[]←MaintainPage[TRUE];
Space.ForceOut[inscriptSpace]; }};
ENDCASE=>ERROR; };
InscriptRBProc: CedarSnapshot.RollbackProc={
InscriptNotifier[coming]; };
InscriptCPProc: PROC[ASP: PROC[Space.Handle]] = {
InscriptNotifier[going]; ASP[inscriptSpace];};
pageDone.timeout ← maintTicks;
-- NOTE: Must be locked because of known planned use by other abstractions! --
[]←Intime.ReadEventTime[]; -- make sure Time starts first.
SpecialSpace.MakeGlobalFrameResident[InscriptImpl];
SpecialSpace.MakeCodeResident[InscriptImpl];
Process.EnableAborts[@pageDone];
Process.EnableAborts[@entriesWaiting];
TerminalMultiplex.RegisterNotifier[InscriptNotifier];
CedarSnapshot.Register[c: InscriptCPProc, r: InscriptRBProc];
END.
-- Currently unneeded
InitPage: PROC[inscriptPage: InscriptPage] = INLINE {
Space.Kill[IPToSubspace[inscriptPage]]; };
IPToSubspace: PROCEDURE[inscriptPage: InscriptPage] RETURNS[h: Space.Handle] = INLINE
{ RETURN[Space.GetHandle[Space.PageFromLongPointer[inscriptPage]]]; };