LeaderPageImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last edited by:
MBrown on January 30, 1984 12:17:43 pm PST
Taft on June 3, 1983 11:12 am
Kolling on June 3, 1983 1:37 pm
Hauser, March 8, 1985 10:37:36 am PST
Carl Hauser, June 26, 1986 4:58:17 pm PDT
DIRECTORY
AlpineEnvironment,
AlpineFile,
AlpineInline,
AlpineInternal,
Basics USING [bytesPerWord],
BasicTime USING [Now, ToNSTime, FromNSTime],
CountedVM USING [Handle],
FileInstance,
FileLock,
FileLog,
FileMap,
FilePageMgr,
FilePrivate,
LeaderPage,
LeaderPageFormat,
LogMap,
PrincOpsUtils USING [LongCopy],
RPC USING [maxShortStringLength],
Rope USING [ActionType, Equal, FromProc, Length, Map],
SafeStorage USING [GetSystemZone],
TransactionMap;
LeaderPageImpl: PROGRAM
IMPORTS AlpineFile, AlpineInline, BasicTime, FileInstance, FileLock, FileLog, FileMap, FilePageMgr, FilePrivate, LeaderPage, LogMap, PrincOpsUtils, Rope, SafeStorage
EXPORTS AlpineInternal, FileLog, LeaderPage =
BEGIN OPEN LeaderPage, LeaderPageFormat;
String: TYPE = AlpineEnvironment.String;
LeaderPageHandle: TYPE = REF LeaderPageObject;
LeaderPageObject: PUBLIC TYPE = LeaderPageFormat.LeaderPageObject;
LeaderPage.
Initialize: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] =
BEGIN
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
description: LogMap.FileDescription = LogMap.DescribeFile[file: file, trans: trans];
Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- =
BEGIN
AlpineInline.LongZero[lp, AlpineEnvironment.wordsPerPage];
lp.dataStart ← lp.dataEnd ← offsetData;
lp.seal ← sealLeaderPageRecord;
FOR property: Property IN Property DO
SetPropertyInternal[lp: lp, nPages: 1, propertyValue: GetDefaultPropertyValue[property]];
ENDLOOP;
FileInstance.SetMaxDeltaVersion[fileInstance, 1];
[] ← FileInstance.SetHighWaterMark[fileInstance, 0];
IF validateAfterEveryUpdate THEN ValidateInternal[lp, 1];
END; -- Work
IF ~description.registered OR ~description.created OR
LogMap.GetLeaderPageHandle[file: file, trans: trans]#NIL THEN Error[notVirginFile];
IF FileInstance.GetLockMode[fileInstance] # write THEN ERROR;
PerformWork[fileInstance: fileInstance, work: Work, intention: write];
LogMap.SetCommittedVersion[file: file, version: 0];
LogMap.SetCommittedHighWaterMark[file: file, highWaterMark: 0];
END;
GetProperty: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, property: Property, lock: LockOption ← [read, wait]] RETURNS [propertyValue: PropertyValuePair] =
BEGIN
file: FileMap.Handle ← FileInstance.GetFileHandle[fileInstance];
Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- =
BEGIN
found: BOOLEAN;
IF validateBeforeEveryOperation THEN ValidateInternal[lp, nPages];
[found: found, propertyValue: propertyValue] ← GetPropertyInternal[lp: lp, property: property];
IF ~found THEN propertyValue ← GetDefaultPropertyValue[property];
END; -- Work
FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: property, requested: lock, minimum: read];
SELECT property FROM
highWaterMark =>
BEGIN -- writes beyold old highWaterMark may not be reflected in leader page yet
cachedHighWaterMark: PageCount = FileInstance.GetHighWaterMark[fileInstance];
PerformWork[fileInstance: fileInstance, work: Work];
IF cachedHighWaterMark#LAST[PageCount] THEN
WITH propertyValue SELECT FROM
highWaterMark => highWaterMark ← MAX[highWaterMark, cachedHighWaterMark];
ENDCASE => ERROR;
RETURN;
END;
version =>
BEGIN
version: FileVersion = LogMap.GetCommittedVersion[file: file];
IF version#0 THEN RETURN [[version[version]]];
If not in LogMap, fall through and do the work to get the version from the base.
END;
ENDCASE;
PerformWork[fileInstance: fileInstance, work: Work];
END;
GetUserProperty: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, property: UserProperty, lock: LockOption ← [read, wait]] RETURNS [propertyValue: UserPropertyValuePair] =
BEGIN
file: FileMap.Handle ← FileInstance.GetFileHandle[fileInstance];
Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- =
BEGIN
found: BOOLEAN;
IF validateBeforeEveryOperation THEN ValidateInternal[lp, nPages];
[found: found, propertyValue: propertyValue] ← GetUserPropertyInternal[lp: lp, property: property];
END; -- Work
FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: stringName, requested: lock, minimum: read];
PerformWork[fileInstance: fileInstance, work: Work];
END;
GetPropertyList: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, desiredProperties: PropertySet ← ALL [TRUE], lock: LockOption ← [read, wait]] RETURNS [propertyList: LIST OF PropertyValuePair ← NIL] =
BEGIN
FOR property: Property DECREASING IN Property DO
IF desiredProperties[property] THEN
propertyList ← FilePrivate.stdPZone.CONS[GetProperty[fileInstance: fileInstance, property: property, lock: lock], propertyList];
ENDLOOP;
END;
GetUserPropertyList: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, desiredProperties: UserProperties, lock: LockOption ← [read, wait]] RETURNS [properties: UserPropertyValuePairs ← NIL] =
BEGIN
Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- =
BEGIN
offset: LeaderPageOffset ← Find[lp, 0, userProperty];
WHILE offset # 0 DO
prop: LONG POINTER TO PropertyRep[userProperty] ← LOOPHOLE[lp+offset];
property: String ← StringFromRep[@prop.userPropertyValue.name];
userVal: LONG POINTER TO UserValueRep ← LOOPHOLE[@prop.userPropertyValue+RepFromString[NIL, property, FALSE]];
WITH v: userVal SELECT v.type FROM
longCardinal => properties ← CONS[[property, longCardinal[v.longCardinal]], properties];
rope => properties ← CONS[[property, rope[StringFromRep[@v.rope]]], properties];
ENDCASE => ERROR;
offset ← Find[lp, offset+prop.size, userProperty];
ENDLOOP;
END; -- Work
IF desiredProperties = NIL THEN {
PerformWork[fileInstance: fileInstance, work: Work, intention: read]
}
ELSE FOR p: UserProperties ← desiredProperties, p.rest WHILE p # NIL DO
properties ← CONS[GetUserProperty[fileInstance: fileInstance, property: p.first, lock: lock], properties];
ENDLOOP;
END;
SetProperty: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, propertyValue: PropertyValuePair, lock: LockOption ← [update, wait]] =
BEGIN
Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- =
BEGIN
IF validateBeforeEveryOperation THEN ValidateInternal[lp, nPages];
SetPropertyInternal[lp: lp, nPages: nPages, propertyValue: propertyValue];
IF validateAfterEveryUpdate THEN ValidateInternal[lp, nPages];
FileInstance.SetMaxDeltaVersion[fileInstance, 1];
END; -- Work
file: FileMap.Handle ← FileInstance.GetFileHandle[fileInstance];
FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: propertyValue.property, requested: lock, minimum: update];
WITH propertyValue SELECT FROM
highWaterMark => {
file: FileMap.Handle ← FileInstance.GetFileHandle[fileInstance];
committedHighWaterMark: PageCount ← LogMap.GetCommittedHighWaterMark[file];
IF committedHighWaterMark=LAST[PageCount] THEN BEGIN
committedHighWaterMark ← NARROW[LeaderPage.GetProperty[fileInstance, highWaterMark], PropertyValuePair[highWaterMark]].highWaterMark;
LogMap.SetCommittedHighWaterMark[file, committedHighWaterMark]
END;
[] ← FileInstance.SetHighWaterMark[fileInstance, highWaterMark];
};
version =>
ERROR Error[unwritableProperty];
ENDCASE;
PerformWork[fileInstance: fileInstance, work: Work, intention: write];
END;
SetUserProperty: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, propertyValue: UserPropertyValuePair, lock: LockOption ← [update, wait]] =
BEGIN
Work: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL]-- =
BEGIN
IF validateBeforeEveryOperation THEN ValidateInternal[lp, nPages];
SetUserPropertyInternal[lp: lp, nPages: nPages, propertyValue: propertyValue];
IF validateAfterEveryUpdate THEN ValidateInternal[lp, nPages];
FileInstance.SetMaxDeltaVersion[fileInstance, 1];
END; -- Work
file: FileMap.Handle ← FileInstance.GetFileHandle[fileInstance];
FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: stringName, requested: lock, minimum: update];
PerformWork[fileInstance: fileInstance, work: Work, intention: write];
END;
SetPropertyList: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, propertyList: LIST OF PropertyValuePair, lock: LockOption ← [update, wait]] =
BEGIN
WHILE propertyList#NIL DO
SetProperty[fileInstance: fileInstance, propertyValue: propertyList.first, lock: lock];
propertyList ← propertyList.rest;
ENDLOOP;
END;
Validate: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] =
BEGIN
PerformWork[fileInstance: fileInstance, work: ValidateInternal];
END;
Note: Finalize and CarryOut need not serialize access to the file by multiple clients in the same transaction, because only one client at a time can access the FileStore while the transaction is in this late stage. However, exclusion of access under other transactions (by locking) is still required.
Finalize: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] =
BEGIN
deltaVersion: LONG INTEGER = FileInstance.GetDeltaVersion[fileInstance];
No need to do any of this stuff if the file was not modified by this transaction or if recovery is in progress. Note: during recovery, the version property will be locked when the WriteLeaderPage record is encountered in the log, which will occur before CarryOut is called.
IF deltaVersion#0 AND ~FilePrivate.recovering THEN
BEGIN
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
description: LogMap.FileDescription = LogMap.DescribeFile[file: file, trans: trans];
version: FileVersion;
IF description.deleted THEN
File deleted by this transaction: no need to update leader page
LogMap.SetLeaderPageHandle[file: file, trans: NIL, leaderPage: NIL]
ELSE BEGIN
newHighWaterMark: PageCount ← FileInstance.GetHighWaterMark[fileInstance];
IF newHighWaterMark#LAST[PageCount] THEN
BEGIN -- highWaterMark may have changed
IF newHighWaterMark > LogMap.GetCommittedHighWaterMark[file] THEN
FileLog.LogChangeHWM[fileInstance, LogMap.GetCommittedHighWaterMark[file], newHighWaterMark];
SetProperty[fileInstance: fileInstance, propertyValue: [highWaterMark[newHighWaterMark]], lock: [write, wait]]
END;
FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: version, requested: [write, wait], minimum: write];
IF description.registered AND description.created THEN
BEGIN
This transaction created the file: ordinarily, no need to update the version at all, since the leader page was initialized with a default version of 1. However, if the client has changed deltaVersion, then write the new version directly to the base.
version ← deltaVersion; -- since this is the first one
IF version#1 THEN
BEGIN
vmPageSet: FilePageMgr.VMPageSet;
vmPageSetHandle: CountedVM.Handle;
vmPageSetHandle ensures continued existence of vmPageSet;
[vmPageSet, vmPageSetHandle] ← FilePageMgr.ReadLeaderPages[file];
SetPropertyInternal[lp: vmPageSet.pages, nPages: vmPageSet.pageRun.count, propertyValue: [version[version]]];
FilePageMgr.ReleaseVMPageSet[vMPageSet: vmPageSet, releaseState: writeIndividualNoWait, keep: TRUE];
END;
END
ELSE BEGIN
Pre-existing file: must write a leader page log record containing the incremented version number.
leaderPage: LeaderPageHandle;
recordID: FileLog.LogRecordID;
lp: LONG POINTER TO LeaderPageRecord;
leaderPage ← LogMap.GetLeaderPageHandle[file: file, trans: trans];
IF leaderPage=NIL THEN
Leader page not otherwise updated by this transaction. Copy the leader page from the base file into a temporary LeaderPageRecord (this also causes the committed version to be remembered in the LogMap if it is not already known).
leaderPage ← ReadLeaderPages[file];
lp ← @leaderPage.record;
version ← LogMap.GetCommittedVersion[file: file];
version ← version+deltaVersion;
SetPropertyInternal[lp: lp, nPages: leaderPage.nPages, propertyValue: [version[version]]];
recordID ← FileLog.LogWriteLeaderPages[fileInstance: fileInstance, leaderPage: leaderPage];
LogMap.RegisterLeaderPage[file: file, trans: trans, logRecordID: recordID];
LogMap.SetLeaderPageHandle[file: file, trans: NIL, leaderPage: NIL];
END;
LogMap.SetUncommittedVersion[file: file, trans: trans, version: version];
END;
END;
END;
CarryOut: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle] =
BEGIN
deltaVersion: LONG INTEGER = FileInstance.GetDeltaVersion[fileInstance];
IF deltaVersion#0 THEN -- nonzero means file was updated by this transaction
BEGIN
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
highWaterMark: PageCount = FileInstance.GetHighWaterMark[fileInstance];
location: LogMap.Location;
recordID: FileLog.LogRecordID;
leaderPage: LeaderPageHandle;
[location: location, logRecordID: recordID] ← LogMap.LocateLeaderPage[file: file, trans: trans];
IF location=log THEN
BEGIN
vmPageSet: FilePageMgr.VMPageSet;
vmPageSetHandle: CountedVM.Handle;
leaderPage ← FileLog.LogReadLeaderPages[fileInstance: fileInstance, recordID: recordID];
FilePageMgr.SetLeaderSize[ file, leaderPage.nPages ];
[vmPageSet, vmPageSetHandle] ← FilePageMgr.UseLeaderPages[file];
PrincOpsUtils.LongCopy[to: vmPageSet.pages, from: @leaderPage.record, nwords: leaderPage.nPages*AlpineEnvironment.wordsPerPage];
FilePageMgr.ReleaseVMPageSet[vMPageSet: vmPageSet, releaseState: writeIndividualNoWait, keep: TRUE];
LogMap.UnregisterLeaderPage[file];
END;
LogMap.SetCommittedVersion[file: file, version: LogMap.GetUncommittedVersion[file: file, trans: trans]];
IF highWaterMark#LAST[PageCount] THEN
LogMap.SetCommittedHighWaterMark[file: file, highWaterMark: highWaterMark];
LogMap.SetUncommittedVersion[file: file, trans: NIL, version: 0];
END;
END;
Error: PUBLIC ERROR [errorType: ErrorType] = CODE;
Debugging switches, settable by client
validateBeforeEveryOperation: PUBLIC BOOLEANFALSE;
validateAfterEveryUpdate: PUBLIC BOOLEANFALSE;
FileLog.
RecoverWriteLeaderPage: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, recordID: FileLog.LogRecordID, outcome: FileLog.TransState] =
BEGIN
IF outcome#aborted THEN
BEGIN
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
The version lock covers the actual logging and writing of the leader page, so there is no need to lock the other properties. (This is just as well, since we don't have any way of knowing whether the other properties are supposed to be locked or not.) Also no need to fiddle with the highWaterMark, since the correct new value is already contained in the logged leader page; we just let it remain undefined in both the FileInstance and the LogMap.
FileLock.AcquirePropertyLock[fileInstance: fileInstance, property: version, requested: [write, wait], minimum: write];
LogMap.RegisterLeaderPage[file: file, trans: trans, logRecordID: recordID];
END;
END;
Private procedures
LeaderPageWork: TYPE = PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL];
PerformWork: PROCEDURE [fileInstance: FileInstance.Handle, work: LeaderPageWork, intention: {read, write} ← read] =
Obtains a pointer to the file's leader page (or a copy thereof, after doing appropriate FilePageMgr and/or LogMap operations), enters the FileHandle's monitor, and calls work[lp: LONG POINTER TO LeaderPageRecord]. Expects appropriate locking to have been done already.
BEGIN
DoWork: SAFE PROCEDURE = TRUSTED {work[lp, nPages]};
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
description: LogMap.FileDescription = LogMap.DescribeFile[file: file, trans: trans];
leaderPage: LeaderPageHandle ← LogMap.GetLeaderPageHandle[file: file, trans: trans];
lp: LONG POINTER TO LeaderPageRecord;
nPages: CARDINAL;
IF leaderPage=NIL AND (intention=read OR description.created) THEN
BEGIN -- Perform the operation on the base
vmPageSet: FilePageMgr.VMPageSet;
vmPageSetHandle: CountedVM.Handle;
release: FilePageMgr.ReleaseState = IF intention=read THEN clean ELSE writeIndividualNoWait;
BEGIN
again: BOOLEAN;
DO
again ← FALSE;
[vmPageSet, vmPageSetHandle] ← FilePageMgr.ReadLeaderPages[file
! FilePageMgr.NoSuchFile => ERROR AlpineFile.Unknown[fileID] ];
lp ← vmPageSet.pages;
nPages ← vmPageSet.pageRun.count;
FileMap.Enter[file, DoWork !
LeaderPage.Error => IF errorType=full THEN {again ← TRUE; CONTINUE};
UNWIND => FilePageMgr.ReleaseVMPageSet[vMPageSet: vmPageSet, releaseState: release, keep: TRUE]];
FilePageMgr.ReleaseVMPageSet[vMPageSet: vmPageSet, releaseState: release, keep: TRUE];
IF NOT again THEN EXIT;
FilePageMgr.SetLeaderSize[file, nPages+1];
ENDLOOP;
END;
END
ELSE BEGIN -- Perform the operation on the LeaderPageObject cached in the LogMap
IF leaderPage=NIL THEN
BEGIN
leaderPage ← ReadLeaderPages[file];
LogMap.SetLeaderPageHandle[file: file, trans: trans, leaderPage: leaderPage !
The alreadySet error will no longer be raised by SetLeaderPageHandle; there may be circumstances where it should be, so I'll leave this here for now.
LogMap.Error => IF error=alreadySet THEN {
This can happen only if some other process set the LeaderPageHandle concurrently. Just discard the newly-created one and use the one that is already there.
leaderPage ← LogMap.GetLeaderPageHandle[file: file, trans: trans];
IF leaderPage=NIL THEN ERROR;
CONTINUE}];
END;
BEGIN
again: BOOLEAN;
newLeaderPage: LeaderPageHandle;
DO
again ← FALSE;
lp ← @leaderPage.record;
nPages ← leaderPage.nPages;
FileMap.Enter[file, DoWork !
LeaderPage.Error => IF errorType=full THEN {again ← TRUE; CONTINUE} ];
IF NOT again THEN EXIT;
newLeaderPage ← newLeaderPageObject[leaderPage.nPages+1];
PrincOpsUtils.LongCopy[to: @newLeaderPage.record, from: @leaderPage.record, nwords: leaderPage.nPages*AlpineEnvironment.wordsPerPage];
leaderPage ← newLeaderPage;
LogMap.SetLeaderPageHandle[file: file, trans: trans, leaderPage: leaderPage];
ENDLOOP;
END;
END;
END;
ReadLeaderPages: PROCEDURE [file: FileMap.Handle] RETURNS [leaderPage: LeaderPageHandle] =
Creates and returns LeaderPageObject for file; reads the file's leader page into it (from the base file), and remembers the committed version in the LogMap if it is not already known. Should be called only for an existing (as opposed to newly-created) file, and the leader page must be at least read-locked by the caller. ReadLeaderPages need not be protected by the FileObject's monitor, because the base file's leader page is immutable while the current transaction is in progress.
BEGIN
vmPageSet: FilePageMgr.VMPageSet;
vmPageSetHandle: CountedVM.Handle;
lp: LONG POINTER TO LeaderPageRecord;
[vmPageSet, vmPageSetHandle] ← FilePageMgr.ReadLeaderPages[file];
lp ← vmPageSet.pages;
leaderPage ← newLeaderPageObject[vmPageSet.pageRun.count];
PrincOpsUtils.LongCopy[to: @leaderPage.record, from: vmPageSet.pages, nwords: leaderPage.nPages*AlpineEnvironment.wordsPerPage];
FilePageMgr.ReleaseVMPageSet[vMPageSet: vmPageSet, releaseState: clean, keep: TRUE];
IF LogMap.GetCommittedVersion[file: file]=0 THEN
BEGIN
propertyValue: PropertyValuePair;
found: BOOLEAN;
[found, propertyValue] ← GetPropertyInternal[lp: lp, property: version];
IF ~found THEN ERROR;
LogMap.SetCommittedVersion[file: file, version: NARROW[propertyValue, PropertyValuePair[version]].version];
END;
END;
GetPropertyInternal: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, property: Property] RETURNS [found: BOOLEANFALSE, propertyValue: PropertyValuePair] =
BEGIN
pickledProperty: PickledProperty = propertyPickleMap[property];
prop: LONG POINTER TO PropertyRep;
offset: LeaderPageOffset = Find[lp, 0, pickledProperty];
IF offset=0 THEN RETURN;
prop ← LOOPHOLE[lp+offset];
WITH prop SELECT prop.property FROM
byteLength => propertyValue ← [byteLength[byteLength]];
createTime => propertyValue ← [createTime[BasicTime.FromNSTime[createTime]]];
highWaterMark => propertyValue ← [highWaterMark[highWaterMark]];
modifyAccess => propertyValue ← [modifyAccess[AccessListFromRep[@modifyAccess]]];
owner => propertyValue ← [owner[StringFromRep[@owner]]];
readAccess => propertyValue ← [readAccess[AccessListFromRep[@readAccess]]];
stringName => propertyValue ← [stringName[StringFromRep[@stringName]]];
version => propertyValue ← [version[version]];
ENDCASE => ERROR;
found ← TRUE;
END;
FindUserProp: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, property: UserProperty] RETURNS [offset: LeaderPageOffset] =
Returns offset of specified property, or zero if not found.
BEGIN
offset ← Find[lp, 0, userProperty];
WHILE offset#0 DO
prop: LONG POINTER TO PropertyRep[userProperty] ← LOOPHOLE[lp+offset];
propName: String ← StringFromRep[@prop.userPropertyValue.name];
IF Rope.Equal[propName, property, FALSE] THEN RETURN;
offset ← Find[lp, offset+prop.size, userProperty];
ENDLOOP;
END;
GetUserPropertyInternal: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, property: UserProperty] RETURNS [found: BOOLEANFALSE, propertyValue: UserPropertyValuePair] =
BEGIN
offset: LeaderPageOffset = FindUserProp[lp, property];
IF offset#0 THEN {
prop: LONG POINTER TO PropertyRep[userProperty] ← LOOPHOLE[lp+offset];
userVal: LONG POINTER TO UserValueRep ← LOOPHOLE[@prop.userPropertyValue+RepFromString[NIL, property, FALSE]];
found ← TRUE;
WITH v: userVal SELECT v.type FROM
longCardinal => propertyValue ← [property, longCardinal[v.longCardinal]];
rope => propertyValue ← [property, rope[StringFromRep[@v.rope]]];
ENDCASE => ERROR;
RETURN;
};
END;
AccessListFromRep: PROCEDURE [rep: LONG POINTER TO AccessListRep]
RETURNS [accessList: AccessList] =
BEGIN
string: LONG POINTER TO StringRep ← @rep.principals;
tail: AccessList ← NIL;
accessList ← NIL;
THROUGH [0..rep.count) DO
accessItem: AccessList = FilePrivate.stdPZone.CONS[StringFromRep[string], NIL];
IF tail=NIL THEN accessList ← accessItem ELSE tail.rest ← accessItem;
tail ← accessItem;
string ← string + SIZE[CARDINAL] + (string.length+Basics.bytesPerWord-1)/Basics.bytesPerWord;
ENDLOOP;
END;
StringFromRep: PROCEDURE [rep: LONG POINTER TO StringRep] RETURNS [string: String] =
BEGIN
Fetch: SAFE PROCEDURE RETURNS [c: CHAR] = TRUSTED
{c ← rep.text[index]; index ← index+1};
index: CARDINAL ← 0;
RETURN [IF rep.length=0 THEN NIL ELSE Rope.FromProc[len: rep.length, p: Fetch]];
END;
SetPropertyInternal: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL, propertyValue: PropertyValuePair] =
! Error {full, nameTooLong};
BEGIN
pickledProperty: PickledProperty = propertyPickleMap[propertyValue.property];
offset: LeaderPageOffset = Find[lp: lp, start: 0, pickledProperty: pickledProperty];
prop: LONG POINTER TO PropertyRep ← LOOPHOLE[lp+offset];
size: CARDINAL = RepSize[propertyValue];
wordsRequired: CARDINAL = lp.dataEnd - (IF offset#0 THEN prop.size ELSE 0) + size;
IF wordsRequired > nPages*AlpineEnvironment.wordsPerPage THEN BEGIN
Leader space will have to be extended before setting the new property value;
ERROR Error[full];
END;
IF offset#0 THEN Delete[lp: lp, offset: offset];
prop ← LOOPHOLE[lp+lp.dataEnd];
prop.size ← size;
prop.property ← pickledProperty;
WITH propertyValue SELECT FROM
byteLength =>
WITH pp: prop SELECT byteLength FROM
byteLength => pp.byteLength ← byteLength; ENDCASE;
createTime =>
WITH pp: prop SELECT createTime FROM
createTime => pp.createTime ← BasicTime.ToNSTime[createTime]; ENDCASE;
highWaterMark =>
WITH pp: prop SELECT highWaterMark FROM
highWaterMark => pp.highWaterMark ← highWaterMark; ENDCASE;
modifyAccess =>
WITH pp: prop SELECT modifyAccess FROM
modifyAccess => [] ← RepFromAccessList[@pp.modifyAccess, modifyAccess]; ENDCASE;
owner =>
WITH pp: prop SELECT owner FROM
owner => [] ← RepFromString[@pp.owner, owner]; ENDCASE;
readAccess =>
WITH pp: prop SELECT readAccess FROM
readAccess => [] ← RepFromAccessList[@pp.readAccess, readAccess]; ENDCASE;
stringName =>
WITH pp: prop SELECT stringName FROM
stringName => [] ← RepFromString[@pp.stringName, stringName]; ENDCASE;
version =>
WITH pp: prop SELECT version FROM
version => pp.version ← version; ENDCASE;
ENDCASE => ERROR;
lp.dataEnd ← lp.dataEnd+prop.size;
END;
SetUserPropertyInternal: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, nPages: CARDINAL, propertyValue: UserPropertyValuePair] =
! Error {full, nameTooLong};
BEGIN
offset: LeaderPageOffset = FindUserProp[lp: lp, property: propertyValue.property];
prop: LONG POINTER TO PropertyRep ← LOOPHOLE[lp+offset];
size: CARDINAL = UserRepSize[propertyValue];
wordsRequired: CARDINAL = lp.dataEnd - (IF offset#0 THEN prop.size ELSE 0) + size;
IF wordsRequired > nPages*AlpineEnvironment.wordsPerPage THEN BEGIN
Leader space will have to be extended before setting the new property value;
ERROR Error[full];
END;
IF offset#0 THEN Delete[lp: lp, offset: offset];
prop ← LOOPHOLE[lp+lp.dataEnd];
prop.size ← size;
prop.property ← userProperty;
WITH pp: prop SELECT userProperty FROM
userProperty => {
val: LONG POINTER TO UserValueRep ← LOOPHOLE[@pp.userPropertyValue.name + RepFromString[@pp.userPropertyValue.name, propertyValue.property]];
val.type ← PickledUserTypeMap[propertyValue.propertyType];
WITH pV: propertyValue SELECT FROM
longCardinal => WITH v: val SELECT longCardinal FROM
longCardinal => v.longCardinal ← pV.longCardinal; ENDCASE;
rope => WITH v: val SELECT rope FROM
rope => [] ← RepFromString[@v.rope, pV.rope]; ENDCASE;
ENDCASE => ERROR;
}
ENDCASE;
lp.dataEnd ← lp.dataEnd+prop.size;
END;
RepSize: PROCEDURE [propertyValue: PropertyValuePair] RETURNS [size: CARDINAL] =
BEGIN
sizeCommon: CARDINAL = 1; -- size of common part of PropertyRep record
size ← WITH propertyValue SELECT FROM
byteLength, createTime, highWaterMark, version =>
SIZE[PropertyRep[byteLength]],
modifyAccess =>
RepFromAccessList[NIL, modifyAccess, FALSE]+sizeCommon,
owner =>
RepFromString[NIL, owner, FALSE]+sizeCommon,
readAccess =>
RepFromAccessList[NIL, readAccess, FALSE]+sizeCommon,
stringName =>
RepFromString[NIL, stringName, FALSE]+sizeCommon,
ENDCASE => ERROR;
END;
UserRepSize: PROCEDURE [propertyValue: UserPropertyValuePair] RETURNS [size: CARDINAL] =
BEGIN
sizeCommon: CARDINAL = 1; -- size of common part of PropertyRep record
size ← sizeCommon+RepFromString[NIL, propertyValue.property, FALSE]+(
WITH propertyValue SELECT FROM
longCardinal => 1+1,
rope => 1+RepFromString[NIL, rope, FALSE],
ENDCASE => ERROR);
END;
RepFromAccessList: PROCEDURE [rep: LONG POINTER TO AccessListRep, accessList: AccessList, copy: BOOLEANTRUE] RETURNS [size: CARDINAL] =
! Error {nameTooLong};
BEGIN
count: CARDINAL ← 0;
stringRep: LONG POINTER TO StringRep ← @rep.principals;
size ← SIZE[CARDINAL];
FOR accessItem: AccessList ← accessList, accessItem.rest WHILE accessItem#NIL DO
sizeString: CARDINAL = RepFromString[stringRep, accessItem.first, copy];
count ← count+1;
size ← size+sizeString;
stringRep ← stringRep+sizeString;
ENDLOOP;
IF copy THEN rep.count ← count;
END;
RepFromString: PROCEDURE [rep: LONG POINTER TO StringRep, string: String, copy: BOOLEANTRUE] RETURNS [size: CARDINAL] =
! Error {nameTooLong};
BEGIN
Store: Rope.ActionType --[c: CHAR] RETURNS [quit: BOOLFALSE]-- = TRUSTED
{rep.text[index] ← c; index ← index+1};
length: INT = Rope.Length[string];
index: CARDINAL ← 0;
IF length>RPC.maxShortStringLength THEN ERROR Error[nameTooLong];
size ← SIZE[CARDINAL] + (length+Basics.bytesPerWord-1)/Basics.bytesPerWord;
IF copy THEN
BEGIN
rep.length ← length;
IF length#0 THEN [] ← Rope.Map[base: string, len: length, action: Store];
END;
END;
Find: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, start: LeaderPageOffset, pickledProperty: PickledProperty] RETURNS [offset: LeaderPageOffset] =
Returns offset of specified property, or zero if not found.
BEGIN
offset ← MAX[start, lp.dataStart];
WHILE offset<lp.dataEnd DO
prop: LONG POINTER TO PropertyRep = LOOPHOLE[lp+offset];
IF prop.property=pickledProperty THEN EXIT;
offset ← offset+prop.size;
REPEAT
FINISHED => offset ← 0;
ENDLOOP;
END;
Delete: PROCEDURE [lp: LONG POINTER TO LeaderPageRecord, offset: LeaderPageOffset] =
BEGIN
prop: LONG POINTER TO PropertyRep = LOOPHOLE[lp+offset];
size: CARDINAL = prop.size;
PrincOpsUtils.LongCopy[to: prop, from: prop+size, nwords: lp.dataEnd-(offset+size)];
lp.dataEnd ← lp.dataEnd-size;
END;
GetDefaultPropertyValue: PROCEDURE [property: Property] RETURNS [propertyValue: PropertyValuePair] =
BEGIN
RETURN [
SELECT property FROM
byteLength => [byteLength[0]],
createTime => [createTime[BasicTime.Now[]]],
highWaterMark => [highWaterMark[0]],
modifyAccess => [modifyAccess[NIL]],
owner => [owner[NIL]],
readAccess => [readAccess[NIL]],
stringName => [stringName[NIL]],
version => [version[1]],
ENDCASE => ERROR];
END;
ValidateInternal: LeaderPageWork --[lp: LONG POINTER TO LeaderPageRecord]-- =
BEGIN
offset: LeaderPageOffset;
definedPropertiesSet: PACKED ARRAY PickledProperty OF BOOLEANALL [FALSE];
IF lp.seal#sealLeaderPageRecord OR lp.dataStart NOT IN [2..AlpineEnvironment.wordsPerPage) OR lp.dataEnd<lp.dataStart THEN
ERROR Error[damaged]; -- Offsets fouled up in overhead region
offset ← lp.dataStart;
WHILE offset<lp.dataEnd DO
prop: LONG POINTER TO PropertyRep = LOOPHOLE[lp+offset];
IF prop.size=0 OR offset+prop.size>lp.dataEnd OR prop.property NOT IN PickledProperty THEN
ERROR Error[damaged]; -- Malformed property
IF definedPropertiesSet[prop.property] THEN
ERROR Error[damaged] -- Duplicate property
ELSE definedPropertiesSet[prop.property] ← TRUE;
offset ← offset+prop.size;
ENDLOOP;
IF offset#lp.dataEnd THEN ERROR Error[damaged];
IF ~definedPropertiesSet[version] THEN ERROR Error[damaged]; -- Version property missing
END;
newLeaderPageObject: PUBLIC PROC [nPages: CARDINAL] RETURNS [leaderPage: LeaderPageHandle] =
BEGIN
leaderPage ← leaderPageZone.NEW[LeaderPageObject[nPages-1]];
leaderPage.nPages ← nPages;
END;
Module initialization
leaderPageZone: ZONE = SafeStorage.GetSystemZone[]; -- This zone is only for LeaderPageObjects
END.
Appendix 1: leader page locking
Access to the contents of the leader page itself (in the base file system) and of the cached copy (in the LogMap) is serialized by explicitly entering the FileObject's monitor. All code outside that monitor is intended to work properly even in the face of concurrent access to the same file within the same transaction.
Transaction consistency is accomplished as follows. There are two locks, one for the version property and one for all other properties. When the first change is made to a property other than the version, a copy of the leader page is read and cached in the LogMap. Everything in this copy except for version is covered by the lock. The version is read-only from the client's point of view and is changed only during transaction commit. During phase 1 of commit, a write lock is placed on the version. If no other properties were written by the same transaction, the leader page is read from the base file, the updated version number stored in it, and it is written into the log. But if other properties were written by the same transaction, the updated version is instead stored in the cached copy and that is written into the log. In either case, during phase 2 the logged copy is rewritten into the base before the version lock (and the other properties lock, if acquired) is released.
Note that the cached copy, if present, is not written into the log until phase 1 of commit; correctness depends on this, as will become apparent shortly. Additionally, there is an efficiency advantage in doing things this way: changing multiple properties during a transaction will not result in writing multiple leader page records to the log.
The crucial observation here is that if the file was changed in any way by this transaction (including changing properties other than the version), then the version lock covers the creation, logging, and rewriting of the new version of the leader page. Note that if this transaction does not change any property besides the version, some other transaction may be making changes to the properties (and have a copy of the leader page cached in the LogMap). This means that the version property in the cached leader page may become obsolete. Therefore, the commited version number is maintained separately in the LogMap (and is updated only at commit time while the version is write-locked).
Correct recovery depends on the fact that leader pages (for committed transactions) are logged in strictly increasing order of version number, and each is followed by the commit record for the same transaction, with no intervening leader page records for the same file. This is enforced by the write lock on the version, which is set before writing the leader page record and is not removed until after commit.
Appendix 2: high water mark property maintenance
Handling of the highWaterMark property is also somewhat tricky, because it is desirable to maintain it properly without having to lock it and check it during every page write. To accomplish this, uncommitted and committed highWaterMark values are maintained in the FileInstance and LogMap, respectively. Things are complicated by the fact that a file's LogMap does not exist (and its cached committed highWaterMark therefore unknown) until the first write is performed. The following pseudo-program sketches the basic algorithm:
Abbreviations: LM = LogMap, FI = FileInstance, LP = LeaderPage, hwm = highWaterMark.
hwm has a distinguished nil value whose representation is actually LAST[PageCount]. When a LM or FI is created, its hwm is initialized to nil.
LM.hwm = committed hwm; nil => unknown (not present in LM)
FI.hwm = highest page written during this trans since last explicit sethwm, or last explicit sethwm; nil => no page writes or sethwms in this trans.
LP.hwm = last explicit hwm in this trans if any, else committed hwm; nil does not occur.
Important invariant: if LP.hwm has been changed by trans then LM.hwm contains the actual committed hwm (not nil). In fact, if this transaction has done any hwm manipulations at all, including just write pages, LM.hwm contains the actual committed hwm (not nil). This stronger invariant may not be important.
The following operations are the only ones which touch the hwm in any way.
AlpineFile.Create:
[Assert: trans has a whole-file write lock on file.]
LM.hwm ← FI.hwm ← LP.hwm ← 0;
[Note: opening a file does not perform any operations on the hwm.]
AlpineFile.WritePages, in which pageNumber is the last page to be written:
FI.hwm ← IF FI.hwm=nil THEN 1+pageNumber ELSE MAX[FI.hwm, 1+pageNumber];
target ← log; -- assume we will have to write into the log
IF FI.lockMode>=read THEN {
[Assert: trans holds at least a whole file read lock, which also covers hwm property]
IF LM.hwm=nil THEN LM.hwm ← LP.hwm; -- ensure committed hwm is in LM
IF pageNumber>=LM.hwm THEN target ← base -- can write directly to base --};
LeaderPage.SetProperty[hwm], in which client explicitly changes hwm property to hwmNew:
AcquirePropertyLock[hwm, update];
IF LM.hwm=nil THEN LM.hwm ← LP.hwm; -- ensure committed hwm is in LM
FI.hwm ← LP.hwm ← hwmNew;
LeaderPage.GetProperty[hwm]:
AcquirePropertyLock[hwm, read]; result ← IF FI.hwm=nil THEN LP.hwm ELSE FI.hwm;
LeaderPage.Finalize:
IF FI.hwm#nil THEN {
AcquirePropertyLock[hwm, read];
IF FI.hwm>LM.hwm THEN {
increase in committed hwm value, due to WritePages, SetProperty, or both
AcquirePropertyLock[hwm, write];
LP.hwm ← FI.hwm;
writeChangeHWM[LM.hwm, FI.hwm]
}
ELSE
decreasing or unchanging. Claim: can decrease only if SetProperty was called,
in which case hwm is already locked
LP.hwm ← FI.hwm;
};
LeaderPage.CarryOut:
IF FI.hwm#nil THEN LM.hwm ← FI.hwm;
Change History:
Hauser February 12, 1985: Changes to handle multiple leader pages: LeaderPageObject becomes a variable-sized object; hence, allocate LeaderPageObjects only after we know how many leader pages there are.
Hauser, March 8, 1985 10:37:22 am PST
Added copyright.
Carl Hauser, August 26, 1985 5:13:37 pm PDT
The highWaterMark code in SetProperty and Finalize looked like it couldn't work, and violated stated invariants. Changed to correspond to the invariants. Will it work as well now? Also changed the comments at the end about hwm.
changes to: SetProperty added call to LogMap.SetCommittedHighWaterMark, Finalize reworked highWaterMark code.