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;
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:
BOOLEAN ←
FALSE, 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:
BOOLEAN ←
FALSE, 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:
BOOLEAN ←
TRUE]
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:
BOOLEAN ←
TRUE]
RETURNS [size:
CARDINAL] =
! Error {nameTooLong};
BEGIN
Store: Rope.ActionType
--[c:
CHAR]
RETURNS [quit:
BOOL ←
FALSE]-- =
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 BOOLEAN ← ALL [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;
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.
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;