FileLockImpl.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Carl Hauser, June 18, 1986 10:17:14 am PDT
Last edited by:
MBrown on January 31, 1984 11:48:02 am PST
Taft on April 10, 1983 3:02 pm
Last Edited by: Kupfer, February 7, 1985 2:35:36 pm PST
Loose ends:
1. Do VolumeGroup locks need to be recovered?
DIRECTORY
AlpineEnvironment USING [FileID, Property, TransID, nullTransID, VolumeGroupID, VolumeID],
AlpineInternal,
File USING [FindVolumeFromID, GetVolumeName],
FileInstance,
FileLock,
FileLockFormat,
FileLog,
FileMap USING [GetName],
Lock,
SkiPatrolHooks USING[TransIDFromTransHandle],
SkiPatrolLog USING[LockConflictInfo, notice],
TransactionMap;
FileLockImpl: MONITOR
The module monitor serializes updates to the LockMode in the FileInstance, and serves no other purpose.
IMPORTS File, FileInstance, FileMap, Lock, SkiPatrolHooks, SkiPatrolLog
EXPORTS FileLock, FileLog =
BEGIN OPEN FileLock, FileLockFormat;
VolumeID: TYPE = AlpineEnvironment.VolumeID;
FileID: TYPE = AlpineEnvironment.FileID;
LockIDRep: TYPE = RECORD [v: VolumeID, f: INT, fSub: FileLockSubID];
MakeLockID: PROC [file: AlpineInternal.FileInstanceHandle, fileLockSubID: FileLockSubID] RETURNS [lockID: AlpineInternal.LockID] = {
vID: VolumeID; fID: FileID;
[vID, fID] ← FileInstance.GetVolumeIDAndFileID[file];
RETURN [[LOOPHOLE[vID], LOOPHOLE[fID.id], LOOPHOLE[fileLockSubID]]]
};
TransIDFromFileInstance: PROC [fileInstance: FileInstance.Handle] RETURNS [AlpineEnvironment.TransID] = {
RETURN [SkiPatrolHooks.TransIDFromTransHandle[FileInstance.GetTransHandle[fileInstance]]]
};
FileLock.
AcquireFileLock: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, requested: LockOption, minimum: LockMode, cacheTrans: AlpineEnvironment.TransID ← AlpineEnvironment.nullTransID] =
BEGIN
newFileMode: LockMode;
requested.mode ← Lock.Sup[requested.mode][minimum];
Acquire the file lock unconditionally, regardless of whether or not it is needed, so that calls to Set and Release balance properly.
newFileMode ← Lock.Set[
trans: FileInstance.GetTransHandle[fileInstance],
lock: MakeLockID[fileInstance, [file[]]],
mode: requested.mode, wait: requested.ifConflict=wait,
cacheTrans: cacheTrans
! Lock.Failed => {  -- Log the error and let the error propagate.
logProc: PROC [SkiPatrolLog.LockConflictInfo];
IF (logProc ← SkiPatrolLog.notice.lockConflict) # NIL THEN
logProc[[
what: why,   -- (conflict or timeout)
where: "FileLockImpl.AcquireFileLock",
transID: TransIDFromFileInstance[fileInstance],
mode: requested.mode,
specifics: entireFile[FileMap.GetName[FileInstance.GetFileHandle[fileInstance]]],
message: ""
]]
}
];
SetLockMode[fileInstance, newFileMode];
END;
ReleaseFileLock: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, retainCacheLock: BOOLFALSE] =
BEGIN
Note that in the following call we do not catch Lock.Error. It is a caller error if the lock doesn't exist, and the unreleasable error cannot occur since we specify releasable: ALL in the call.
UnlockAndSetResultingLockMode[
fileInstance: fileInstance,
lockID: MakeLockID[fileInstance, [file[]]],
releasable: ALL[yes], retainCacheLock: retainCacheLock];
END;
ReleaseFile: PUBLIC PROCEDURE [
fileInstance: FileInstance.Handle, retainCacheLocks: BOOLEAN ← FALSE] =
BEGIN
releasable: Lock.ModeReleasableSet ← [
none: no, read: yes, update: no, write: no,
readIntendUpdate: no, readIntendWrite: no,
intendRead: yes, intendUpdate: no, intendWrite: no];
Lock.ReleaseFileLocks[
trans: FileInstance.GetTransHandle[fileInstance],
prototype: MakeLockID[fileInstance, [page[0]]],
releasable: releasable,
retainCacheLocks: retainCacheLocks !
Lock.Error => CONTINUE];
END;
AcquirePageLocks: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, pageRun: PageRun, requested: LockOption, minimum: AssertedPageLock, cacheTrans: AlpineEnvironment.TransID ← AlpineEnvironment.nullTransID] =
BEGIN
existingFileMode: LockMode ← FileInstance.GetLockMode[fileInstance];
IF requested.mode NOT IN AssertedPageLock THEN requested.mode ← minimum;
requested.mode ← Lock.Sup[requested.mode][minimum];
If file lock subsumes page locks, then no need to lock pages at all.
IF Lock.Sup[requested.mode][existingFileMode] = existingFileMode THEN RETURN;
Get the necessary file intention lock to cover page locks.
{
minFileMode: LockMode = minIntention[requested.mode];
IF Lock.Sup[minFileMode][existingFileMode] # existingFileMode THEN
AcquireFileLock[fileInstance: fileInstance, requested: [minFileMode, requested.ifConflict], minimum: minFileMode, cacheTrans: cacheTrans];
};
The lock mode captured in existingFileMode may have been changed so check once more;
existingFileMode ← FileInstance.GetLockMode[fileInstance];
IF Lock.Sup[requested.mode][existingFileMode] # existingFileMode THEN
BEGIN
Lock the page(s) themselves.
FOR page: PageNumber IN [pageRun.firstPage .. pageRun.firstPage+pageRun.count) DO
[] ← Lock.Set[
trans: FileInstance.GetTransHandle[fileInstance],
lock: MakeLockID[fileInstance, [page[page]]],
mode: requested.mode, wait: requested.ifConflict=wait,
cacheTrans: cacheTrans !
Lock.Failed => {
Log the error and attempt to release the locks we already got (if possible)
logProc: PROC [SkiPatrolLog.LockConflictInfo];
IF (logProc ← SkiPatrolLog.notice.lockConflict) # NIL THEN
logProc[[
what: why,   -- (conflict or timeout)
where: "FileLockImpl.AcquirePageLocks",
transID: TransIDFromFileInstance[fileInstance],
mode: requested.mode,
message: "trying to lock a run of pages",
specifics: page[
fileName: FileMap.GetName[FileInstance.GetFileHandle[fileInstance]],
page: page
]
]];
IF page#pageRun.firstPage THEN
ReleasePageLocks[fileInstance: fileInstance, pageRun: [firstPage: pageRun.firstPage, count: page-pageRun.firstPage], mode: requested.mode]
}
];
ENDLOOP;
END;
END;
ValidateCachePageLocks: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, cacheTrans: AlpineEnvironment.TransID ← AlpineEnvironment.nullTransID, pageRun: PageRun] RETURNS[success: BOOLEAN]=
BEGIN
existingFileMode: LockMode ← FileInstance.GetLockMode[fileInstance];
If file lock subsumes page locks, then no need to lock pages at all.
IF Lock.Sup[read][existingFileMode] = existingFileMode THEN RETURN[TRUE];
Get the necessary file intention lock to cover page locks.
{
IF Lock.Sup[intendRead][existingFileMode] # existingFileMode THEN {
existingFileMode ← Lock.Set[trans: FileInstance.GetTransHandle[fileInstance], lock: MakeLockID[fileInstance, [file[]]], mode: intendRead, wait: FALSE, cacheTrans: cacheTrans !
Lock.Failed => {IF why=cantConvert THEN success ← FALSE; GOTO giveUp}];
};
EXITS
giveUp => RETURN;
};
The lock mode captured in existingFileMode may have been changed so check once more;
existingFileMode ← FileInstance.GetLockMode[fileInstance];
IF Lock.Sup[read][existingFileMode] # existingFileMode THEN
BEGIN
Lock the page(s) themselves.
success ← TRUE;
FOR page: PageNumber IN [pageRun.firstPage .. pageRun.firstPage+pageRun.count) DO
resultMode: LockMode;
resultMode ← Lock.Set[
trans: FileInstance.GetTransHandle[fileInstance],
lock: MakeLockID[fileInstance, [page[page]]],
mode: read, wait: FALSE,
cacheTrans: cacheTrans !
Lock.Failed => IF why=cantConvert THEN { success ← FALSE; CONTINUE }];
IF NOT success THEN {
ReleasePageLocks[fileInstance: fileInstance, pageRun: [firstPage: pageRun.firstPage, count: page-pageRun.firstPage], mode: read];
RETURN;
};
ENDLOOP;
END;
END;
ReleasePageLocks: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, pageRun: PageRun, mode: LockMode ← read, retainCacheLocks: BOOLEANFALSE] =
BEGIN
releasable: Lock.ModeReleasableSet ← ALL [no];
releasable[mode] ← yes;
FOR page: PageNumber IN [pageRun.firstPage .. pageRun.firstPage+pageRun.count) DO
[] ← Lock.Release[
trans: FileInstance.GetTransHandle[fileInstance],
lock: MakeLockID[fileInstance, [page[page]]],
releasable: releasable,
retainCacheLock: retainCacheLocks !
Lock.Error => CONTINUE];
ENDLOOP;
END;
AcquirePropertyLock: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, property: Property, requested: LockOption, minimum: AssertedPageLock] =
BEGIN
existingFileMode: LockMode ← FileInstance.GetLockMode[fileInstance];
IF requested.mode NOT IN AssertedPageLock THEN requested.mode ← minimum;
requested.mode ← Lock.Sup[requested.mode][minimum];
If file lock subsumes property lock, then no need to lock property at all.
IF Lock.Sup[requested.mode][existingFileMode] = existingFileMode THEN RETURN;
Get the necessary file intention lock to cover property lock.
{
minFileMode: LockMode = minIntention[requested.mode];
IF Lock.Sup[minFileMode][existingFileMode] # existingFileMode THEN
AcquireFileLock[fileInstance: fileInstance, requested: [minFileMode, requested.ifConflict], minimum: minFileMode];
};
The lock mode captured in existingFileMode may have been changed so check once more;
existingFileMode ← FileInstance.GetLockMode[fileInstance];
IF Lock.Sup[requested.mode][existingFileMode] # existingFileMode THEN
BEGIN
Lock the property itself. This is the place where all properties besides version are mapped to a single lock.
[] ← Lock.Set[
trans: FileInstance.GetTransHandle[fileInstance],
lock: MakeLockID[fileInstance, [property[property: IF property=version THEN version ELSE other]]],
mode: requested.mode, wait: requested.ifConflict=wait
! Lock.Failed => {  -- Log the error and let the error propagate.
logProc: PROC [SkiPatrolLog.LockConflictInfo];
IF (logProc ← SkiPatrolLog.notice.lockConflict) # NIL THEN
logProc[[
what: why,   -- (conflict or timeout)
where: "FileLockImpl.AcquirePropertyLock",
transID: TransIDFromFileInstance[fileInstance],
mode: requested.mode,
message: "",
specifics: property[
fileName: FileMap.GetName[FileInstance.GetFileHandle[fileInstance]],
property: property
]
]]
}
];
END;
END;
ReleasePropertyLock: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, property: Property, mode: LockMode ← read] =
BEGIN
releasable: Lock.ModeReleasableSet ← ALL [no];
releasable[mode] ← yes;
[] ← Lock.Release[
trans: FileInstance.GetTransHandle[fileInstance],
lock: MakeLockID[fileInstance, [property[property: IF property=version THEN version ELSE other]]],
releasable: releasable !
Lock.Error => CONTINUE];
END;
AcquireSizeLock: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, requested: LockOption, minimum: AssertedPageLock] =
BEGIN
existingFileMode: LockMode ← FileInstance.GetLockMode[fileInstance];
IF requested.mode NOT IN AssertedPageLock THEN requested.mode ← minimum;
requested.mode ← Lock.Sup[requested.mode][minimum];
If file lock subsumes size lock, then no need to lock size at all.
IF Lock.Sup[requested.mode][existingFileMode] = existingFileMode THEN RETURN;
Get the necessary file intention lock to cover size lock.
{
minFileMode: LockMode = minIntention[requested.mode];
IF Lock.Sup[minFileMode][existingFileMode] # existingFileMode THEN
AcquireFileLock[fileInstance: fileInstance, requested: [minFileMode, requested.ifConflict], minimum: minFileMode];
};
The lock mode captured in existingFileMode may have been changed so check once more;
existingFileMode ← FileInstance.GetLockMode[fileInstance];
IF Lock.Sup[requested.mode][existingFileMode] # existingFileMode THEN
BEGIN
Now lock the size itself.
[] ← Lock.Set[
trans: FileInstance.GetTransHandle[fileInstance],
lock: MakeLockID[fileInstance, [size[]]],
mode: requested.mode, wait: requested.ifConflict=wait
! Lock.Failed => {  -- Log the error and let the error propagate.
logProc: PROC [SkiPatrolLog.LockConflictInfo];
IF (logProc ← SkiPatrolLog.notice.lockConflict) # NIL THEN
logProc[[
what: why,   -- (conflict or timeout)
where: "FileLockImpl.AcquireSizeLock",
transID: TransIDFromFileInstance[fileInstance],
mode: requested.mode,
message: "",
specifics: size[FileMap.GetName[FileInstance.GetFileHandle[fileInstance]]]
]]
}
];
END;
END;
AcquireVolumeGroupLock: PUBLIC PROCEDURE [volumeGroupID: AlpineEnvironment.VolumeGroupID, trans: TransactionMap.Handle, requested: LockOption, minimum: AssertedPageLock] =
BEGIN
subID: FileLockSubID ← [volumeGroup[]];
IF requested.mode NOT IN AssertedPageLock THEN requested.mode ← minimum;
requested.mode ← Lock.Sup[requested.mode][minimum];
[] ← Lock.Set[
trans: trans,
lock: [LOOPHOLE[volumeGroupID], LOOPHOLE[LONG[0]], LOOPHOLE[subID]],
mode: requested.mode, wait: requested.ifConflict=wait
! Lock.Failed => {  -- Log the error and let the error propagate.
logProc: PROC [SkiPatrolLog.LockConflictInfo];
IF (logProc ← SkiPatrolLog.notice.lockConflict) # NIL THEN
logProc[[
what: why,   -- (conflict or timeout)
where: "FileLockImpl.AcquireVolumeGroupLock",
transID: SkiPatrolHooks.TransIDFromTransHandle[trans],
mode: requested.mode,
message: "",
specifics: volumeGroup[File.GetVolumeName[volume: File.FindVolumeFromID[id: volumeGroupID]]]
]]
}
];
END;
FileLog.
RecoverLock: PUBLIC PROCEDURE [fileInstance: FileInstance.Handle, lockSubID: AlpineInternal.LockSubID, outcome: AlpineInternal.TransState] =
BEGIN
IF outcome=ready THEN
BEGIN
mode: LockMode ← Lock.Set[
trans: FileInstance.GetTransHandle[fileInstance],
lock: MakeLockID[fileInstance, LOOPHOLE[lockSubID]],
mode: write, wait: FALSE
! Lock.Failed => {  -- Log the error and let the error propagate.
logProc: PROC [SkiPatrolLog.LockConflictInfo];
IF (logProc ← SkiPatrolLog.notice.lockConflict) # NIL THEN
logProc[[
what: why,   -- (conflict or timeout)
where: "FileLockImpl.RecoverLock",
transID: TransIDFromFileInstance[fileInstance],
mode: write,
message: "",
specifics: unknown[]
]]
}
];
SetLockMode[fileInstance, mode];
END;
END;
Private stuff
minIntention: PACKED ARRAY LockMode[none..write] OF LockMode = [none: none, read: intendRead, write: intendWrite, update: intendUpdate]; -- minIntention[r] is the minimum file intention mode for page lock mode r
This serialization of updates to the FileInstance.LockMode works only under the following assumptions:
1. Setting a lock can only raise the lock's strength or leave it the same; it can never weaken it.
2. It is not harmful for the FileInstance.LockMode to record a lock mode weaker than the one which is actually set on the file.
3. Any given caller will release only those locks which it has set; i.e., it will not call Release more times than it has previously called Acquire.
SetLockMode: ENTRY PROCEDURE [fileInstance: FileInstance.Handle, mode: LockMode] = INLINE
BEGIN
FileInstance.SetLockMode[fileInstance, mode];
END;
UnlockAndSetResultingLockMode: ENTRY PROCEDURE [fileInstance: FileInstance.Handle, lockID: AlpineInternal.LockID, releasable: Lock.ModeReleasableSet, retainCacheLock: BOOLEAN] =
! Lock.Error {lockUnreleasable, unknown};
BEGIN
ENABLE UNWIND => NULL;
FileInstance.SetLockMode[fileInstance, Lock.Release[trans: FileInstance.GetTransHandle[fileInstance], lock: lockID, releasable: releasable, retainCacheLock: retainCacheLock]];
END;
END.
Notes on lock recovery
This discussion assumes that there is only one process performing recovery and that actions are recovered in the order that they were originally logged. However, deferred updates may be carried out concurrently, as is true during normal operation.
During recovery, it is necessary in certain cases to set the locks which were set during the original operations. This is necessary if the transaction is in the ready state at the end of recovery, since new actions must be prevented from interfering with the locked entities until the transaction either commits or aborts.
Locking during recovery is not necessary if the recovered state of the transaction is either committed or aborted; in either case, if we did set locks, they would be removed before the end of recovery. Deferred updates have a separate mechanism (the LogMap CheckOut facility) for preventing interference from new actions.
The only locks that really need to be set during recovery are ones corresponding to updates by the recovered transaction. Entities that were merely read by the recovered transaction need not be locked. For such entities, the outcome of the recovered transaction cannot affect the state of the file system as viewed by some new transaction. And updates to such entities by a new transaction cannot affect the state of the file system produced by committing the recovered transaction, because the updates which produce that state are entirely recorded in the log and are not a function of the current state of the base file system.
Therefore, it suffices simply to write-lock all entities which were write-locked originally so as to ensure that no new transaction can access these entities until the outcome of the recovered transaction is assured.
The various types of locks are recovered as follows (bearing in mind that only write locks need to be recovered):
File locks are logged explicitly. This is because opening a file may set a lock but does not otherwise give rise to a logged update. If a whole-file lock existed during the original transaction then it is desirable for it to be set during recovery; this way, page-level locking will be suppressed as it was originally. (This is not logically necessary; but setting more locks during recovery than during the original transaction could break the Lock manager.)
Page locks are recovered by setting them during the processing of WritePages log records; they are set only when not subsumed by a file lock, as just described. Note that writes above highWaterMark (which don't give rise to WritePages log records) are covered by a whole-file lock, because the highWaterMark mechanism only operates when the file is locked.
Size locks are recovered by setting them during the processing of SetSize log records.
Property locks are recovered by setting them during the processing of WriteLeaderPage log records. More precisely, processing a WriteLeaderPage log record sets a property lock on the version number; this is adequate because the version lock covers updating of all properties during commit (see notes in LeaderPageImpl for details).
If everything is working properly, setting a lock during recovery cannot cause a conflict. The locks set during recovery are a subset of the locks set originally. Any lock set during recovery is applied in a context which includes a subset of the original locks, set in the same sequence. Since the original lock sequence worked and locks modes form a partial order, the new lock cannot cause a conflict. Therefore, as a consistency check, all locks are set with ifConflict=fail and Lock.Failed is not caught.
A complication arises if we allow write locks which have been set to be released later while the transaction is still in progress. In principle, this would require us to log releasing locks as well as setting them; not doing so might result in a conflict during recovery. In practice, file locks are released only as part of error recovery within the operation which set them (currently just Open); so we simply arrange not to log the lock until all possibility of error has passed. All other write locks (page, size, property) cannot be released at all.
CHANGE LOG
Edited on July 25, 1984 10:59:36 am PDT, by Kupfer
Add SkiPatrolLog probe.
changes to: DIRECTORY, AcquirePageLocks, AcquireFileLock, AcquirePropertyLock, AcquireSizeLock, AcquireVolumeGroupLock, RecoverLock, FileLockImpl, TransIDFromFileInstance
Edited on August 6, 1984 3:22:52 pm PDT, by Kupfer
Remove the possible race condition in SkiPatrolLog probes by assigning the PROC to a temporary variable.
changes to: DIRECTORY, AcquireFileLock, AcquirePageLocks, AcquirePropertyLock, AcquireSizeLock, AcquireVolumeGroupLock, RecoverLock
Edited on February 7, 1985 11:49:59 am PST, by Kupfer
For lock conflicts, record useful information like the file name, what page, what property, etc.