<> <> <> <> <> 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; <> 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 { <> AcquireInterlockAttempted => { IF ~failed THEN {concurrency _ exclusive; RESUME} ELSE GOTO acquireInterlockFailed}; <> 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[]; <> 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 <> 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; <> AcquireInterlockAttempted: SIGNAL [failed: BOOLEAN] = CODE; exclusiveWorkCompleted: CONDITION; AwaitExclusiveWorkCompleted: ENTRY PROCEDURE = {WAIT exclusiveWorkCompleted}; StartExclusiveWork: ENTRY PROCEDURE [file: FileMap.Handle, trans: TransactionMap.Handle, workLevel: AlpineInternal.WorkLevel] = <> 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.