|
|||||||||
PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |
java.lang.Objectorg.curjent.impl.agent.Deadlocks
final class Deadlocks
Blocked message states and deadlock detection algorithm. This class maintains the set of blocked messages, along with the threads that are waiting on each blocked message. Messages notify this class when they block and unblock. They also request this class to determine if a waiting thread has deadlocked.
If a message is executing, a thread waiting on the message is deadlocked if
the message's thread is deadlocked. On the other hand, if the message is
queued, the waiting thread is deadlocked if the message's agent has no
pending messengers and all of its executing threads are deadlocked. In the
latter case, we need to recheck all waiting threads when a messenger
finishes. See Controller.deadcheckBlocked()
for additional details
and an example scenario.
Only a thread waiting indefinitely is considered deadlocked (see also
Awaiters.await(Awaiter, boolean, org.curjent.agent.CallState, long)
).
Normally when deadlock is detected, the waiting thread is thrown a
DeadlockException
. However, an exception is not thrown when the
thread is making a synchronous call and the agent is executing the message.
Instead, the synchronous call continues to wait indefinitely in order to
simulate a direct method call (see also Message.await()
). In this
case, the other threads involved in the deadlock are notified to check for
deadlock (via Awaiter.deadcheck()
).
The following diagram depicts an example deadlock scenario. This is a simplified diagram showing only the threads. It does not show the messages the threads are waiting on or the associated agents. In particular, note that thread A is blocked on two threads. It is waiting on a message that is queued on an agent with two messengers running on threads B and C.
The table below shows the deadlock detection results for the diagram above.
In this example, thread A has called deadcheck()
. The
algorithm starts with thread A pushed on the evaluation stack. Each time
through the evaluation loop the top of the stack is popped and evaluated.
The blocked-by map accumulates the threads that are blocking each thread, where the key is the blocked thread and the value is the set of threads it is blocked by. The is-blocking map accumulates the threads a thread is blocking, where the key is the blocking thread and the value is the set of threads it is blocking.
Whereas only direct dependents are accumulated in the blocked-by map, all
dependents (direct and indirect) are accumulated in the is-blocking map.
Propagation of indirect dependencies enables quick lookups to verify circular
dependencies. See
propagate(Thread, HashSet, HashSet, HashMap)
for details.
The algorithm terminates immediately if any thread is making progress (i.e., it is not blocked). The thread evaluating deadlock is only deadlocked if all blocking threads (direct and indirect) are also blocked.
Stack | Blocked-By | Is-Blocking | Evaluation |
---|---|---|---|
A | Pop A A is blocked by B and C Push A for re-evaluation Push B and C for evaluation Propagate dependencies |
||
A, B, C | A -> B, C | B -> A C -> A |
Pop C C is blocked by D Push C for re-evaluation Push D for evaluation Propagate dependencies |
A, B, C, D | A -> B, C C -> D |
B -> A C -> A D -> C, A |
Pop D D is blocked by A Push D for re-evaluation Propagate dependencies |
A, B, C, D | A -> B, C C -> D D -> A |
B -> A C -> A, D, C D -> C, A, D A -> D, C, A |
Pop D D is deadlocked |
A, B, C | A -> B, C C -> D D -> A |
B -> A C -> A, D, C D -> C, A, D A -> D, C, A |
Pop C C is deadlocked |
A, B | A -> B, C C -> D D -> A |
B -> A C -> A, D, C D -> C, A, D A -> D, C, A |
Pop B B is blocked by A Push B for re-evaluation Propagate dependencies |
A, B | A -> B, C C -> D D -> A B -> A |
B -> A, D, C, B C -> A, D, C, B D -> C, A, D, B A -> D, C, A, B |
Pop B B is deadlocked |
A | A -> B, C C -> D D -> A B -> A |
B -> A, D, C, B C -> A, D, C, B D -> C, A, D, B A -> D, C, A, B |
Pop A A is deadlocked |
Since deadlock occurs across agents, this singleton maintains a global lock. To avoid deadlock, this singleton must be called outside any other lock or synchronized block. Specifically, this singleton must be locked before calling into a controller or message, and a controller must be locked before calling into a message.
Field Summary | |
---|---|
private HashMap<Thread,Awaiter> |
blocked
Maps blocked threads to the messages they are waiting on. |
(package private) static Deadlocks |
INSTANCE
Singleton. |
private ReentrantLock |
lock
Global lock for all operations. |
private HashSet<Thread> |
synched
Keeps track of which threads are waiting in a synchronous call (i.e., calls returning a value and therefore implicitly synchronous, as well as calls explicility annotated as Synchronous ). |
Constructor Summary | |
---|---|
private |
Deadlocks()
Enforces the singleton pattern. |
Method Summary | |
---|---|
(package private) void |
blocked(Awaiter awaiter,
boolean synchronous)
Notification from an awaiter that it is blocked. |
(package private) void |
deadcheck()
Called by an awaiter to see if the waiting thread has deadlocked. |
private void |
deadcheck(Thread currentThread,
HashSet<Thread> blockedSet)
Determines whether to throw a DeadlockException or notify
the set of blocked threads to perform their own deadcheck. |
private boolean |
deadlocked(Thread blockedThread,
HashSet<Thread> blockingSet,
HashMap<Thread,HashSet<Thread>> isBlocking)
Checks if a thread and the threads blocking it are all deadlocked. |
private Thread[] |
getBlockingThreads(Thread blockedThread)
Returns the threads blocking the given blockedThread . |
private void |
propagate(Thread blockingThread,
HashSet<Thread> blockedSet,
HashSet<Thread> dependentThreads,
HashMap<Thread,HashSet<Thread>> isBlocking)
Updates isBlocking to include circular dependencies. |
(package private) void |
unblocked()
Notification from an awaiter that it is no longer blocked. |
private void |
update(Thread blockedThread,
Thread[] blockingThreads,
ArrayList<Thread> stack,
HashMap<Thread,HashSet<Thread>> blockedBy,
HashMap<Thread,HashSet<Thread>> isBlocking)
Updates the deadlock evaluation state given a blocked thread and the threads blocking it. |
Methods inherited from class java.lang.Object |
---|
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Field Detail |
---|
static final Deadlocks INSTANCE
private final ReentrantLock lock
private final HashMap<Thread,Awaiter> blocked
private final HashSet<Thread> synched
Synchronous
). This is only
possible within Message.await()
. It does not apply, for instance,
to threads waiting on pending messages
(Message.awaitPending(long)
) or the get method of a
Future
(Message.get(long, java.util.concurrent.TimeUnit)
).
Constructor Detail |
---|
private Deadlocks()
Method Detail |
---|
void blocked(Awaiter awaiter, boolean synchronous)
synchronous
is true
only when called from Message.await()
.
blocked
,
synched
,
unblocked()
void unblocked()
blocked(Awaiter, boolean)
void deadcheck()
DeadlockException
if the current thread is deadlocked. See
Deadlocks
for details.
private boolean deadlocked(Thread blockedThread, HashSet<Thread> blockingSet, HashMap<Thread,HashSet<Thread>> isBlocking)
true
only if all are deadlocked.
For example, when thread D is popped the second time in the scenario
outlined in Deadlocks
, D is blocking itself, so D is deadlocked.
Furthermore, D is blocked by A, and A is blocking itself, so it is
deadlocked, as well. Since D, the blockedThread
, is
deadlocked, and all threads blocking it, the blockingSet
,
are also deadlocked, the result is true
.
private Thread[] getBlockingThreads(Thread blockedThread)
blockedThread
.
Returns null
if it is not blocked or if the awaiter it is
waiting on is not blocked.
If the blocked thread really is blocked and the awaiter the thread is waiting on is running, only the awaiter's execution thread is returned. If the awaiter is queued instead, then all threads of the awaiter's agent are returned.
Deadlocks
,
Controller.getBlockingThreads()
,
Controller.deadcheckBlocked()
private void update(Thread blockedThread, Thread[] blockingThreads, ArrayList<Thread> stack, HashMap<Thread,HashSet<Thread>> blockedBy, HashMap<Thread,HashSet<Thread>> isBlocking)
blockingThread
is pushed on the stack for
evaluation (unless it is already on the stack
for
re-evaluation).blockedBy
is updated to map the
blockedThread
to the set of blockingThreads
.isBlocking
is updated to map each
blockingThread
to the blockedThread
.isBlocking
.
propagate(Thread, HashSet, HashSet, HashMap)
private void propagate(Thread blockingThread, HashSet<Thread> blockedSet, HashSet<Thread> dependentThreads, HashMap<Thread,HashSet<Thread>> isBlocking)
isBlocking
to include circular dependencies. For
example, in the following diagram thread B is blocking thread A, so B is
also blocking all threads that A is blocking, directly and indirectly,
including X and Y.
For the parameters in this first example:
blockingThread
is B.blockedSet
is the set of threads B is blocking,
initially just A on entry.dependentThreads
is the set of threads A is blocking,
including X and Y.isBlocking
is the map of threads to the threads they
block, including the mapping of blockingThread
to
blockedSet
.dependentThreads
(the set of threads A is blocking, namely X
and Y) are added to blockedSet
(the set of threads B is
blocking, initially just A) such that blockedSet
contains X,
Y and A on exit.
If we extend the example so that C is blocking B, and A is blocking C, as depicted in the next diagram, then A is indirectly blocking itself; i.e., A is potentially deadlocked.
For the parameters in this second example:
blockingThread
is A.blockedSet
is the set of threads A is blocking,
initially X, Y and C on entry.dependentThreads
is the set of threads C is blocking,
including X, Y, A and B.dependentThreads
(the set of threads C is blocking,
including X, Y, A and B) are added to blockedSet
(the set of
threads A is blocking, initially X, Y and C) such that
blockedSet
contains X, Y, A, B and C on exit.
Furthermore, in this second example we need to propagate the circular
relationships to the relevant threads A is blocking. We add all threads
in blockedSet
(the threads A is blocking) to the set of
threads each thread in blockedSet
is blocking so long as
that thread is also in a circular relationship with
blockingThread
. As we iterate through
blockedSet
, we find that B and C are blocking A, so the set
of threads B and C are blocking are also updated to include X, Y, A, B
and C on exit. On the other hand, X and Y are not in a circular
relationship with A, so the set of threads they are blocking are left
unchanged.
For our second example, isBlocking
has the following
mappings on exit:
private void deadcheck(Thread currentThread, HashSet<Thread> blockedSet)
DeadlockException
or notify
the set of blocked threads to perform their own deadcheck. See
Deadlocks
for details.
Multiple calls may throw if they all detect deadlock before one of the others unblocks and allows its messenger to execute the next message or exit.
|
|||||||||
PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |