BTreeSimpleImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Taft, January 21, 1984 5:00:25 pm PST
Russ Atkinson (RRA) March 11, 1985 9:48:59 pm PST
DIRECTORY
Ascii USING [Lower],
BTree USING [Compare, CompareEntries, DeleteKey, Entry, EntryFromRecord, EntrySize, EnumerateEntries, GetState, Key, New, NewPathStk, NewRecordFromEntry, Open, PageNumber, PagePtr, PageStorage, PathStk, ReadEntry, ReferencePage, Relation, ReleasePage, SalvageEntries, SetState, SetUpdateInProgress, State, statePage, Tree, UpdateEntry, UpdateType, Validate],
BTreeSimple,
BTreeVM USING [CacheSize, FilePagesPerPage],
FS USING [BytesForPages, GetInfo, OpenFile, Read, SetByteCountAndCreatedTime, SetPageCount, WordsForPages, Write],
PrincOpsUtils USING [GetClockPulses, LongCopy],
Process USING [Detach],
Rope USING [InlineFlatten, TextRep],
SafeStorage USING [CantEstablishFinalization, EnableFinalization, EstablishFinalization, FinalizationQueue, FQNext, NewFQ, ReEstablishFinalization],
VM USING [AddressForPageNumber, Allocate, Free, Interval, PagesForWords, PageNumberForAddress];
BTreeSimpleImpl: CEDAR MONITOR -- the monitor protects the BTreeVM cache only
LOCKS h USING h: Handle
IMPORTS Ascii, BTree, FS, PrincOpsUtils, Process, Rope, SafeStorage, VM
EXPORTS BTreeSimple
SHARES Rope
= { OPEN BTreeSimple;
BTree representation
KeyFromEntry: PUBLIC UNSAFE PROC [entryKey: EntryKey] RETURNS [key: InternalKey] = UNCHECKED {
IF entryKey=NIL THEN RETURN[NIL];
key ← NEW[Rope.TextRep[entryKey.length] ← [text[length: entryKey.length, text: ]]];
PrincOpsUtils.LongCopy[to: BASE[DESCRIPTOR[key.text]], from: BASE[DESCRIPTOR[entryKey.text]], nwords: (entryKey.length+1)/2];
};
ValueFromEntry: PUBLIC UNSAFE PROC [entryValue: EntryValue] RETURNS [value: Value] = UNCHECKED {
IF entryValue=NIL THEN RETURN[NIL];
value ← NEW[ValueObject[entryValue.length]];
PrincOpsUtils.LongCopy[to: BASE[DESCRIPTOR[value.words]], from: BASE[DESCRIPTOR[entryValue.words]], nwords: entryValue.length];
};
DefaultCompare: Compare --[key: InternalKey, entryKey: EntryKey] RETURNS [Comparison]-- = UNCHECKED {
FOR i: CARDINAL IN [0 .. MIN[key.length, entryKey.length]) DO
kc: CHAR ← Ascii.Lower[key[i]];
ec: CHAR ← Ascii.Lower[entryKey[i]];
IF kc#ec THEN RETURN[IF kc<ec THEN less ELSE greater];
ENDLOOP;
RETURN[IF key.length=entryKey.length THEN equal
ELSE IF key.length<entryKey.length THEN less ELSE greater];
};
DefaultCompareEntries: CompareEntries --[entryKey1, entryKey2: EntryKey] RETURNS [Comparison]-- = UNCHECKED {
FOR i: CARDINAL IN [0 .. MIN[entryKey1.length, entryKey2.length]) DO
c1: CHAR ← Ascii.Lower[entryKey1[i]];
c2: CHAR ← Ascii.Lower[entryKey2[i]];
IF c1#c2 THEN RETURN[IF c1<c2 THEN less ELSE greater];
ENDLOOP;
RETURN[IF entryKey1.length=entryKey2.length THEN equal
ELSE IF entryKey1.length<entryKey2.length THEN less ELSE greater];
};
defaultCompareProcs: PUBLIC CompareProcs ← [compare: DefaultCompare, compareEntries: DefaultCompareEntries];
BTree global operations
New: PUBLIC PROC [compareProcs: CompareProcs ← defaultCompareProcs, initialState: State[closed..suspended] ← closed] RETURNS [tree: Tree] = {
RETURN[BTree.New[repPrim: [compare: LOOPHOLE[compareProcs.compare], compareEntries: LOOPHOLE[compareProcs.compareEntries], entrySize: EntrySize, entryFromRecord: EntryFromRecord, newRecordFromEntry: NewRecordFromEntry], storPrim: [referencePage: ReferencePage, releasePage: ReleasePage], minEntrySize: EntryKeyObject[1].SIZE+ValueObject[0].SIZE, initialState: initialState]];
};
Open: PUBLIC PROC [tree: Tree, file: FS.OpenFile, filePagesPerPage: FilePagesPerPage ← 1, cacheSize: CacheSize ← 25, initialize: BOOLEANFALSE, maintainRecomputableState: BOOLEANTRUE] = {
storage: Handle = CacheOpen[file: file, filePagesPerPage: filePagesPerPage, cacheSize: cacheSize];
tree.Open[storage: storage, pageSize: FS.WordsForPages[filePagesPerPage], initialize: initialize, maintainRecomputableState: maintainRecomputableState];
};
SetState: PUBLIC PROC [tree: Tree, state: State[closed..suspended]] = {
tree.SetState[state];
};
GetState: PUBLIC PROC [tree: Tree] RETURNS [state: State, entryCount: LONG CARDINAL, greatestPage: INT, depth: CARDINAL, file: FS.OpenFile] = {
storage: BTree.PageStorage;
[state: state, entryCount: entryCount, greatestPage: greatestPage, depth: depth, storage: storage] ← tree.GetState[];
file ← NARROW[storage, Handle].file;
};
GetStatistics: PUBLIC PROC [tree: Tree] RETURNS [hits, misses, reads, writes, cumChainLength, cumReadWriteTime: LONG CARDINAL] = {
RETURN GetCacheStats[NARROW[tree.GetState[].storage]];
};
Validate: PUBLIC PROC [tree: Tree] = {
tree.Validate[];
};
SetUpdateInProgress: PUBLIC PROC [tree: Tree, updateInProgress: BOOLEAN] = {
tree.SetUpdateInProgress[updateInProgress];
};
Higher-level BTree access
ReadRecord: PUBLIC PROC [tree: Tree, key: Key, relation: Relation ← equal, pathStk: PathStk ← NIL, useExistingPath: BOOLEANFALSE] RETURNS [actualKey: InternalKey, value: Value] = TRUSTED {
EntryProc: UNSAFE PROC [key: EntryKey, value: EntryValue] = UNCHECKED {
IF key=NIL THEN {actualKey ← NIL; returnValue ← NIL}
ELSE {actualKey ← KeyFromEntry[key]; returnValue ← ValueFromEntry[value]};
};
returnValue: Value;
ReadEntry[tree: tree, key: key, relation: relation, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc];
RETURN [actualKey, returnValue];
};
ReadValue: PUBLIC PROC [tree: Tree, key: Key, relation: Relation ← equal, pathStk: PathStk ← NIL, useExistingPath: BOOLEANFALSE] RETURNS [value: Value] = TRUSTED {
EntryProc: UNSAFE PROC [key: EntryKey, value: EntryValue] = UNCHECKED {
returnValue ← (IF value=NIL THEN NIL ELSE ValueFromEntry[value]);
};
returnValue: Value;
ReadEntry[tree: tree, key: key, relation: relation, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc];
RETURN [returnValue];
};
EnumerateRecords: PUBLIC PROC [tree: Tree, key: Key, relation: Relation ← equal, pathStk: PathStk ← NIL, useExistingPath: BOOLEANFALSE, Proc: PROC [key: InternalKey, value: Value] RETURNS [continue: BOOLEAN]] RETURNS [exhausted: BOOLEAN] = TRUSTED {
EntryProc: UNSAFE PROC [key: EntryKey, value: EntryValue] RETURNS [continue: BOOLEAN] = UNCHECKED {
RETURN [Proc[key: KeyFromEntry[key], value: ValueFromEntry[value]]] };
exhausted ← EnumerateEntries[tree: tree, key: key, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc];
};
UpdateRecord: PUBLIC PROC [tree: Tree, key: Key, value: Value, pathStk: PathStk ← NIL, useExistingPath: BOOLEANFALSE, updateType: UpdateType ← insertOrReplace] = TRUSTED {
ProduceEntry: UNSAFE PROC [value: EntryValue] = UNCHECKED {
PrincOpsUtils.LongCopy[to: value, from: suppliedValue, nwords: ValueObject[suppliedValue.length].SIZE]};
suppliedValue: LONG POINTER TO ValueObject = LOOPHOLE[value];
UpdateEntry[tree: tree, key: key, pathStk: pathStk, useExistingPath: useExistingPath, valueLength: suppliedValue.length, Proc: ProduceEntry, updateType: updateType];
};
DeleteKey: PUBLIC PROC [tree: Tree, key: Key, pathStk: PathStk ← NIL, useExistingPath: BOOLEANFALSE] RETURNS [found: BOOLEAN] = {
RETURN[tree.DeleteKey[key: key.InlineFlatten[], pathStk: pathStk, useExistingPath: useExistingPath]];
};
Lower-level BTree access
ReadEntry: PUBLIC UNSAFE PROC [tree: Tree, key: Key, relation: Relation ← equal, pathStk: PathStk ← NIL, useExistingPath: BOOLEANFALSE, Proc: UNSAFE PROC [key: EntryKey, value: EntryValue]] = UNCHECKED {
EntryProc: UNSAFE PROC [entry: BTree.Entry] = UNCHECKED {
IF entry=NIL THEN Proc[NIL, NIL]
ELSE Proc[entry, entry+EntryKeyObject[LOOPHOLE[entry, EntryKey].length].SIZE];
};
tree.ReadEntry[key: key.InlineFlatten[], relation: relation, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc];
};
EnumerateEntries: PUBLIC UNSAFE PROC [tree: Tree, key: Key, relation: Relation ← equal, pathStk: PathStk ← NIL, useExistingPath: BOOLEANFALSE, Proc: UNSAFE PROC [key: EntryKey, value: EntryValue] RETURNS [continue: BOOLEAN]] RETURNS [exhausted: BOOLEAN] = UNCHECKED {
EntryProc: UNSAFE PROC [entry: BTree.Entry] RETURNS [continue: BOOLEAN] = UNCHECKED {
RETURN[Proc[entry, entry+EntryKeyObject[LOOPHOLE[entry, EntryKey].length].SIZE]]};
exhausted ← tree.EnumerateEntries[key: key.InlineFlatten[], relation: relation, pathStk: pathStk, useExistingPath: useExistingPath, Proc: EntryProc];
};
UpdateEntry: PUBLIC UNSAFE PROC [tree: Tree, key: Key, pathStk: PathStk ← NIL, useExistingPath: BOOLEANFALSE, valueLength: CARDINAL, Proc: UNSAFE PROC [value: EntryValue], updateType: UpdateType ← insertOrReplace] = UNCHECKED {
EntryProc: UNSAFE PROC [entry: BTree.Entry] = UNCHECKED {
entryKey: EntryKey = LOOPHOLE[entry];
entryValue: EntryValue = LOOPHOLE[entry+entryKeySize];
LOOPHOLE[entryKey, LONG POINTER TO CARDINAL]^ ← internalKey.length; -- entryKey.length ← internalKey.length
PrincOpsUtils.LongCopy[to: BASE[DESCRIPTOR[entryKey.text]], from: BASE[DESCRIPTOR[internalKey.text]], nwords: (internalKey.length+1)/2];
Proc[entryValue];
LOOPHOLE[entryValue, LONG POINTER TO CARDINAL]^ ← valueLength; -- entryValue.length ← valueLength
};
internalKey: InternalKey = key.InlineFlatten[];
entryKeySize: CARDINAL = EntryKeyObject[internalKey.length].SIZE;
tree.UpdateEntry[key: internalKey, pathStk: pathStk, useExistingPath: useExistingPath, words: entryKeySize+ValueObject[valueLength].SIZE, Proc: EntryProc, updateType: updateType];
};
SalvageEntries: PUBLIC UNSAFE PROC [tree: Tree, Proc: UNSAFE PROC [key: EntryKey, value: EntryValue] RETURNS [continue: BOOLEAN]] RETURNS [exhausted: BOOLEAN] = UNCHECKED {
EntryProc: UNSAFE PROC [entry: BTree.Entry] RETURNS [continue: BOOLEAN] = UNCHECKED {
RETURN[Proc[entry, entry+EntryKeyObject[LOOPHOLE[entry, EntryKey].length].SIZE]]};
exhausted ← tree.SalvageEntries[Proc: EntryProc];
};
PathStk hints
NewPathStk: PUBLIC PROC RETURNS [pathStk: PathStk] = {
RETURN[BTree.NewPathStk[]];
};
Private
The following are the procedures passed to the BTree package as the RepresentationPrimitives (in addition to the client's Compare and CompareEntries procedures).
EntrySize: BTree.EntrySize --[entry: BTree.Entry] RETURNS [words: BTree.EntSize]-- = UNCHECKED {
words ← EntryKeyObject[LOOPHOLE[entry, EntryKey].length].SIZE;
words ← words + ValueObject[LOOPHOLE[entry+words, EntryValue].length].SIZE;
};
EntryFromRecord: BTree.EntryFromRecord --[record: BTree.Record] RETURNS [entry: BTree.Entry]-- = UNCHECKED {
ERROR Bug[cantGetHere]; -- we never call the BTree procedures that traffic in Records
};
NewRecordFromEntry: BTree.NewRecordFromEntry --[entry: BTree.Entry] RETURNS [record: BTree.Record]-- = UNCHECKED {
ERROR Bug[cantGetHere]; -- we never call the BTree procedures that traffic in Records
};
Storage primitives and BTree cache
This is almost identical to BTreeVMImpl, and perhaps someday most of the code should be shared (in particular, everything to do with VM cache management as opposed to file I/O). The main difference is that the BTree is designated by an FS.OpenFile rather than a File.Handle.
CacheIndex: TYPE = [0 .. LAST[BTreeVM.CacheSize]];
noIndex: CacheIndex = LAST[CacheIndex];
Handle: TYPE = REF VMObject;
VMObject: TYPE = MONITORED RECORD [
file: FS.OpenFile,
filePages: INT,
filePagesPerPage: BTreeVM.FilePagesPerPage,
vmPagesPerPage: CARDINAL,
rover: CacheIndex,
cache: REF Cache,
hits, misses, reads, writes, cumChainLength, cumReadWriteTime: LONG CARDINAL ← 0,
hashTable: PACKED SEQUENCE count: CARDINAL OF CacheIndex
];
Cache: TYPE = RECORD [PACKED SEQUENCE count: CacheIndex OF CacheEntry];
CacheEntry: TYPE = RECORD [
pNum: BTree.PageNumber,
ptr: BTree.PagePtr,
referenceCount: [0..256),
hash: CacheIndex,
empty, recentlyUsed, dirty: BOOLEAN
];
CacheOpen: PROC [file: FS.OpenFile, filePagesPerPage: BTreeVM.FilePagesPerPage,
cacheSize: BTreeVM.CacheSize] RETURNS [Handle] = {
h: Handle ← NEW[VMObject[2*cacheSize+1]];
h.file ← file;
h.filePages ← 0; -- indicates that we don't know --
h.filePagesPerPage ← filePagesPerPage;
h.vmPagesPerPage ← VM.PagesForWords[FS.WordsForPages[filePagesPerPage]];
h.rover ← 0;
h.cache ← NEW[Cache[cacheSize]];
FOR i: CacheIndex IN [0 .. h.cache.count) DO
h.cache[i].ptr ← NIL;
h.cache[i].empty ← TRUE;
ENDLOOP;
FOR c: CARDINAL IN [0..h.count) DO
h.hashTable[c] ← noIndex;
ENDLOOP;
SafeStorage.EnableFinalization[h];
RETURN [h];
};
ReferencePage: BTree.ReferencePage --[storage: PageStorage, number: PageNumber, type: ReferenceType] RETURNS [ptr: PagePtr]-- = {
InnerReference: ENTRY PROC [h: Handle] = INLINE {
hash: CARDINAL = HashIndex[h, number];
i: CacheIndex;
FOR i ← h.hashTable[hash], h.cache[i].hash UNTIL i = noIndex DO
h.cumChainLength ← h.cumChainLength+1;
IF h.cache[i].pNum = number THEN { h.hits ← h.hits+1; EXIT };
REPEAT FINISHED => { -- not found --
h.misses ← h.misses+1;
i ← GetEmptyCacheEntry[h];
h.cache[i].pNum ← number;
h.cache[i].empty ← FALSE;
IF type # new THEN LoadCachePage[h, i];
h.cache[i].hash ← h.hashTable[hash];
h.hashTable[hash] ← i;
};
ENDLOOP;
h.cache[i].recentlyUsed ← TRUE;
h.cache[i].referenceCount ← h.cache[i].referenceCount + 1;
IF type # read THEN h.cache[i].dirty ← TRUE;
IF type = new THEN ExtendFile[h, number];
ptr ← h.cache[i].ptr;
};
InnerReference[NARROW[storage]];
};
ReleasePage: BTree.ReleasePage --[storage: PageStorage, number: PageNumber, update: UpdateState]-- = {
InnerRelease: ENTRY PROC [h: Handle] = INLINE {
i: CacheIndex;
FOR i ← h.hashTable[HashIndex[h, number]], h.cache[i].hash UNTIL i = noIndex DO
IF h.cache[i].pNum = number THEN EXIT;
REPEAT FINISHED => ERROR Bug[pageNotInUse];
ENDLOOP;
IF h.cache[i].referenceCount = 0
THEN ERROR Bug[pageNotInUse]
ELSE h.cache[i].referenceCount ← h.cache[i].referenceCount - 1;
IF update # unchanged THEN {
IF number = BTree.statePage
THEN { -- is the statePage; can be either startOfUpdate or endOfUpdate
IF update = startOfUpdate
THEN StoreCachePage[h, i] -- only this page is dirty, so just write it
ELSE WriteDirtyPagesWithStatePageLast[h]; -- many pages possibly dirty
}
ELSE StoreCachePage[h, i]; -- is not the statePage; must be endOfUpdate; only page i is dirty
};
};
InnerRelease[NARROW[storage]];
};
GetCacheStats: ENTRY PROC [h: Handle] RETURNS [hits, misses, reads, writes, cumChainLength, cumReadWriteTime: LONG CARDINAL] = {
RETURN [hits: h.hits, misses: h.misses, reads: h.reads, writes: h.writes, cumChainLength: h.cumChainLength, cumReadWriteTime: h.cumReadWriteTime];
};
FlushCache: ENTRY PROC [h: Handle] = {
WriteDirtyPagesWithStatePageLast[h];
FOR i: CacheIndex IN [0 .. h.cache.count) DO
h.cache[i].empty ← TRUE;
ENDLOOP;
FOR c: CARDINAL IN [0..h.count) DO
h.hashTable[c] ← noIndex;
ENDLOOP;
};
FreeBuffers: ENTRY PROC [h: Handle] = {
FOR i: CacheIndex IN [0 .. h.cache.count) DO
IF h.cache[i].ptr#NIL THEN {ReleaseSpace[h, h.cache[i].ptr]; h.cache[i].ptr ← NIL};
h.cache[i].empty ← TRUE;
ENDLOOP;
FOR c: CARDINAL IN [0..h.count) DO
h.hashTable[c] ← noIndex;
ENDLOOP;
};
HashIndex: INTERNAL PROC [h: Handle, pNum: BTree.PageNumber]
RETURNS [CARDINAL] = INLINE {
RETURN [pNum MOD h.count];
};
WriteDirtyPagesWithStatePageLast: INTERNAL PROC [h: Handle] = {
statePageIndex: CacheIndex ← noIndex;
FOR i: CacheIndex IN [0 .. h.cache.count) DO
IF NOT h.cache[i].empty AND h.cache[i].dirty
THEN {
IF h.cache[i].pNum = BTree.statePage
THEN statePageIndex ← i
ELSE StoreCachePage[h, i];
};
ENDLOOP;
IF statePageIndex # noIndex THEN StoreCachePage[h, statePageIndex];
};
GetEmptyCacheEntry: INTERNAL PROC [h: Handle] RETURNS [i: CacheIndex] = {
The cache entry that is returned is empty; it has a valid space and a useCount of zero. It may be assocaited with a page by setting pNum.
FOR cntr: CARDINAL IN [0..2*h.cache.count) DO
i ← (h.rover + cntr) MOD h.cache.count;
SELECT TRUE FROM
h.cache[i].empty => {
empty entry
IF h.cache[i].ptr = NIL
THEN h.cache[i].ptr ← GetSpace[h];
h.cache[i].recentlyUsed ← FALSE;
h.cache[i].dirty ← FALSE;
h.cache[i].referenceCount ← 0;
EXIT;
};
h.cache[i].referenceCount = 0 => {
not currently being used
IF h.cache[i].recentlyUsed
THEN h.cache[i].recentlyUsed ← FALSE
ELSE { -- not even recently used
IF h.cache[i].dirty THEN
IF h.cache[i].pNum = BTree.statePage
THEN LOOP -- never force out the statePage
ELSE StoreCachePage[h, i];
EXIT;
};
};
ENDCASE;
REPEAT FINISHED => ERROR Bug[noAvailableCacheEntries];
ENDLOOP;
IF NOT h.cache[i].empty THEN {
hx: CARDINAL = HashIndex[h, h.cache[i].pNum];
IF h.hashTable[hx] = i
THEN h.hashTable[hx] ← h.cache[i].hash
ELSE FOR j: CacheIndex ← h.hashTable[hx], h.cache[j].hash UNTIL j = noIndex DO
IF h.cache[j].hash = i THEN {h.cache[j].hash ← h.cache[i].hash; EXIT};
REPEAT FINISHED => ERROR Bug[cacheEntryNotInHashTable];
ENDLOOP;
h.cache[i].empty ← TRUE;
};
h.rover ← (i+1) MOD h.cache.count;
};
ExtendFile: INTERNAL PROC [h: Handle, n: BTree.PageNumber] = {
fileSizeForNPlus1: INT = (n+1)*h.filePagesPerPage;
IF h.filePages = 0 THEN h.filePages ← h.file.GetInfo[].pages;
IF h.filePages < fileSizeForNPlus1
THEN {
h.filePages ← fileSizeForNPlus1 + 8*h.filePagesPerPage;
h.file.SetPageCount[h.filePages];
h.file.SetByteCountAndCreatedTime[bytes: FS.BytesForPages[h.filePages]];
};
};
LoadCachePage: INTERNAL PROC [h: Handle, i: CacheIndex] = {
then: LONG CARDINAL = PrincOpsUtils.GetClockPulses[];
TRUSTED {h.file.Read[from: h.cache[i].pNum*h.filePagesPerPage, nPages: h.filePagesPerPage, to: h.cache[i].ptr]};
h.cumReadWriteTime ← h.cumReadWriteTime + (PrincOpsUtils.GetClockPulses[]-then);
h.reads ← h.reads+1;
};
StoreCachePage: INTERNAL PROC [h: Handle, i: CacheIndex] = {
then: LONG CARDINAL = PrincOpsUtils.GetClockPulses[];
TRUSTED {h.file.Write[to: h.cache[i].pNum*h.filePagesPerPage, nPages: h.filePagesPerPage, from: h.cache[i].ptr]};
h.cumReadWriteTime ← h.cumReadWriteTime + (PrincOpsUtils.GetClockPulses[]-then);
h.writes ← h.writes+1;
h.cache[i].dirty ← FALSE;
};
GetSpace: INTERNAL PROC [h: Handle] RETURNS [BTree.PagePtr] = TRUSTED {
interval: VM.Interval = VM.Allocate[count: h.vmPagesPerPage];
RETURN [VM.AddressForPageNumber[interval.page]];
};
ReleaseSpace: INTERNAL PROC [h: Handle, ptr: BTree.PagePtr] = TRUSTED {
VM.Free[[page: VM.PageNumberForAddress[ptr], count: h.vmPagesPerPage]];
};
Errors
Bug: ERROR [type: BugType] = CODE;
BugType: TYPE = {cacheEntryNotInHashTable, cantGetHere, noAvailableCacheEntries, pageNotInUse};
Initialization and finalization
Initialize: PROC = {
finalizationQueue ← SafeStorage.NewFQ[];
SafeStorage.EstablishFinalization[type: VMObject.CODE, npr: 0, fq: finalizationQueue
! SafeStorage.CantEstablishFinalization =>
SafeStorage.ReEstablishFinalization[type: VMObject.CODE, npr: 0, fq: finalizationQueue]];
TRUSTED {Process.Detach[FORK FinalizationProcess]};
};
FinalizationProcess: PROC = {
DO
h: Handle ← NARROW [SafeStorage.FQNext[finalizationQueue]];
FreeBuffers[h];
h ← NIL;
ENDLOOP;
};
finalizationQueue: SafeStorage.FinalizationQueue;
Initialize[];
}.