FilePrivateImpl.mesa
Last edited by:
Taft on May 25, 1983 10:46 am
MBrown on November 15, 1982 5:11 pm
Kolling on March 9, 1983 12:05 pm
DIRECTORY
AccessControl,
AlpineDebug,
AlpineEnvironment,
AlpineFile,
AlpineInternal,
AlpineOwner,
AlpineVolume,
FileInstance,
FileMap,
FilePrivate,
Lock,
LogMap,
OpenFileMap,
TransactionMap;
FilePrivateImpl: MONITOR
IMPORTS AccessControl, AlpineFile, FileInstance, FileMap, Lock, LogMap, OpenFileMap, TransactionMap
EXPORTS FilePrivate =
BEGIN OPEN FilePrivate;
FilePrivate.
EstablishOpenFileContext: PUBLIC PROCEDURE [conversation: AlpineEnvironment.Conversation, openFileID: AlpineEnvironment.OpenFileID, work: OpenFileWork, workLevel: AlpineInternal.WorkLevel ← normal, concurrency: Concurrency ← normal] =
BEGIN
openFile: OpenFileMap.Handle = OpenFileMap.GetAndCheckHandle[conversation: conversation, openFileID: openFileID !
OpenFileMap.OpenFileIDNotFound, OpenFileMap.BadConversation => GOTO unknownOpenFileID];
fileInstance: FileInstance.Handle = OpenFileMap.GetFileInstanceHandle[openFile];
file: FileMap.Handle = FileInstance.GetFileHandle[fileInstance];
trans: TransactionMap.Handle = FileInstance.GetTransHandle[fileInstance];
updateCost: INT;
lockFailure: AlpineEnvironment.LockFailure;
operationFailure: AlpineEnvironment.OperationFailure;
unknownType: AlpineEnvironment.UnknownType;
DO
IF concurrency=normal THEN
BEGIN
IF ~TransactionMap.StartWork[trans, workLevel] THEN
ERROR AlpineFile.Unknown[transID]; -- trans must already have finished
END
ELSE StartExclusiveWork[file, trans, workLevel];
BEGIN ENABLE {
Informational signal from AcquireFileInterlock:
AcquireInterlockAttempted => {
IF ~failed THEN {concurrency ← exclusive; RESUME}
ELSE GOTO acquireInterlockFailed};
Public error. Clean up local state and then allow signal to propagate. n.b. this does the right thing only for ERRORs, not SIGNALs.
AlpineFile.AccessFailed, AlpineFile.LockFailed, AlpineFile.OperationFailed, AlpineFile.StaticallyInvalid, AlpineFile.Unknown => {
IF concurrency=normal THEN TransactionMap.StopWork[trans, updateCost]
ELSE StopExclusiveWork[file, trans, updateCost]}};
description: LogMap.FileDescription = LogMap.DescribeFile[file, trans];
IF description.registered AND ~description.exists THEN
ERROR AlpineFile.Unknown[openFileID]; -- most likely the file has been deleted by this transaction
updateCost ← 0;
BEGIN
work[openFile: openFile, fileInstance: fileInstance, file: file, trans: trans, pUpdateCost: @updateCost !
AccessControl.LockFailed, Lock.Failed => {lockFailure ← why; GOTO lockFailed};
AccessControl.OperationFailed => {operationFailure ← why; GOTO operationFailed};
AccessControl.StaticallyInvalid => GOTO staticallyInvalid;
AccessControl.Unknown => {unknownType ← why; GOTO unknown};
Lock.TransAborting => {unknownType ← transID; GOTO unknown}];
EXITS
lockFailed => ERROR AlpineFile.LockFailed[lockFailure];
operationFailed => ERROR AlpineFile.OperationFailed[operationFailure];
staticallyInvalid => ERROR AlpineFile.StaticallyInvalid;
unknown => ERROR AlpineFile.Unknown[unknownType];
END;
GOTO finished;
EXITS
acquireInterlockFailed =>
BEGIN
TransactionMap.StopWork[trans, updateCost];
AwaitExclusiveWorkCompleted[];
now loop back to "BEGIN ENABLE ..."
END;
END;
REPEAT
finished => NULL;
ENDLOOP;
IF concurrency=normal THEN TransactionMap.StopWork[trans, updateCost]
ELSE StopExclusiveWork[file, trans, updateCost];
EXITS
unknownOpenFileID => ERROR AlpineFile.Unknown[openFileID];
END;
AcquireFileInterlock: PUBLIC PROCEDURE [file: FileMap.Handle] =
BEGIN
SIGNAL AcquireInterlockAttempted[failed: FileMap.SetInterlock[file, TRUE]]; -- RESUMEd only if failed=FALSE
END;
EstablishTransactionContext: PUBLIC PROCEDURE [conversation: AlpineEnvironment.Conversation, transID: AlpineEnvironment.TransID, work: TransactionWork, workLevel: AlpineInternal.WorkLevel ← normal] =
BEGIN
trans: TransactionMap.Handle = TransactionMap.GetHandle[transID];
updateCost: INT;
lockFailure: AlpineEnvironment.LockFailure;
operationFailure: AlpineEnvironment.OperationFailure;
unknownType: AlpineEnvironment.UnknownType;
IF trans=TransactionMap.nullHandle OR ~TransactionMap.StartWork[trans, workLevel] THEN
ERROR AlpineFile.Unknown[transID];
BEGIN ENABLE
Public error. Clean up local state and then allow signal to propagate. n.b. this does the right thing only for ERRORs, not SIGNALs.
AlpineFile.AccessFailed, AlpineFile.LockFailed, AlpineFile.OperationFailed, AlpineFile.StaticallyInvalid, AlpineFile.Unknown =>
TransactionMap.StopWork[trans, updateCost];
updateCost ← 0;
BEGIN -- extra nesting so errors raised from EXITS clause are caught by the ENABLE
work[trans: trans, pUpdateCost: @updateCost !
AccessControl.LockFailed, Lock.Failed => {lockFailure ← why; GOTO lockFailed};
AccessControl.OperationFailed => {operationFailure ← why; GOTO operationFailed};
AccessControl.StaticallyInvalid => GOTO staticallyInvalid;
AccessControl.Unknown => {unknownType ← why; GOTO unknown};
Lock.TransAborting => {unknownType ← transID; GOTO unknown}];
EXITS
lockFailed => ERROR AlpineFile.LockFailed[lockFailure];
operationFailed => ERROR AlpineFile.OperationFailed[operationFailure];
staticallyInvalid => ERROR AlpineFile.StaticallyInvalid;
unknown => ERROR AlpineFile.Unknown[unknownType];
END;
END;
TransactionMap.StopWork[trans, updateCost];
END;
Internal stuff
AcquireInterlockAttempted: SIGNAL [failed: BOOLEAN] = CODE;
exclusiveWorkCompleted: CONDITION;
AwaitExclusiveWorkCompleted: ENTRY PROCEDURE = {WAIT exclusiveWorkCompleted};
StartExclusiveWork: ENTRY PROCEDURE [file: FileMap.Handle, trans: TransactionMap.Handle, workLevel: AlpineInternal.WorkLevel] =
! Unknown[transID]
BEGIN
DO
IF ~TransactionMap.StartWork[trans, workLevel] THEN
RETURN WITH ERROR AlpineFile.Unknown[transID]; -- trans must already have finished
IF ~FileMap.SetInterlock[file, TRUE] THEN EXIT;
TransactionMap.StopWork[trans, 0];
WAIT exclusiveWorkCompleted;
ENDLOOP;
END;
StopExclusiveWork: ENTRY PROCEDURE[file: FileMap.Handle, trans: TransactionMap.Handle, updateCost: INT] =
BEGIN
IF ~FileMap.SetInterlock[file, FALSE] THEN ERROR;
TransactionMap.StopWork[trans, updateCost];
BROADCAST exclusiveWorkCompleted;
END;
END.