-- Copyright (C) 1981, 1983, 1984, 1985 by Xerox Corporation. All rights reserved. -- RegBTree.mesa, Transport Mechanism Registration Server - Operations on the B-Tree -- -- HGM, 15-Sep-85 7:59:00 -- Randy Gobbel, 19-May-81 12:12:10 -- Andrew Birrell, 4-Nov-81 16:19:48 -- Mike Schroeder 25-Jan-83 15:33:27 DIRECTORY BodyDefs USING [maxRNameLength, oldestTime, RName, Timestamp], BTreeDefs, EnquiryDefs USING [], Heap USING [systemZone], HeapDefs USING [ GetReaderObject, HeapAbandonWrite, HeapEndWrite, HeapEndRead, HeapReadRName, HeapStartRead, HeapStartWrite, HeapWriteRName, ObjectNumber, ReaderHandle, WriterHandle], Inline USING [LongCOPY], LogDefs USING [ShowLine], ObjectDirDefs USING [FreeObject, RestartObject, UseObject], PolicyDefs USING [EndOperation, RegPurgerPause, WaitOperation], Process USING [Pause], ProtocolDefs USING [RNameType], RegBTreeDefs USING [LookupReason, RegistryObject, RegState], RegCacheDefs USING [AddName, FlushName, ReadName, TestKnownReg], RegistryDefs USING [MakeTimestamp], RegServerDefs USING [ConsiderPurging, ReallyPurge], String USING [AppendString], VMDefs USING [OpenFile]; RegBTree: MONITOR IMPORTS BTreeDefs, Heap, HeapDefs, Inline, LogDefs, ObjectDirDefs, PolicyDefs, Process, RegCacheDefs, RegistryDefs, RegServerDefs, String, VMDefs EXPORTS EnquiryDefs, RegBTreeDefs = BEGIN OPEN RegBTreeDefs; -- the b-tree is the only part of the data structures that needs to be -- protected by the monitor. The requirement is that at the end of an -- update to the database, the result must be accepted only if the entry -- in the b-tree is still the same as it was when the update commenced. -- If the entry has changed, a signal is raised and the update is re- -- calculated. -- B-Tree lookups must also be protected, since during b-tree entry -- replacement the item is temporarily deleted from the b-tree. Note that -- the result of a b-tree lookup must have incremented the reference count -- on the object, in case someone else deletes the entry. -- The lookups are protected by a single-writer, multiple-reader interlock. LowerCase: PROCEDURE [c: CHARACTER] RETURNS [CHARACTER] = INLINE { RETURN[IF c IN ['A..'Z] THEN c - 'A + 'a ELSE c]}; IsFirstGE: BTreeDefs.TestKeys = BEGIN -- parameters a,b: DESC FOR ARRAY OF WORD returns[ BOOLEAN]-- aC: LONG POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[a]]; bC: LONG POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[b]]; FOR i: CARDINAL IN [0..2 * MIN[LENGTH[a], LENGTH[b]]) DO IF LowerCase[aC[i]] < LowerCase[bC[i]] THEN RETURN[FALSE]; IF LowerCase[aC[i]] > LowerCase[bC[i]] THEN RETURN[TRUE]; ENDLOOP; RETURN[LENGTH[a] >= LENGTH[b]]; END; AreTheyEq: BTreeDefs.TestKeys = BEGIN aC: LONG POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[a]]; bC: LONG POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[b]]; IF LENGTH[a] = LENGTH[b] THEN FOR i: CARDINAL IN [0..2 * LENGTH[a]) DO IF LowerCase[aC[i]] # LowerCase[bC[i]] THEN EXIT; REPEAT FINISHED => RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; END; RNameDesc: PROCEDURE [name: BodyDefs.RName] RETURNS [LONG DESCRIPTOR FOR ARRAY OF WORD] = BEGIN IF name.length MOD 2 # 0 THEN name[name.length] ← '@; RETURN[DESCRIPTOR[@(name.text), (1 + name.length) / 2]] END; tree: BTreeDefs.BTreeHandle; Init: ENTRY PROC = BEGIN tree ← BTreeDefs.CreateAndInitializeBTree[ fileH: LOOPHOLE[VMDefs.OpenFile[ name: "Registration.BTree"L, options: oldOrNew, cacheFraction: 10]], initializeFile: TRUE, useDefaultOrderingRoutines: FALSE, isFirstGreaterOrEqual: IsFirstGE, areTheyEqual: AreTheyEq]; END; -- BTree interlock: -- presence of readers is indicated by readerCount -- -- Writers operate with the monitor locked -- readerCount: CARDINAL ← 0; noReaders: CONDITION; StartReader: ENTRY PROC = INLINE {readerCount ← readerCount + 1}; EndReader: ENTRY PROC = INLINE { readerCount ← readerCount - 1; IF readerCount = 0 THEN BROADCAST noReaders}; StartWriter: INTERNAL PROC = INLINE { UNTIL readerCount = 0 DO WAIT noReaders ENDLOOP}; -- representation of an entry within the b-tree -- TreeData: TYPE = RECORD [ knownReg: BOOLEAN, type: ProtocolDefs.RNameType, stamp: BodyDefs.Timestamp, object: HeapDefs.ObjectNumber]; Lookup: PUBLIC PROCEDURE [ name: BodyDefs.RName, reason: RegBTreeDefs.LookupReason] RETURNS [info: RegistryObject] = BEGIN -- returns a reader to ensure the object doesn't go away -- -- if you're doing an update, the reader will be closed by Insert -- objsize: CARDINAL = SIZE[TreeData]; treeInfo: TreeData; StartReader[]; -- try cache first -- [treeInfo.type, treeInfo.stamp, treeInfo.object] ← RegCacheDefs.ReadName[ name]; IF treeInfo.type = notFound THEN BEGIN length: CARDINAL; length ← BTreeDefs.Lookup[ tree, RNameDesc[name], DESCRIPTOR[@treeInfo, objsize]]; IF length # objsize THEN treeInfo.type ← notFound ELSE RegCacheDefs.AddName[ name, treeInfo.knownReg, treeInfo.type, treeInfo.stamp, treeInfo.object]; END; info ← IF treeInfo.type # notFound THEN [ type: treeInfo.type, stamp: treeInfo.stamp, reader: IF (SELECT reason FROM readNone => FALSE, readIndividual => treeInfo.type = individual, readGroup => treeInfo.type = group, readEither => treeInfo.type # dead, readAny => TRUE, ENDCASE => ERROR) THEN HeapDefs.HeapStartRead[treeInfo.object] ELSE NIL] ELSE [type: notFound, stamp: BodyDefs.oldestTime, reader: NIL]; EndReader[]; END; OldReaderNeeded: ERROR = CODE; UpdateFailed: PUBLIC ERROR [info: RegistryObject] = CODE; Insert: PUBLIC ENTRY PROCEDURE [ name: BodyDefs.RName, type: ProtocolDefs.RNameType, stamp: POINTER TO BodyDefs.Timestamp, writer: HeapDefs.WriterHandle, info: POINTER TO RegistryObject] = BEGIN InsertinBTree: INTERNAL PROCEDURE [number: HeapDefs.ObjectNumber] = BEGIN value ← [knownReg: FALSE, type: type, stamp: stamp↑, object: number]; ObjectDirDefs.UseObject[number]; WriteToTreeAndCache[name, @value]; END; value: TreeData; valuesize: CARDINAL = SIZE[TreeData]; valuedesc: DESCRIPTOR FOR ARRAY OF WORD = DESCRIPTOR[@value, valuesize]; namedesc: LONG DESCRIPTOR FOR ARRAY OF WORD = RNameDesc[name]; StartWriter[]; IF info # NIL AND info.type # notFound AND info.reader = NIL THEN ERROR OldReaderNeeded[]; -- here, b-tree contains either the old entry or a new one with different -- object number, so we can end the reader on the old object -- BEGIN ENABLE UNWIND => IF writer # NIL THEN HeapDefs.HeapAbandonWrite[writer]; oldObj: HeapDefs.ObjectNumber; IF info # NIL AND info.reader # NIL THEN { oldObj ← HeapDefs.GetReaderObject[info.reader]; HeapDefs.HeapEndRead[info.reader]}; IF BTreeDefs.Lookup[tree, namedesc, valuedesc] = valuesize THEN IF info # NIL AND (info.type = notFound OR oldObj # value.object) THEN ERROR UpdateFailed[ [ type: value.type, stamp: value.stamp, reader: HeapDefs.HeapStartRead[value.object]]] ELSE BEGIN BTreeDefs.Delete[tree, namedesc]; RegCacheDefs.FlushName[name]; ObjectDirDefs.FreeObject[value.object]; END ELSE IF info # NIL AND info.type # notFound THEN ERROR UpdateFailed[ [type: notFound, stamp: BodyDefs.oldestTime, reader: NIL]] ELSE NULL; END --ENABLE-- ; IF type # notFound THEN BEGIN IF writer = NIL THEN ERROR; HeapDefs.HeapEndWrite[writer, InsertinBTree]; END; END; BadKnownRegCall: ERROR = CODE; KnownRegistry: PUBLIC ENTRY PROC [name: BodyDefs.RName, yes: BOOLEAN] = BEGIN value: TreeData; valuesize: CARDINAL = SIZE[TreeData]; valuedesc: DESCRIPTOR FOR ARRAY OF WORD = DESCRIPTOR[@value, valuesize]; namedesc: LONG DESCRIPTOR FOR ARRAY OF WORD = RNameDesc[name]; StartWriter[]; IF BTreeDefs.Lookup[tree, namedesc, valuedesc] = valuesize THEN BEGIN IF value.type # group THEN ERROR BadKnownRegCall[]; value.knownReg ← yes; WriteToTreeAndCache[name, @value]; END ELSE IF yes THEN ERROR BadKnownRegCall[]; END; TestKnownReg: PUBLIC PROC [name: BodyDefs.RName] RETURNS [state: RegBTreeDefs.RegState] = BEGIN regName: BodyDefs.RName = [BodyDefs.maxRNameLength]; gv: STRING = ".gv"L; value: TreeData; valuesize: CARDINAL = SIZE[TreeData]; valuedesc: DESCRIPTOR FOR ARRAY OF WORD = DESCRIPTOR[@value, valuesize]; IF name.length > regName.maxlength THEN RETURN[bad]; FOR i: CARDINAL DECREASING IN [0..name.length) DO IF name[i] = '. THEN BEGIN FOR j: CARDINAL IN [i + 1..name.length) DO regName[regName.length] ← name[j]; regName.length ← regName.length + 1; ENDLOOP; EXIT END; REPEAT FINISHED => RETURN[bad] ENDLOOP; IF regName.length + gv.length > regName.maxlength THEN RETURN[bad]; String.AppendString[regName, gv]; StartReader[]; state ← RegCacheDefs.TestKnownReg[regName]; IF state = bad THEN BEGIN IF BTreeDefs.Lookup[tree, RNameDesc[regName], valuedesc] = valuesize THEN state ← IF value.knownReg THEN yes ELSE no ELSE state ← bad; END; EndReader[]; END; MarkKnown: PUBLIC SIGNAL = CODE; EnumerateTree: PUBLIC PROCEDURE [ type: ProtocolDefs.RNameType, action: PROCEDURE [BodyDefs.RName]] = BEGIN InnerAction: PROCEDURE [name: BodyDefs.RName, value: LONG POINTER TO TreeData] RETURNS [dirty: BOOLEAN] = BEGIN dirty ← FALSE; IF value.type = type OR type = notFound -- ! -- THEN action[ name ! MarkKnown => { value.knownReg ← TRUE; dirty ← TRUE; RegCacheDefs.AddName[ name, value.knownReg, value.type, value.stamp, value.object]; RESUME }]; END; EnumerateAllTree[InnerAction]; END; EnumerateAllTree: PROC [ action: PROCEDURE [BodyDefs.RName, LONG POINTER TO TreeData] RETURNS [dirty: BOOLEAN]] = BEGIN TreeAction: BTreeDefs.Call = BEGIN rName: BodyDefs.RName = [BodyDefs.maxRNameLength]; value: LONG POINTER TO TreeData = LOOPHOLE[BASE[v]]; IF LENGTH[k] = 0 THEN {dirty ← FALSE; more ← TRUE; RETURN}; rName.length ← 2 * LENGTH[k]; Inline.LongCOPY[from: BASE[k], to: @(rName.text), nwords: LENGTH[k]]; IF rName.length > 0 AND rName[rName.length - 1] = '@ THEN rName.length ← rName.length - 1 -- undo padding kludge -- ; dirty ← action[rName, value]; more ← TRUE; END; StartReader[]; BTreeDefs.EnumerateFrom[ tree, DESCRIPTOR[NIL, 0], TreeAction ! UNWIND => EndReader[]]; EndReader[]; END; KeepObject: PUBLIC ENTRY PROCEDURE [ name: BodyDefs.RName, type: ProtocolDefs.RNameType, stamp: POINTER TO BodyDefs.Timestamp, number: HeapDefs.ObjectNumber] = -- This is called only during the restart sequence -- BEGIN value: TreeData; valuesize: CARDINAL = SIZE[TreeData]; valuedesc: DESCRIPTOR FOR ARRAY OF WORD = DESCRIPTOR[ LOOPHOLE[@value, POINTER], valuesize]; namedesc: LONG DESCRIPTOR FOR ARRAY OF WORD = RNameDesc[name]; StartWriter[]; IF BTreeDefs.Lookup[tree, namedesc, valuedesc] = valuesize THEN BEGIN BTreeDefs.Delete[tree, namedesc]; ObjectDirDefs.FreeObject[value.object]; END; value ← [knownReg: FALSE, type: type, stamp: stamp↑, object: number]; ObjectDirDefs.RestartObject[number]; WriteToTreeAndCache[name, @value]; END; WriteToTreeAndCache: INTERNAL PROC [ name: BodyDefs.RName, value: POINTER TO TreeData] = BEGIN BTreeDefs.Insert[tree, RNameDesc[name], DESCRIPTOR[value, SIZE[TreeData]]]; RegCacheDefs.AddName[ name, value.knownReg, value.type, value.stamp, value.object]; END; -- BTree purger process -- RegPurger: PUBLIC PROC = {RegPurgerProcess ← FORK RegPurgerMain[]}; RegPurgerProcess: PROCESS; ageLimit: CARDINAL ← 14 -- days -- ; RegPurgerMain: PROC = BEGIN DO limit: BodyDefs.Timestamp; writer: HeapDefs.WriterHandle ← NIL; RegPurgerAction: PROC [name: BodyDefs.RName, value: LONG POINTER TO TreeData] RETURNS [BOOLEAN] = BEGIN reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[value.object]; IF RegServerDefs.ConsiderPurging[[value.type, value.stamp, reader], limit] THEN BEGIN IF writer = NIL THEN writer ← HeapDefs.HeapStartWrite[temp]; HeapDefs.HeapWriteRName[writer, name]; END; HeapDefs.HeapEndRead[reader]; RETURN[FALSE] END; RegPurgerCleanup: PROC [obj: HeapDefs.ObjectNumber] = BEGIN reader: HeapDefs.ReaderHandle = HeapDefs.HeapStartRead[obj]; DO name: STRING = [BodyDefs.maxRNameLength + 21]; ended: BOOLEAN = HeapDefs.HeapReadRName[reader, name]; [] ← PossiblyPurge[name, limit]; Process.Pause[1]; --please can I have a process scheduler -- IF ended THEN EXIT; ENDLOOP; HeapDefs.HeapEndRead[reader]; END; PolicyDefs.RegPurgerPause[]; PolicyDefs.WaitOperation[regPurger]; limit ← RegistryDefs.MakeTimestamp[]; limit.time ← limit.time - ageLimit * 24 * LONG[60 * 60]; LogDefs.ShowLine["RegPurger running"L]; EnumerateAllTree[RegPurgerAction]; IF writer # NIL THEN HeapDefs.HeapEndWrite[writer, RegPurgerCleanup]; PolicyDefs.EndOperation[regPurger]; ENDLOOP; END; ImmediatePurge: PUBLIC PROC [name: BodyDefs.RName] RETURNS [done: BOOLEAN] = -- Provided for the viticulturists' entrance -- {RETURN[PossiblyPurge[name, RegistryDefs.MakeTimestamp[]]]}; PossiblyPurge: ENTRY PROC [name: BodyDefs.RName, limit: BodyDefs.Timestamp] RETURNS [done: BOOLEAN] = BEGIN InsertInBTree: INTERNAL PROCEDURE [number: HeapDefs.ObjectNumber] = BEGIN value.object ← number; ObjectDirDefs.UseObject[number]; WriteToTreeAndCache[name, @value]; END; value: TreeData; valuesize: CARDINAL = SIZE[TreeData]; valuedesc: DESCRIPTOR FOR ARRAY OF WORD = DESCRIPTOR[ LOOPHOLE[@value, POINTER], valuesize]; namedesc: LONG DESCRIPTOR FOR ARRAY OF WORD = RNameDesc[name]; StartWriter[]; IF BTreeDefs.Lookup[tree, namedesc, valuedesc] = valuesize THEN BEGIN writer: HeapDefs.WriterHandle; [done, writer] ← RegServerDefs.ReallyPurge[ name, [value.type, value.stamp, HeapDefs.HeapStartRead[value.object]], limit]; IF done THEN BEGIN RegCacheDefs.FlushName[name]; IF writer = NIL THEN --purged dead entry-- BEGIN BTreeDefs.Delete[tree, namedesc]; ObjectDirDefs.FreeObject[value.object]; LogPurgeResult["Purged entry: "L, name]; END ELSE -- object revised by removing deleted data -- BEGIN ObjectDirDefs.FreeObject[value.object]; HeapDefs.HeapEndWrite[writer, InsertInBTree]; LogPurgeResult["Purged data: "L, name]; END; END ELSE LogPurgeResult["Purge abandoned: "L, name]; END; END; LogPurgeResult: PROC [result, name: LONG STRING] = BEGIN log: LONG STRING ← Heap.systemZone.NEW[StringBody[128]]; String.AppendString[log, result]; String.AppendString[log, name]; LogDefs.ShowLine[log]; Heap.systemZone.FREE[@log]; END; Init[]; END.