<> <> <> <> <<>> DIRECTORY AMEvents USING [Debugged, Debugging], Process USING [GetCurrent, MsecToTicks, Pause], DebuggerSwap USING [CallDebugger], ViewerClasses USING [Column, Lock, Viewer], ViewerDebug USING [debugging], ViewerLocks USING [], ViewerGroupLocks USING [], ViewerOps USING [BlinkDisplay, EnumerateChildren, EnumerateViewers, EnumProc], ViewerPrivate; ViewerLocksImpl: CEDAR MONITOR IMPORTS AMEvents, Process, DebuggerSwap, ViewerOps EXPORTS ViewerLocks, ViewerGroupLocks, ViewerPrivate = BEGIN <> <> <> <> <> <> <> <> <> <> <> <> <> <<(currently the readLocksExclusive flag causes read locks to be exclusive)>> <> <> <> <> <> <> <> <> CARD: TYPE = LONG CARDINAL; Column: TYPE = ViewerClasses.Column; Lock: TYPE = ViewerClasses.Lock; Viewer: TYPE = ViewerClasses.Viewer; <> lockFree: CONDITION; <> readLocksExclusive: BOOL _ FALSE; <> treeOwner: PROCESS _ NIL; <> treeUsers: CARDINAL _ 0; <> ColumnInfo: TYPE = RECORD [lock: Lock, wedgeCount: CARDINAL]; columns: ARRAY Column OF ColumnInfo _ ALL[[lock: [process: NIL, count: 0], wedgeCount: 0]]; <> CallRootUnderWriteLock: PUBLIC PROC [proc: PROC, viewer: Viewer] = { <> CallUnderWriteLock[proc, viewer]; }; CallRootAndLinksUnderWriteLock: PUBLIC PROC [proc: PROC, viewer: Viewer] = { WHILE viewer # NIL AND NOT viewer.destroyed DO root: Viewer _ GetRoot[viewer]; link1: Viewer _ IF root = NIL THEN NIL ELSE root.link; link2: Viewer _ IF link1 = NIL THEN NIL ELSE link1.link; quick: BOOL _ TRUE; inner: PROC = { <> IF root # GetRoot[viewer] THEN RETURN; IF root.link # link1 THEN RETURN; IF quick AND (link1 # NIL AND link2 # root) THEN RETURN; proc[]; win _ TRUE; }; win: BOOL _ FALSE; SELECT TRUE FROM root = viewer AND link1 = NIL => CallUnderWriteLock[inner, viewer]; link1 = NIL OR link2 = root => CallUnderWriteLocks[inner, viewer, root, link1]; ENDCASE => {quick _ FALSE; CallUnderViewerTreeLock[inner]}; IF win THEN RETURN; ENDLOOP; }; <> LockViewerTree: PUBLIC ENTRY PROC = { <> ENABLE UNWIND => NULL; process: PROCESS = CurrentProcess[]; DO <> IF treeOwner = process THEN EXIT; <> FOR c: Column IN Column DO IF columns[c].wedgeCount # 0 THEN RETURN WITH ERROR Wedged[c]; ENDLOOP; IF treeUsers = 0 THEN { IF treeOwner # NIL THEN Error[]; EXIT; }; WAIT lockFree; ENDLOOP; treeUsers _ treeUsers + 1; treeOwner _ process; }; ReleaseViewerTree: PUBLIC ENTRY PROC = { <> ENABLE UNWIND => NULL; IF CurrentProcess[] # treeOwner THEN Error[]; IF treeUsers = 0 THEN Error[]; treeOwner _ NIL; treeUsers _ treeUsers - 1; BROADCAST lockFree; }; CallUnderViewerTreeLock: PUBLIC PROC [proc: PROC] = { LockViewerTree[]; proc[! UNWIND => ReleaseViewerTree[]]; ReleaseViewerTree[]; }; <> Wedged: PUBLIC SAFE ERROR [wedged: Column] = CODE; CallUnderColumnLock: PUBLIC PROC [proc: PROC, column: Column] = { ENABLE { AMEvents.Debugging => MarkColumnWedged[column]; AMEvents.Debugged => MarkColumnUnWedged[column]}; Wait: ENTRY PROC = {ENABLE UNWIND => NULL; WAIT lockFree}; AcquireColumnWriteLock[column ! Wedged => {Wait[]; RETRY}]; proc[! UNWIND => ReleaseColumnWriteLock[column]]; ReleaseColumnWriteLock[column]; }; CallUnderColumnLocks: PUBLIC PROC [proc: PROC, c0, c1: Column] = { ENABLE { AMEvents.Debugging => {MarkColumnWedged[c0]; MarkColumnWedged[c1]}; AMEvents.Debugged => {MarkColumnUnWedged[c0]; MarkColumnUnWedged[c1]}; }; Wait: ENTRY PROC = {ENABLE UNWIND => NULL; WAIT lockFree}; IF c0 > c1 THEN {c: Column _ c0; c0 _ c1; c1 _ c}; AcquireColumnWriteLock[c0 ! Wedged => {Wait[]; RETRY}]; AcquireColumnWriteLock[c1 ! Wedged => {Wait[]; RETRY}]; proc[! UNWIND => {ReleaseColumnWriteLock[c1]; ReleaseColumnWriteLock[c0]}]; ReleaseColumnWriteLock[c1]; ReleaseColumnWriteLock[c0]; }; AcquireColumnWriteLock: PUBLIC ENTRY PROC [column: Column] = { ENABLE UNWIND => NULL; IF ~ColumnWriteLockInternal[column] THEN RETURN WITH ERROR Wedged[column]; }; ColumnWriteLockInternal: INTERNAL PROC [column: Column] RETURNS [success: BOOL _ TRUE] = { process: PROCESS ~ CurrentProcess[]; count: CARDINAL _ 0; <> DO IF columns[column].wedgeCount>0 THEN <> RETURN [FALSE]; count _ columns[column].lock.count; IF count=0 OR columns[column].lock.process = process THEN <> IF treeOwner = process OR treeOwner = NIL THEN EXIT; <> WAIT lockFree; <> ENDLOOP; <> treeUsers _ treeUsers + 1; columns[column].lock _ [process, count+1]; }; ReleaseColumnWriteLock: PUBLIC ENTRY PROC [column: Column] = { ENABLE UNWIND => NULL; ReleaseColumnLockInternal[column]; }; ReleaseColumnLockInternal: INTERNAL PROC [column: Column] = { <> IF columns[column].lock.count = 0 THEN Error[]; IF treeUsers = 0 THEN Error[]; <> treeUsers _ treeUsers - 1; IF treeUsers = 0 THEN { treeOwner _ NIL; BROADCAST lockFree; }; <> IF (columns[column].lock.count _ columns[column].lock.count-1) = 0 THEN { columns[column].lock.process _ NIL; BROADCAST lockFree; }; }; ReadLockColumn: INTERNAL PROC [viewer: Viewer _ NIL, column: Column _ static] RETURNS [success: BOOL _ TRUE] = { process: PROCESS ~ CurrentProcess[]; <> DO IF viewer # NIL THEN column _ ViewerColumn[viewer]; IF columns[column].wedgeCount>0 THEN <> RETURN [FALSE]; SELECT columns[column].lock.process FROM NIL, process => <> IF treeOwner = process OR treeOwner = NIL THEN EXIT; <> ENDCASE; WAIT lockFree; <> ENDLOOP; <> treeUsers _ treeUsers + 1; columns[column].lock.count _ columns[column].lock.count+1; }; ColumnWedged: PUBLIC ENTRY PROC [column: Column] RETURNS [wedged, write: BOOL] = TRUSTED { ENABLE UNWIND => NULL; RETURN [columns[column].wedgeCount # 0, columns[column].lock.process # NIL]; }; MarkViewerWedged: PUBLIC PROC [viewer: Viewer] = { ENABLE UNWIND => NULL; IF viewer # NIL THEN { viewer.paintingWedged _ TRUE; MarkColumnUnWedged[ViewerColumn[viewer]]; }; }; MarkViewerUnWedged: PUBLIC PROC [viewer: Viewer] = { ENABLE UNWIND => NULL; IF viewer # NIL THEN { viewer.paintingWedged _ FALSE; MarkColumnUnWedged[ViewerColumn[viewer]]; }; }; MarkColumnWedged: PUBLIC ENTRY PROC [column: Column] = { ENABLE UNWIND => NULL; IF ViewerDebug.debugging THEN RETURN; IF columns[column].wedgeCount = 0 THEN THROUGH [0..2) DO ViewerOps.BlinkDisplay[]; Process.Pause[Process.MsecToTicks[200]]; ENDLOOP; columns[column].wedgeCount _ columns[column].wedgeCount+1; }; MarkColumnUnWedged: PUBLIC ENTRY PROC [column: Column] = { ENABLE UNWIND => NULL; IF ViewerDebug.debugging THEN RETURN; IF (columns[column].wedgeCount _ columns[column].wedgeCount-1) = 0 THEN BROADCAST lockFree; }; <> LockOrder: PUBLIC PROC [v0, v1: Viewer] RETURNS [BOOL] = { SELECT TRUE FROM v0 = NIL => GO TO swapped; v1 = NIL => {}; ENDCASE => { c0: Column _ IF v0.iconic THEN static ELSE v0.column; c1: Column _ IF v1.iconic THEN static ELSE v1.column; SELECT TRUE FROM c0 > c1 => {}; c0 < c1 => GO TO swapped; LOOPHOLE[v0, CARD] < LOOPHOLE[v1, CARD] => GO TO swapped; ENDCASE; }; RETURN [TRUE]; EXITS swapped => RETURN [FALSE]; }; CallUnderWriteLock: PUBLIC PROC [proc: PROC, viewer: Viewer] = { IF viewer # NIL AND NOT viewer.destroyed THEN DO root: Viewer _ GetRoot[viewer]; AcquireWriteLock[root]; IF root # GetRoot[viewer] THEN {ReleaseWriteLock[root]; LOOP}; proc[ ! UNWIND => ReleaseWriteLock[root]; AMEvents.Debugging => MarkViewerWedged[root]; AMEvents.Debugged => MarkViewerUnWedged[root]; ]; ReleaseWriteLock[root]; RETURN; ENDLOOP; }; CallUnderWriteLocks: PUBLIC PROC [proc: PROC, v0, v1, v2: Viewer _ NIL] = { DO r0: Viewer _ GetRoot[v0]; r1: Viewer _ GetRoot[v1]; r2: Viewer _ GetRoot[v2]; AcquireWriteLocks[r0, r1, r2]; IF r0 = GetRoot[v0] AND r1 = GetRoot[v1] AND r2 = GetRoot[v2] THEN { proc[ ! UNWIND => ReleaseWriteLocks[r0, r1, r2]; AMEvents.Debugging => { IF r0 # NIL THEN MarkViewerWedged[r0]; IF r1 # NIL THEN MarkViewerWedged[r1]; IF r2 # NIL THEN MarkViewerWedged[r2]}; AMEvents.Debugged => { IF r0 # NIL THEN MarkViewerUnWedged[r0]; IF r1 # NIL THEN MarkViewerUnWedged[r1]; IF r2 # NIL THEN MarkViewerUnWedged[r2]}; ]; ReleaseWriteLocks[r0, r1, r2]; RETURN; }; ReleaseWriteLocks[r0, r1, r2]; ENDLOOP; }; CallUnderReadLock: PUBLIC PROC [proc: PROC, viewer: Viewer] = { IF viewer # NIL AND NOT viewer.destroyed THEN DO root: Viewer _ GetRoot[viewer]; AcquireReadLock[root]; proc[ ! UNWIND => ReleaseReadLock[root]; AMEvents.Debugging => MarkViewerWedged[root]; AMEvents.Debugged => MarkViewerUnWedged[root]; ]; ReleaseReadLock[root]; ENDLOOP; }; CallUnderReadLocks: PUBLIC PROC [proc: PROC, v0, v1, v2: Viewer _ NIL] = { DO r0: Viewer _ GetRoot[v0]; r1: Viewer _ GetRoot[v1]; r2: Viewer _ GetRoot[v2]; AcquireReadLocks[r0, r1, r2]; IF r0 = GetRoot[v0] AND r1 = GetRoot[v1] AND r2 = GetRoot[v2] THEN { proc[ ! UNWIND => ReleaseReadLocks[r0, r1, r2]; AMEvents.Debugging => { IF r0 # NIL THEN MarkViewerWedged[r0]; IF r1 # NIL THEN MarkViewerWedged[r1]; IF r2 # NIL THEN MarkViewerWedged[r2]}; AMEvents.Debugged => { IF r0 # NIL THEN MarkViewerUnWedged[r0]; IF r1 # NIL THEN MarkViewerUnWedged[r1]; IF r2 # NIL THEN MarkViewerUnWedged[r2]}; ]; ReleaseReadLocks[r0, r1, r2]; RETURN; }; ReleaseReadLocks[r0, r1, r2]; ENDLOOP; }; AcquireWriteLock: PUBLIC ENTRY PROC [viewer: Viewer] = { ENABLE UNWIND => NULL; IF NOT ReadLockColumn[viewer] THEN RETURN WITH ERROR Wedged[ViewerColumn[viewer]]; WriteLockInternal[viewer]; }; WriteLockInternal: INTERNAL PROC [viewer: Viewer] = { <> IF viewer # NIL THEN { process: PROCESS ~ CurrentProcess[]; UNTIL (viewer.lock.count=0 OR viewer.lock.process=process) DO WAIT lockFree; ENDLOOP; viewer.lock _ [process, viewer.lock.count+1]; }; }; ReleaseWriteLock: PUBLIC ENTRY PROC [viewer: Viewer] = { ENABLE UNWIND => NULL; IF viewer # NIL THEN ReleaseLockInternal[viewer]; }; AcquireWriteLocks: PUBLIC ENTRY PROC [v0, v1, v2: Viewer _ NIL] = { <> ENABLE UNWIND => NULL; c: Column; { [v0, v1, v2] _ LockColumnsForViewers[v0, v1, v2 ! Wedged => {c _ wedged; GO TO wedgedError} ]; EXITS wedgedError => RETURN WITH ERROR Wedged[c]; }; IF v0 # NIL THEN WriteLockInternal[v0]; IF v1 # NIL THEN WriteLockInternal[v1]; IF v2 # NIL THEN WriteLockInternal[v2]; }; ReleaseWriteLocks: PUBLIC ENTRY PROC [v0, v1, v2: Viewer _ NIL] = { <> ENABLE UNWIND => NULL; IF v0 # NIL THEN ReleaseLockInternal[v0]; IF v1 # NIL THEN ReleaseLockInternal[v1]; IF v2 # NIL THEN ReleaseLockInternal[v2]; }; AcquireReadLock: PUBLIC ENTRY PROC [viewer: Viewer] = { ENABLE UNWIND => NULL; IF NOT ReadLockColumn[viewer] THEN RETURN WITH ERROR Wedged[ViewerColumn[viewer]]; ReadLockInternal[viewer]; }; ReadLockInternal: INTERNAL PROC [viewer: Viewer] = { <> process: PROCESS = CurrentProcess[]; UNTIL (viewer.lock.process=NIL OR viewer.lock.process=process) DO WAIT lockFree; ENDLOOP; IF readLocksExclusive THEN viewer.lock.process _ process; <> viewer.lock.count _ viewer.lock.count+1; }; ReleaseReadLock: PUBLIC ENTRY PROC [viewer: Viewer] = { ENABLE UNWIND => NULL; IF viewer # NIL THEN ReleaseLockInternal[viewer]; }; AcquireReadLocks: PUBLIC ENTRY PROC [v0, v1, v2: Viewer _ NIL] = { <> ENABLE UNWIND => NULL; c: Column; { [v0, v1, v2] _ LockColumnsForViewers[v0, v1, v2 ! Wedged => {c _ wedged; GO TO wedgedError} ]; EXITS wedgedError => RETURN WITH ERROR Wedged[c]; }; IF v0 # NIL THEN ReadLockInternal[v0]; IF v1 # NIL THEN ReadLockInternal[v1]; IF v2 # NIL THEN ReadLockInternal[v2]; }; ReleaseReadLocks: PUBLIC ENTRY PROC [v0, v1, v2: Viewer _ NIL] = { <> ENABLE UNWIND => NULL; IF v0 # NIL THEN ReleaseLockInternal[v0]; IF v1 # NIL THEN ReleaseLockInternal[v1]; IF v2 # NIL THEN ReleaseLockInternal[v2]; }; ReleaseLockInternal: INTERNAL PROC [viewer: Viewer] = { ReleaseColumnLockInternal[ViewerColumn[viewer]]; IF viewer.lock.count = 0 THEN Error[]; IF (viewer.lock.count _ viewer.lock.count-1) = 0 THEN { viewer.lock.process _ NIL; BROADCAST lockFree; }; }; <> Error: PROC = { DebuggerSwap.CallDebugger["ViewerLocks bug"L]; }; GetRoot: ENTRY PROC [viewer: Viewer] RETURNS [root: Viewer] = { root _ viewer; IF root # NIL THEN DO next: Viewer _ root.parent; IF next = NIL THEN EXIT; root _ next; ENDLOOP; }; SetReadExclusive: PROC [state: BOOL _ TRUE] = { <> LockViewerTree[]; readLocksExclusive _ state; ReleaseViewerTree[]; }; ClearAllLocks: ENTRY PROC = { <> ClearLock: ViewerOps.EnumProc = { IF v.parent = NIL THEN ViewerOps.EnumerateChildren[v, ClearLock]; v.lock _ [NIL, 0]; }; FOR c: Column IN Column DO columns[c] _ [[NIL, 0], 0]; ENDLOOP; ViewerOps.EnumerateViewers[ClearLock]; treeOwner _ NIL; treeUsers _ 0; BROADCAST lockFree; }; LockColumnsForViewers: INTERNAL PROC [v0, v1, v2: Viewer] RETURNS [Viewer, Viewer, Viewer] = { <> <<1. If any column in the viewers is wedged, then release all of the column locks acquired, and raise ERROR Wedged[column] for the wedged column.>> <<2. If any viewer column changes before all of the locks are acquired, then release all of the acquired column locks and try again. Since column changes are rare, this should eventually succeed.>> <> DO c0, c1, c2: Column _ static; b0, b1, b2: BOOL _ FALSE; FreeAllColumns: INTERNAL PROC = { IF b0 THEN ReleaseColumnLockInternal[c0]; IF b1 THEN ReleaseColumnLockInternal[c1]; IF b2 THEN ReleaseColumnLockInternal[c2]; }; [v0, v1] _ SortViewers2[v0, v1]; [v1, v2] _ SortViewers2[v1, v2]; [v0, v1] _ SortViewers2[v0, v1]; <> IF v0 = NIL THEN EXIT; b0 _ ReadLockColumn[column: c0 _ ViewerColumn[v0]]; IF ~b0 THEN {FreeAllColumns[]; ERROR Wedged[c0]}; IF c0 # ViewerColumn[v0] THEN {FreeAllColumns[]; LOOP}; IF v1 = NIL THEN EXIT; b1 _ ReadLockColumn[column: c1 _ ViewerColumn[v1]]; IF ~b1 THEN {FreeAllColumns[]; ERROR Wedged[c1]}; IF c1 # ViewerColumn[v1] THEN {FreeAllColumns[]; LOOP}; IF v2 = NIL THEN EXIT; b2 _ ReadLockColumn[column: c2 _ ViewerColumn[v2]]; IF ~b2 THEN {FreeAllColumns[]; ERROR Wedged[c2]}; IF c2 # ViewerColumn[v2] THEN {FreeAllColumns[]; LOOP}; EXIT; ENDLOOP; RETURN [v0, v1, v2]; }; SortViewers2: PROC [v0, v1: Viewer] RETURNS [Viewer, Viewer] = INLINE { <> SELECT TRUE FROM v0 = NIL => GO TO swapped; v1 = NIL => {}; ENDCASE => { c0: Column _ IF v0.iconic THEN static ELSE v0.column; c1: Column _ IF v1.iconic THEN static ELSE v1.column; SELECT TRUE FROM c0 > c1 => {}; c0 < c1 => GO TO swapped; LOOPHOLE[v0, CARD] < LOOPHOLE[v1, CARD] => GO TO swapped; ENDCASE; }; RETURN [v0, v1]; EXITS swapped => RETURN [v1, v0]; }; ViewerColumn: PROC [viewer: Viewer] RETURNS [column: Column] = INLINE { RETURN [IF viewer.iconic THEN static ELSE viewer.column]; }; CurrentProcess: PROC RETURNS [PROCESS] = TRUSTED INLINE { RETURN [LOOPHOLE[Process.GetCurrent[]]]; }; END.