TEditLocksImpl.mesa
Copyright Ó 1985, 1987, 1991 by Xerox Corporation. All rights reserved.
Michael Plass, October 16, 1987 5:00:29 pm PDT
Russ Atkinson (RRA) June 18, 1985 4:01:34 pm PDT
DIRECTORY
MonitoredQueue USING [AddToSet],
NodeProps USING [GetProp, NullCopy, NullRead, NullWrite, PutProp, Register],
Process USING [GetCurrent],
Rope USING [ROPE],
TEditDocument USING [GetViewerForRoot, SpinAndLock, TEditDocumentData, Unlock],
TEditLocks USING [Access, LockRecord, LockRef],
TEditRefresh USING [dirtyDocs],
TextNode USING [Ref],
ViewerClasses USING [Lock, Viewer],
ViewerLocks USING [LockOrder],
ViewerPrivate USING [AcquireWriteLock, ReleaseWriteLock];
TEditLocksImpl: CEDAR MONITOR LOCKS lock USING lock: LockRef
IMPORTS NodeProps, Process, MonitoredQueue, TEditDocument, 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;
Governs the locking of the root viewer for non-split viewers
lockSplitViewer: BOOL ¬ TRUE;
Governs the locking of the root viewer for split viewers
LockBoth: PUBLIC PROC [doc1, doc2: TextNode.Ref, 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.Ref, 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 ¬ 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.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 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.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 ¬ 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 => {
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: BOOL ¬ FALSE] RETURNS [lock: LockRef] = {
IF ~TEditDocument.SpinAndLock[tdd, "LockDocAndTdd1", interrupt, defer] THEN
must lock before read tdd.text
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] = {
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;
};
GetLock: PROC [root: TextNode.Ref] RETURNS [lock: LockRef] ~ INLINE --gfi saver-- {
RETURN [LockedGetLock[privateLock, root]]
};
privateLock: LockRef ~ NEW[LockRecord]; -- used only to lock GetLock
lastRoot: TextNode.Ref ¬ NIL;
lastLock: LockRef ¬ NIL;
LockedGetLock: ENTRY PROC [lock: LockRef, root: TextNode.Ref] RETURNS [LockRef] ~ {
ENABLE UNWIND => NULL;
IF root = NIL THEN RETURN [NIL];
IF root = lastRoot THEN RETURN [lastLock];
lastRoot ¬ root;
lastLock ¬ NARROW[NodeProps.GetProp[root, $DocumentLock]];
IF lastLock # NIL THEN RETURN [lastLock];
lastLock ¬ NEW[LockRecord];
NodeProps.PutProp[root, $DocumentLock, lastLock];
RETURN [lastLock]
};
NodeProps.Register[$DocumentLock, NodeProps.NullRead, NodeProps.NullWrite, NodeProps.NullCopy];
Don't copy document lock or big trouble.
END.