org.curjent.impl.agent
Class Deadlocks

java.lang.Object
  extended by org.curjent.impl.agent.Deadlocks

final class Deadlocks
extends Object

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

INSTANCE

static final Deadlocks INSTANCE
Singleton.


lock

private final ReentrantLock lock
Global lock for all operations.


blocked

private final HashMap<Thread,Awaiter> blocked
Maps blocked threads to the messages they are waiting on. Multiple threads may be waiting on the same blocked message.


synched

private final 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). 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

Deadlocks

private Deadlocks()
Enforces the singleton pattern.

Method Detail

blocked

void blocked(Awaiter awaiter,
             boolean synchronous)
Notification from an awaiter that it is blocked. synchronous is true only when called from Message.await().

See Also:
blocked, synched, unblocked()

unblocked

void unblocked()
Notification from an awaiter that it is no longer blocked.

See Also:
blocked(Awaiter, boolean)

deadcheck

void deadcheck()
Called by an awaiter to see if the waiting thread has deadlocked. Throws a DeadlockException if the current thread is deadlocked. See Deadlocks for details.


deadlocked

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. Returns 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.


getBlockingThreads

private Thread[] getBlockingThreads(Thread blockedThread)
Returns the threads blocking the given 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.

See Also:
Deadlocks, Controller.getBlockingThreads(), Controller.deadcheckBlocked()

update

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.

See Also:
propagate(Thread, HashSet, HashSet, HashMap)

propagate

private void propagate(Thread blockingThread,
                       HashSet<Thread> blockedSet,
                       HashSet<Thread> dependentThreads,
                       HashMap<Thread,HashSet<Thread>> isBlocking)
Updates 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:

B is blocking all threads A is blocking, so all 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:

A is blocking all threads C is blocking, so all 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:


deadcheck

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



Copyright 2009-2011 Tom Landon
Apache License 2.0