<> <> <> <> DIRECTORY TEditLocks, TEditLocksPrivate, Process USING [GetCurrent], MonitoredQueue USING [AddToSet], NodeProps, Rope USING [ROPE], TextNode USING [Ref], TEditDocument USING [GetViewerForRoot, SpinAndLock, TEditDocumentData, Unlock], TEditRefresh USING [dirtyDocs], ViewerClasses USING [Viewer], ViewerLocks USING [LockOrder], ViewerPrivate; TEditLocksImpl: CEDAR MONITOR LOCKS lock USING lock: LockRef IMPORTS NodeProps, Process, MonitoredQueue, TEditDocument, TEditLocksPrivate, TEditRefresh, ViewerLocks, ViewerPrivate EXPORTS TEditLocks = BEGIN OPEN TEditLocks, TEditDocument; ROPE: TYPE = Rope.ROPE; Viewer: TYPE = ViewerClasses.Viewer; locksBothCount: INT _ 0; dubious: INT _ 0; lockRootViewer: BOOL _ TRUE; <> lockSplitViewer: BOOL _ TRUE; <> LockBoth: PUBLIC PROC [doc1, doc2: TextNode.Ref, who: Rope.ROPE, access1, access2: Access _ write] RETURNS [lock1, lock2: LockRef] = { <> <> locksBothCount _ locksBothCount + 1; IF (doc1=doc2 AND access1=write) OR (doc1 # doc2 AND LockOrder[doc1, doc2]) THEN { lock1 _ Lock[doc1, who, access1]; lock2 _ Lock[doc2, who, access2]; } ELSE { lock2 _ Lock[doc2, who, access2]; lock1 _ Lock[doc1, who, access1]; }; }; <<>> Lock: PUBLIC PROC [doc: TextNode.Ref, who: Rope.ROPE, access: Access _ write] RETURNS [lock: LockRef] = { IF doc=NIL THEN ERROR; DO v: Viewer _ TEditDocument.GetViewerForRoot[doc]; <> root: Viewer _ GetRoot[v]; localViewer: Viewer _ NIL; IF (lock _ TEditLocksPrivate.GetLock[doc])=NIL THEN RETURN; IF root # NIL THEN { IF lockRootViewer AND root.link = NIL THEN localViewer _ root; IF lockSplitViewer AND root.link # NIL THEN localViewer _ root; IF localViewer # NIL THEN ViewerPrivate.AcquireWriteLock[localViewer]; }; IF root = GetRoot[v] THEN { <> IF TEditDocument.GetViewerForRoot[doc] = v THEN { <> LockIt[lock, who, access, doc]; IF TEditDocument.GetViewerForRoot[doc] = v THEN { <> NodeProps.PutProp[doc, $LockedViewer, localViewer]; EXIT; }; <> NodeProps.PutProp[doc, $LockedViewer, NIL]; UnlockIt[lock, doc]; }; }; <> IF localViewer # NIL THEN ViewerPrivate.ReleaseWriteLock[localViewer]; ENDLOOP; }; LockIt: ENTRY PROC [lock: LockRef, who: Rope.ROPE, access: Access, doc: TextNode.Ref] = { ENABLE UNWIND => NULL; myProcess: PROCESS; TRUSTED {myProcess _ LOOPHOLE[Process.GetCurrent[]]}; IF access=read THEN { IF lock.process # myProcess THEN WHILE lock.process # NIL DO <> WAIT lock.unlocked; ENDLOOP } ELSE { <> IF lock.process # myProcess THEN { <> IF lock.count > 0 THEN { <> lock.waitingForWrite _ lock.waitingForWrite+1; WHILE lock.count > 0 DO <> WAIT lock.unlocked; ENDLOOP; lock.waitingForWrite _ lock.waitingForWrite-1 }; lock.process _ myProcess }}; IF lock.count=0 THEN lock.whoFirst _ who ELSE lock.whoLast _ who; lock.count _ lock.count+1; lock.maxCount _ MAX[lock.maxCount, lock.count]; }; LockOrder: PUBLIC PROC [doc1, doc2: TextNode.Ref] RETURNS [BOOL] = { v1: Viewer _ IF doc1 = NIL THEN NIL ELSE TEditDocument.GetViewerForRoot[doc1]; v2: Viewer _ IF doc2 = NIL THEN NIL ELSE TEditDocument.GetViewerForRoot[doc2]; IF v1 # v2 THEN RETURN [ViewerLocks.LockOrder[v1, v2]]; RETURN [LOOPHOLE[doc1, INT] <= LOOPHOLE[doc2, INT]] }; Unlock: PUBLIC PROC [doc: TextNode.Ref] = { lock: LockRef; IF doc=NIL THEN ERROR; IF (lock _ TEditLocksPrivate.GetLock[doc])=NIL THEN RETURN; UnlockIt[lock, doc]; }; UnlockIt: ENTRY PROC [lock: LockRef, doc: TextNode.Ref] = { ENABLE UNWIND => NULL; v: Viewer _ NARROW[NodeProps.GetProp[doc, $LockedViewer]]; IF lock=NIL THEN RETURN; IF lock.count <= 0 THEN ERROR; TRUSTED {IF lock.process # NIL AND lock.process # Process.GetCurrent[] THEN ERROR}; SELECT TRUE FROM (lock.count _ lock.count-1) > 0 => {}; ENDCASE => { SELECT TRUE FROM lock.process # NIL => { <> viewer: ViewerClasses.Viewer _ TEditDocument.GetViewerForRoot[doc]; IF viewer # v THEN dubious _ dubious + 1; IF viewer # NIL THEN { FOR v: ViewerClasses.Viewer _ viewer, v.link UNTIL v = NIL DO WITH v.data SELECT FROM tdd: TEditDocument.TEditDocumentData => tdd.dirty _ TRUE; ENDCASE; IF v.link = viewer THEN EXIT; ENDLOOP; MonitoredQueue.AddToSet[doc, TEditRefresh.dirtyDocs]; }; lock.process _ NIL; }; ENDCASE; NodeProps.PutProp[doc, $LockedViewer, NIL]; lock.whoFirst _ lock.whoLast _ NIL; lock.maxCount _ 0; BROADCAST lock.unlocked; }; IF v # NIL THEN ViewerPrivate.ReleaseWriteLock[v]; }; LockDocAndTdd: PUBLIC PROC [tdd: TEditDocument.TEditDocumentData, who: Rope.ROPE, access: Access _ write, interrupt, defer: BOOL _ FALSE] RETURNS [lock: LockRef] = { IF ~TEditDocument.SpinAndLock[tdd, "LockDocAndTdd1", interrupt, defer] THEN <> RETURN [NIL]; DO doc: TextNode.Ref _ tdd.text; TEditDocument.Unlock[tdd]; -- must unlock tdd before try to lock the document. IF doc=NIL THEN RETURN [NIL]; lock _ Lock[doc, who, access]; IF ~TEditDocument.SpinAndLock[tdd, "LockDocAndTdd2", interrupt, defer] THEN { UnlockIt[lock, doc]; RETURN [NIL] }; IF tdd.text = doc THEN RETURN; -- all went well UnlockIt[lock, doc]; -- document changed during the time we had tdd unlocked ENDLOOP; }; UnlockDocAndTdd: PUBLIC PROC [tdd: TEditDocument.TEditDocumentData] = { IF tdd.text#NIL THEN Unlock[tdd.text]; TEditDocument.Unlock[tdd]; }; WaitingForWrite: PUBLIC PROC [lock: LockRef] RETURNS [BOOL] = { <> RETURN [lock.waitingForWrite > 0]; }; GetRoot: 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; }; END.