YggVolatilizeImpl.mesa
Copyright Ó 1988 by Xerox Corporation. All rights reserved.
Bob Hagmann May 6, 1988 11:49:15 am PDT
This module converts documents from stable to volatile forms.
DIRECTORY
Basics USING [charsPerWord, UnsafeBlock],
IO USING [EndOfStream, GetChar, GetLength, STREAM, UnsafeGetBlock],
RefText USING [ObtainScratch, ReleaseScratch],
Rope USING [Concat, Compare, FromRefText, ROPE],
SymTab USING [Create, Delete, EachPairAction, Fetch, GetSize, Insert, Pairs, Ref],
YggDID USING [DID, StreamsForDID],
YggDummyProcess USING [Detach, MsecToTicks, Pause, SetTimeout, Ticks],
YggEnvironment USING [AccessRights, LockMode, LockOption, TransID],
YggInternal USING [OpenDoc],
YggLock USING [Failed, MakeLockID, Set],
YggMonitoringLog USING [LockConflictInfo, notice],
YggOpenDoc USING [Register],
YggRep USING [AccurateGMT, AccurateGMTRep, AccurateGMTRepByteSize, AttributePreambleByte, AttributeValue, Bits, BitsRep, date, float, int, rope, shortRope, TypedPrimitiveElement, unknown, uninterpretedBytes, VDoc, VDocRep],
YggTransContext USING [EstablishTransactionContext, TransactionWork],
YggVolatileObjectCache;
YggVolatilizeImpl: CEDAR MONITOR
IMPORTS IO, RefText, Rope, SymTab, YggDID, YggDummyProcess, YggLock, YggOpenDoc, YggMonitoringLog, YggTransContext
EXPORTS YggDID, YggRep, YggVolatileObjectCache
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
scratchBuffSize: INT = 128;
didCache: SymTab.Ref ← NIL;
desiredSizeOfDIDCache: INT ← 50;
cacheEntry: TYPE ~ REF cacheEntryRep;
cacheEntryRep: TYPE ~ RECORD [
users: INT ← 0,
document: YggRep.VDoc ← NIL
];
myCondition: CONDITION;
DID: PUBLIC TYPE ~ REF DIDRep;
DIDRep: PUBLIC TYPE ~ ROPE;
For Phase 0, the DIDRep is just a string that names a directory (without the trailing /).
Exported conversion procedures
VolatizeFromDID: PUBLIC PROC [transID: YggEnvironment.TransID, did: YggDID.DID,
access: YggEnvironment.AccessRights, lock: YggEnvironment.LockOption]
RETURNS [document: YggRep.VDoc ← NIL] ~ {
Given a DID, return the volatile form of the document it refers to.
readInt: PROC RETURNS [int: INT] = {
read an int from the attributes stream
charsRead: INT ← 0;
rint: INT;
TRUSTED {charsRead ← IO.UnsafeGetBlock[attributes, [LOOPHOLE[@rint], 0, Basics.charsPerWord]];};
IF charsRead # Basics.charsPerWord THEN ERROR;
RETURN[rint];
};
readString: PROC [extraChars: INT] RETURNS [string: ROPENIL]= {
read a null terminated string from the attributes stream
nullFound: BOOLFALSE;
lenInScratch: INT ← 0;
numberToFetch: INT ← 3;
fourChars: PACKED ARRAY [0..Basics.charsPerWord) OF CHAR;
scratch: REF TEXT = RefText.ObtainScratch[scratchBuffSize];
UNTIL nullFound DO
charsRead: INT ← 0;
TRUSTED {charsRead ← IO.UnsafeGetBlock[attributes, [LOOPHOLE[@fourChars], 0 , numberToFetch]];};
IF charsRead # numberToFetch THEN ERROR;
FOR charNo: INT IN [0..numberToFetch) DO
IF fourChars[charNo] = 0C THEN {nullFound ← TRUE; EXIT};
scratch[lenInScratch] ← fourChars[charNo];
lenInScratch ← lenInScratch + 1;
IF lenInScratch = scratchBuffSize THEN {
scratch.length ← lenInScratch;
string ← Rope.Concat[string, Rope.FromRefText[s: scratch, start: 0, len: lenInScratch]];
lenInScratch ← 0;
};
ENDLOOP;
numberToFetch ← 4;
ENDLOOP;
IF lenInScratch # 0 THEN {
scratch.length ← lenInScratch;
string ← Rope.Concat[string, Rope.FromRefText[s: scratch, start: 0, len: lenInScratch]];
};
RefText.ReleaseScratch[scratch];
};
contents, attributes, links: IO.STREAMNIL;
contentsDocType: INT;
innerVolatizeFromDID: YggTransContext.TransactionWork = {
PROCEDURE [trans: YggTransactionMap.TransHandle];
newLockMode: YggEnvironment.LockMode;
openDoc: YggInternal.OpenDoc;
openDoc ← YggOpenDoc.Register[trans: trans, did: did];
newLockMode ← YggLock.Set[
trans: trans,
lock: YggLock.MakeLockID[did],
mode: lock.mode,
wait: lock.ifConflict=wait
! YggLock.Failed => {  -- Log the error and let the error propagate.
logProc: PROC [YggMonitoringLog.LockConflictInfo];
IF (logProc ← YggMonitoringLog.notice.lockConflict) # NIL THEN
logProc[[
what: why,   -- (conflict or timeout)
where: "YggVolatilizeImpl.VolatizeFromDID",
transID: transID,
mode: lock.mode,
specifics: entireFile[""],
message: ""
]]
}
];
IF (document ← LookupDIDInCache[did]) # NIL THEN RETURN;
ReserveDIDInCache[did];
[contents, attributes, links] ← YggDID.StreamsForDID[did];
document ← NEW[YggRep.VDocRep];
document.did ← did;
IF attributes # NIL THEN {
doneWithAttributes: BOOLFALSE;
contentsDocType ← readInt[];
DO -- for each attribute
attributeName: ROPENIL; -- attribute name
attributeValue: LIST OF YggRep.AttributeValue ← NIL; -- attribute name
numberOfAttributeValues: INT ← 1;
preampleByte: CHAR;
preample: YggRep.AttributePreambleByte;
fieldDocType: INT;
TRUSTED {preampleByte ← IO.GetChar[attributes ! IO.EndOfStream => {
doneWithAttributes ← TRUE;
CONTINUE;
};
];}; -- @preample points to preample[0]
IF doneWithAttributes THEN EXIT;
preample ← LOOPHOLE[preampleByte];
fieldDocType ← SELECT preample.typeCode FROM
integer => YggRep.int,
ropeLarge => YggRep.rope,
ropeShort => YggRep.shortRope,
float => YggRep.float,
date => YggRep.date,
uninterpretedBytes => YggRep.uninterpretedBytes,
ENDCASE => YggRep.unknown;
attributeName ← readString[1]; -- read attribute name
IF ~preample.singletonAttribute THEN {
numberOfAttributeValues ← readInt[];
};
FOR attVal: INT IN [0..numberOfAttributeValues) DO -- for each attribute value (a field)
fieldName: ROPENIL;
valueSet: LIST OF YggRep.TypedPrimitiveElement; -- set of values for field
IF ~preample.noFieldNames THEN {
fieldName ← readString[1]; -- read field name
};
DO -- for each typed primitive element in the field's value set
fieldValue: YggRep.Bits;
IF preample.typeCode = separate THEN {
fieldDocType ← readInt[];
};
SELECT fieldDocType FROM
YggRep.unknown => EXIT;
YggRep.int => {
i: INT32;
ri: REF INT32;
i ← readInt[];
ri ← NEW[INT32 ← i];
fieldValue ← ri;
};
YggRep.shortRope => {
rRope: ROPE;
rRope ← readString[0];
fieldValue ← rRope;
};
YggRep.rope => {
len: INT;
lenToRead: INT;
charsRead: INT;
rRope: ROPE;
scratch: REF TEXT;
len ← readInt[];
lenToRead ← Basics.charsPerWord * ((len - 1 + Basics.charsPerWord)/Basics.charsPerWord);
scratch ← RefText.ObtainScratch[lenToRead];
TRUSTED {charsRead ← IO.UnsafeGetBlock[attributes, [LOOPHOLE[@scratch], 0 , lenToRead]];};
IF charsRead # lenToRead THEN ERROR;
scratch.length ← len;
rRope ← Rope.FromRefText[s: scratch, start: 0, len: len];
fieldValue ← rRope;
};
YggRep.float => {
rReal: REF REAL32;
rReal ← NEW[REAL32];
TRUSTED {
charsRead: INT;
ptReal: POINTER TO REAL32;
ptReal ← LOOPHOLE[rReal];
charsRead ← IO.UnsafeGetBlock[attributes, [LOOPHOLE[ptReal], 0, Basics.charsPerWord]];
IF charsRead # Basics.charsPerWord THEN ERROR;
};
fieldValue ← rReal;
};
YggRep.date => {
rAccurateGMT: YggRep.AccurateGMT;
rAccurateGMT ← NEW[YggRep.AccurateGMTRep];
TRUSTED {
charsRead: INT;
ptAGMT: POINTER TO YggRep.AccurateGMTRep;
ptAGMT ← LOOPHOLE[rAccurateGMT];
charsRead ← IO.UnsafeGetBlock[attributes, [LOOPHOLE[ptAGMT], 0, YggRep.AccurateGMTRepByteSize]];
IF charsRead # YggRep.AccurateGMTRepByteSize THEN ERROR;
};
fieldValue ← rAccurateGMT;
};
ENDCASE => {
charsRead: INT;
junk: PACKED ARRAY [0..Basics.charsPerWord) OF CHAR;
len: INT;
lenToRead: INT;
nullsToRead: INT;
rBits: REF YggRep.BitsRep;
len ← readInt[];
lenToRead ← Basics.charsPerWord * ((len - 1 + Basics.charsPerWord)/Basics.charsPerWord);
rBits ← NEW[YggRep.BitsRep[len]];
rBits.validBytes ← len;
TRUSTED {
ptBits: POINTER TO YggRep.BitsRep;
ptBits ← LOOPHOLE[rBits];
charsRead ← IO.UnsafeGetBlock[attributes, [LOOPHOLE[ptBits], 0, lenToRead]];
IF charsRead # lenToRead THEN ERROR;
};
nullsToRead ← len - lenToRead;
IF nullsToRead > 0 THEN TRUSTED {
charsRead ← IO.UnsafeGetBlock[attributes, [LOOPHOLE[@junk], 0 , nullsToRead]];
IF charsRead # nullsToRead THEN ERROR;
};
};
valueSet ← CONS[[fieldDocType, fieldValue], valueSet];
ENDLOOP; -- for each typed primitive element in the field's value set
attributeValue ← CONS[[fieldName, valueSet], attributeValue];
ENDLOOP; -- for each attribute value
IF Rope.Compare["$contents", attributeName, FALSE] = equal THEN {
IF contents # NIL THEN ERROR;
Check to be sure that the value is a singleton value of a singleton set
IF attributeValue = NIL THEN ERROR;
IF attributeValue.first.valueSet = NIL THEN ERROR;
IF attributeValue.rest # NIL THEN ERROR;
IF attributeValue.first.valueSet = NIL THEN ERROR;
IF attributeValue.first.valueSet.rest # NIL THEN ERROR;
document.contents ← attributeValue.first.valueSet.first;
}
ELSE document.attributes ← CONS[[attributeName, preample.ordered, attributeValue], document.attributes];
ENDLOOP; -- for each attribute
};
IF contents # NIL THEN {
bits: REF YggRep.BitsRep;
size: INT;
bytesLeft: INT;
nextByteToRead: INT ← 0;
document.contents.docType ← YggRep.uninterpretedBytes;
size ← bytesLeft ← IO.GetLength[contents];
document.contents.bits ← bits ← NEW[YggRep.BitsRep[size]];
bits.validBytes ← size;
WHILE bytesLeft > 0 DO
nBytesRead: INT;
unsafeBlock: Basics.UnsafeBlock;
TRUSTED {
firstByte: POINTER;
firstByte ← @document.contents.bits;
unsafeBlock ← [LOOPHOLE[firstByte], nextByteToRead, bytesLeft];
nBytesRead ← IO.UnsafeGetBlock[self: contents, block: unsafeBlock];
};
bytesLeft ← bytesLeft - nBytesRead;
nextByteToRead ← nextByteToRead + nBytesRead;
ENDLOOP;
};
IF links # NIL THEN {
};
CacheDID[did, document];
};
YggTransContext.EstablishTransactionContext[transID: transID, work: innerVolatizeFromDID];
};
Cache management utilities
LookupDIDInCache: PUBLIC ENTRY PROC [did: DID] RETURNS [document: YggRep.VDoc ← NIL] ~ {
Given a DID, return the volatile form of the document if it is cached. Block until object is cached, if it is reserved.
found: BOOL;
val: REF;
DO
[found, val] ← SymTab.Fetch[x: didCache, key: did^];
IF found THEN {
entry: cacheEntry;
entry ← NARROW[val];
IF entry.users # 0 THEN {WAIT myCondition; LOOP;};
RETURN[entry.document];
};
ENDLOOP;
};
ReserveDIDInCache: PUBLIC ENTRY PROC [did: DID] ~ {
Given a DID, reserve this DID as being volatized. Calling this procedure obligates the caller to call CacheDID to clear the reservation.
found: BOOL;
val: REF;
[found, val] ← SymTab.Fetch[x: didCache, key: did^];
IF found THEN {
entry: cacheEntry;
entry ← NARROW[val];
WHILE entry.users # 0 DO WAIT myCondition; ENDLOOP;
entry.users ← 1;
}
ELSE ERROR;
};
UnreserveDIDInCache: PUBLIC ENTRY PROC [did: DID] ~ {
Undo a reservation.
found: BOOL;
val: REF;
[found, val] ← SymTab.Fetch[x: didCache, key: did^];
IF found THEN {
entry: cacheEntry;
entry ← NARROW[val];
entry.users ← entry.users - 1;
BROADCAST myCondition;
}
ELSE ERROR;
};
CacheDID: PUBLIC ENTRY PROC [did: DID, document: YggRep.VDoc] ~ {
Add this document to the cache for the did.
found: BOOL;
val: REF;
[found, val] ← SymTab.Fetch[x: didCache, key: did^];
IF found THEN {
entry: cacheEntry;
entry ← NARROW[val];
entry.document ← document;
}
ELSE {
IF ~SymTab.Insert[x: didCache, key: did^, val: NEW[cacheEntryRep ← [0, document]]] THEN ERROR;
BROADCAST myCondition;
};
};
InvalidateDID: PUBLIC ENTRY PROC [did: DID] ~ {
Remove this did from the cache.
found: BOOL;
val: REF;
[found, val] ← SymTab.Fetch[x: didCache, key: did^];
IF found THEN {
entry: cacheEntry;
entry ← NARROW[val];
WHILE entry.users # 0 DO WAIT myCondition; ENDLOOP;
[] ← SymTab.Delete[x: didCache, key: did^];
};
};
SetSizeOfDIDCache: PUBLIC ENTRY PROC [size: INT] = {
Remove this did from the cache.
desiredSizeOfDIDCache ← size;
};
Cache trim
DIDCacheTrimProcess: PROC = {
ticksToWait: YggDummyProcess.Ticks;
ticksToWait ← YggDummyProcess.MsecToTicks[293];
DO
innerTrim: ENTRY PROC = {
eachPairAction: SymTab.EachPairAction = {
PROC [key: Key, val: Val] RETURNS [quit: BOOLFALSE];
ce: cacheEntry;
ce ← NARROW[val];
IF ce.users = 0 THEN [] ← SymTab.Delete[x: didCache, key: key];
size ← size - 1;
IF size <= desiredSizeOfDIDCache THEN quit ← TRUE;
};
size: INT;
size ← SymTab.GetSize[didCache];
[] ← SymTab.Pairs[x: didCache, action: eachPairAction];
};
YggDummyProcess.Pause[ticksToWait];
IF SymTab.GetSize[didCache] > desiredSizeOfDIDCache THEN innerTrim[];
ENDLOOP;
};
Initialization
didCache ← SymTab.Create[mod: 129, case: TRUE];
TRUSTED {
YggDummyProcess.Detach[FORK DIDCacheTrimProcess];
YggDummyProcess.SetTimeout[condition: @myCondition, ticks: YggDummyProcess.MsecToTicks[157]];
};
END.