Copyright (C) 1992, Digital Equipment Corporation
All rights reserved.
See the file COPYRIGHT for a full description.
Last modified on Wed Oct 6 09:32:55 PDT 1993 by sfreeman
MODULE Completion;
IMPORT CompletionSeq, Picture, Thread;
REVEAL
T = Public BRANDED OBJECT
cond : Thread.Condition := NIL;
count : CARDINAL := 0;
next : T := NIL; (* for free list *)
freeProc : Picture.FreeProc := NIL;
freeParam: REFANY := NIL;
OVERRIDES
init := Init;
inc := Inc;
dec := Dec;
isFree := IsFree;
waitUntilFree := WaitUntilFree;
END;
PROCEDURE Init (c : T;
initialCount := 1;
freeProc : Picture.FreeProc := NIL;
freeParam : REFANY := NIL ): T =
BEGIN
IF c.cond = NIL THEN c.cond := NEW(Thread.Condition); END;
c.count := initialCount;
c.freeProc := freeProc;
c.freeParam := freeParam;
RETURN c;
END Init;
PROCEDURE Inc (c: T) =
BEGIN
LOCK c DO INC(c.count); END;
END Inc;
PROCEDURE Dec (c: T) =
VAR signal := FALSE;
BEGIN
LOCK c DO
CASE c.count OF
| 0 => RETURN;
| 1 => DEC(c.count); signal := TRUE;
ELSE
DEC(c.count);
END;
END;
IF signal THEN
IF c.freeProc # NIL THEN
SetupCallback(c)
ELSE
Thread.Broadcast(c.cond);
END;
END;
END Dec;
PROCEDURE IsFree (c: T): BOOLEAN =
BEGIN
RETURN c.count = 0;
END IsFree;
PROCEDURE WaitUntilFree (c: T) RAISES {Thread.Alerted} =
BEGIN
LOCK c DO WHILE c.count > 0 DO Thread.AlertWait(c, c.cond); END; END;
END WaitUntilFree;
PROCEDURE New (): T =
VAR res: T := NIL;
BEGIN
LOCK freeMu DO
IF free # NIL THEN res := free; free := res.next; END;
END;
IF res = NIL THEN res := NEW(T); END;
RETURN res;
END New;
PROCEDURE Dispose (c: T) =
BEGIN
<* ASSERT c.count = 0 *>
LOCK freeMu DO c.next := free; free := c; END;
END Dispose;
VAR
freeMu := NEW(MUTEX);
free : T := NIL;
-- callback handling --
when we need to call the freeProc for a Completion.T, we do it in
another thread to avoid locking problems. This thread is started the
first time we need to call a freeProc. Its creation is protected by
/mu/. Completions waiting for their callbacks to be called are put into
the Sequence /seq/ which is also protected by /mu/
VAR
mu := NEW(MUTEX);
thread : Thread.T := NIL;
seq := NEW(CompletionSeq.T).init();
seqNotEmpty := NEW(Thread.Condition);
start the callback thread if necessary. Add the T to its input queue
PROCEDURE SetupCallback (c: T) =
VAR signal := FALSE;
BEGIN
LOCK mu DO
IF thread = NIL THEN
thread := Thread.Fork(NEW(Thread.Closure, apply := Apply));
END;
seq.addhi(c);
IF seq.size() = 1 THEN signal := TRUE; END;
END;
IF signal THEN Thread.Signal(seqNotEmpty); END;
END SetupCallback;
pull Ts off the queue and call each callback and notify any other
threads waiting for the T
PROCEDURE Apply (<*UNUSED*>cl: Thread.Closure): REFANY =
VAR c: T;
BEGIN
LOOP
LOCK mu DO
WHILE seq.size() = 0 DO Thread.Wait(mu, seqNotEmpty); END;
c := seq.remlo();
END;
<* ASSERT c.freeProc # NIL *>
c.freeProc(c.freeParam);
Thread.Broadcast(c.cond);
Dispose(c);
END;
END Apply;
BEGIN
END Completion.