-- Transport Mechanism Registration Server - Operations on the B-Tree -- -- [Indigo]<Grapevine>MS>RegBTree.mesa -- Randy Gobbel, 19-May-81 12:12:10 -- Andrew Birrell, 4-Nov-81 16:19:48 DIRECTORY BodyDefs USING[ maxRNameLength, oldestTime, RName, Timestamp ], BTreeDefs, EnquiryDefs USING[], HeapDefs USING[ GetReaderObject, HeapAbandonWrite, HeapEndWrite, HeapEndRead, HeapReadRName, HeapStartRead, HeapStartWrite, HeapWriteRName, ObjectNumber, ReaderHandle, WriterHandle], Inline USING[ COPY ], LogDefs USING[ WriteLogEntry ], 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, 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: POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[a]]; bC: 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: POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[a]]; bC: 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[ 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: 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: 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: 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, POINTER TO TreeData]RETURNS[dirty:BOOLEAN] ] = BEGIN TreeAction: BTreeDefs.Call = BEGIN rName: BodyDefs.RName = [BodyDefs.maxRNameLength]; value: POINTER TO TreeData = LOOPHOLE[BASE[v]]; IF LENGTH[k] = 0 THEN { dirty←FALSE; more←TRUE; RETURN }; rName.length ← 2*LENGTH[k]; Inline.COPY[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: 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: 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.WriteLogEntry["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: 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]; String.AppendString[name, ": purged dead entry"L]; END ELSE -- object revised by removing deleted data -- BEGIN ObjectDirDefs.FreeObject[value.object]; HeapDefs.HeapEndWrite[writer, InsertInBTree]; String.AppendString[name, ": purged deleted data"L]; END; END ELSE String.AppendString[name, ": purge abandoned"L]; LogDefs.WriteLogEntry[name]; END; END; Init[]; END.