TEditLocksImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Michael Plass, March 21, 1985 3:06:44 pm PST
Russ Atkinson (RRA) June 18, 1985 4:01:34 pm PDT
Doug Wyatt, September 2, 1986 4:36:50 pm PDT
DIRECTORY
MonitoredQueue USING [AddToSet],
NodeProps USING [GetProp, PutProp],
Process USING [GetCurrent],
Rope USING [ROPE],
TEditDocument USING [GetViewerForRoot, SpinAndLock, TEditDocumentData, Unlock],
TEditLocks USING [Access, LockRef],
TEditLocksPrivate USING [GetLock],
TEditRefresh USING [dirtyDocs],
TextNode USING [Node],
ViewerClasses USING [Viewer],
ViewerLocks USING [LockOrder],
ViewerPrivate USING [AcquireWriteLock, ReleaseWriteLock];
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: BOOLTRUE;
Governs the locking of the root viewer for non-split viewers
lockSplitViewer: BOOLTRUE;
Governs the locking of the root viewer for split viewers
LockBoth: PUBLIC PROC [doc1, doc2: TextNode.Node, who: Rope.ROPE, access1, access2: Access ← write] RETURNS [lock1, lock2: LockRef] = {
Like two calls on Lock, but orders the calls according to LockOrder.
RRA: this is not used, I think!
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.Node, who: Rope.ROPE, access: Access ← write] RETURNS [lock: LockRef] = {
IF doc=NIL THEN ERROR;
DO
v: Viewer ← TEditDocument.GetViewerForRoot[doc];
Be prepared for v = NIL
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 {
At this point we have a lock on the correct viewer (if asked to do so)
IF TEditDocument.GetViewerForRoot[doc] = v THEN {
The viewer is still the right one, so try to lock the document itself
LockIt[lock, who, access, doc];
IF TEditDocument.GetViewerForRoot[doc] = v THEN {
Success! So note the current association for unlocking.
NodeProps.PutProp[doc, $LockedViewer, localViewer];
EXIT;
};
Rats! The viewer changed while we were trying for the lock, so let's undo everything.
NodeProps.PutProp[doc, $LockedViewer, NIL];
UnlockIt[lock, doc];
};
};
At this point we have a viewer lock, but on the wrong viewer!
IF localViewer # NIL THEN ViewerPrivate.ReleaseWriteLock[localViewer];
ENDLOOP;
};
LockIt: ENTRY PROC [lock: LockRef, who: Rope.ROPE, access: Access, doc: TextNode.Node] = {
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 for write lock to clear
WAIT lock.unlocked;
ENDLOOP
}
ELSE {
want write access
IF lock.process # myProcess THEN {
I don't have a write lock on it already
IF lock.count > 0 THEN {
doc is locked
lock.waitingForWrite ← lock.waitingForWrite+1;
WHILE lock.count > 0 DO
wait for all locks to clear
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.Node] 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.Node] = {
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.Node] = {
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 => {
had write lock
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: BOOLFALSE] RETURNS [lock: LockRef] = {
IF ~TEditDocument.SpinAndLock[tdd, "LockDocAndTdd1", interrupt, defer] THEN
must lock before read tdd.text
RETURN [NIL];
DO
doc: TextNode.Node ← 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] = {
Returns true iff some process is waiting to get a write lock for the document.
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.