<<>> <> <> <> <> <> <> <> <> <> <> <<>> DIRECTORY <> <> Process USING [GetCurrent, MsecToTicks, Pause], UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc], ViewerClasses USING [Column, Lock, Viewer], ViewerLocks USING [], ViewerGroupLocks USING [], ViewerOps USING [BlinkDisplay, EnumerateChildren, EnumerateViewers, EnumProc], ViewerPrivate, ViewerSpecs; ViewerLocksImpl: CEDAR MONITOR IMPORTS -- AMEvents2, -- Process, UserProfile, ViewerOps, ViewerSpecs 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; <> debugging: BOOL ¬ TRUE; <> <> <<>> ProfileChanged: UserProfile.ProfileChangedProc ~ { debugging ¬ UserProfile.Boolean["Viewers.debugging", TRUE]; }; lockFree: CONDITION; <> readLocksExclusive: BOOL ¬ FALSE; <> treeOwner: PROCESS ¬ NIL; <> treeUsers: CARDINAL ¬ 0; <> ColumnInfo: TYPE = RECORD [lock: Lock, wedgeCount: CARDINAL]; ColumnInfoInit: TYPE = ColumnInfo ¬ [lock: [process: NIL, count: 0], wedgeCount: 0]; ColumnsRep: TYPE ~ RECORD[SEQUENCE c: Column OF ColumnInfoInit]; columns: REF ColumnsRep ¬ NEW[ColumnsRep[ViewerSpecs.nColumns]]; <> 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 = { <> < NULL;>> process: PROCESS = CurrentProcess[]; DO <> IF treeOwner = process THEN EXIT; <> FOR c: Column IN [Column.FIRST..VAL[ViewerSpecs.nColumns]) DO IF columns[c].wedgeCount # 0 THEN RETURN WITH ERROR Wedged[c]; ENDLOOP; IF treeUsers = 0 THEN { IF treeOwner # NIL THEN ViewerLocksError[]; EXIT; }; WAIT lockFree; ENDLOOP; treeUsers ¬ treeUsers + 1; treeOwner ¬ process; }; ReleaseViewerTree: PUBLIC ENTRY PROC = { <> < NULL;>> IF CurrentProcess[] # treeOwner THEN ViewerLocksError[]; IF treeUsers = 0 THEN ViewerLocksError[]; treeUsers ¬ treeUsers - 1; IF treeUsers=0 THEN treeOwner ¬ NIL; 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] = { <> < MarkColumnWedged[column];>> < MarkColumnUnWedged[column]};>> Wait: ENTRY PROC = {< NULL;>> WAIT lockFree}; AcquireColumnWriteLock[column ! Wedged => {Wait[]; RETRY}]; proc[! UNWIND => ReleaseColumnWriteLock[column]]; ReleaseColumnWriteLock[column]; }; CallUnderColumnLocks: PUBLIC PROC [proc: PROC, c0, c1: Column] = { <> < {MarkColumnWedged[c0]; MarkColumnWedged[c1]};>> < {MarkColumnUnWedged[c0]; MarkColumnUnWedged[c1]};>> <<};>> Wait: ENTRY PROC = {< 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] = { < 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] = { < NULL;>> ReleaseColumnLockInternal[column]; }; ReleaseColumnLockInternal: INTERNAL PROC [column: Column] = { <> <> IF columns[column].lock.count = 0 THEN ViewerLocksError[]; IF treeUsers = 0 THEN ViewerLocksError[]; <> 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 { < NULL;>> RETURN [columns[column].wedgeCount # 0, columns[column].lock.process # NIL]; }; MarkViewerWedged: PUBLIC PROC [viewer: Viewer] = { IF viewer # NIL THEN { viewer.paintingWedged ¬ TRUE; MarkColumnUnWedged[ViewerColumn[viewer]]; }; }; MarkViewerUnWedged: PUBLIC PROC [viewer: Viewer] = { IF viewer # NIL THEN { viewer.paintingWedged ¬ FALSE; MarkColumnUnWedged[ViewerColumn[viewer]]; }; }; MarkColumnWedged: PUBLIC ENTRY PROC [column: Column] = { ENABLE UNWIND => NULL; IF 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] = { < NULL;>> IF 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 THEN DO root: Viewer ¬ GetRoot[viewer]; AcquireWriteLock[root]; IF root # GetRoot[viewer] THEN {ReleaseWriteLock[root]; LOOP}; IF NOT viewer.destroyed THEN proc[ ! UNWIND => ReleaseWriteLock[root]; < MarkViewerWedged[root];>> < 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]; < {>> <> <> <> < {>> <> <> <> ]; ReleaseWriteLocks[r0, r1, r2]; RETURN; }; ReleaseWriteLocks[r0, r1, r2]; ENDLOOP; }; CallUnderReadLock: PUBLIC PROC [proc: PROC, viewer: Viewer] = { IF viewer # NIL THEN DO root: Viewer ¬ GetRoot[viewer]; AcquireReadLock[root]; IF root # GetRoot[viewer] THEN {ReleaseReadLock[root]; LOOP}; IF NOT viewer.destroyed THEN proc[ ! UNWIND => ReleaseReadLock[root]; < MarkViewerWedged[root];>> < MarkViewerUnWedged[root];>> ]; ReleaseReadLock[root]; RETURN; 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]; < {>> <> <> <> < {>> <> <> <> ]; ReleaseReadLocks[r0, r1, r2]; RETURN; }; ReleaseReadLocks[r0, r1, r2]; ENDLOOP; }; AcquireWriteLock: PUBLIC ENTRY PROC [viewer: Viewer] = { < 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] = { < 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] = { <> < 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] = { < 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] = { < 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] = { <> < 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 ViewerLocksError[]; IF (viewer.lock.count ¬ viewer.lock.count-1) = 0 THEN { viewer.lock.process ¬ NIL; BROADCAST lockFree; }; }; <> ViewerLocksError: PROC = { <> <> <> ERROR; }; GetRoot: ENTRY PROC [viewer: Viewer] RETURNS [root: Viewer] = { < NULL;>> 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 = { <> ENABLE UNWIND => NULL; ClearLock: ViewerOps.EnumProc = { IF v.parent = NIL THEN ViewerOps.EnumerateChildren[v, ClearLock]; v.lock ¬ [NIL, 0]; }; FOR c: Column IN [Column.FIRST..VAL[ViewerSpecs.nColumns]) 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[]]]; }; <> UserProfile.CallWhenProfileChanges[ProfileChanged]; END.