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];
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;
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.