-- 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]]]; };