Each transaction coordinator has a number of threads that act on it:
|The application thread||The thread that starts the transaction, does work within its scope and then terminates the transaction.|
|The timer thread||The thread responsible for triggering rollback due to timeout.|
|The two-phase commit thread||Two-phase termination is done by a separate thread (unless you explicitly disable this). Imported transactions will have a subordinate coordinator in the transaction service, and this subordinate coordinator will be called by the commit threads of the remote parent transaction. In the case of recursive calls between virtual machines, this will even lead to re-entrant commits (the parent coordinator is in turn a subordinate of its own subordinates).|
|LogAdministrator GUI threads||If you have any log administrators then these will inspect the coordinator(s) in a GUI-spawned thread.|
|The TransactionService thread||The thread that starts and stops the transaction service (usually the main thread of the application).|
This will lead to an endless wait case, since each thread can only complete when it gets the lock its waiting for. But the locks they are waiting for are held by another waiting thread, so these locks are never freed.
There are some guidelines that, if enforced consistently, can avoid deadlocks.
In general, deadlocks will not happen (are impossible) if the following techniques are used throughout the code;:
Merely living up to the first rule will help, but is hardly practical in realistic applications. So the second rule is bound to be relevant as well. However, there is a problem…
The FSM observers (listeners) in the coordinator are problematic: the FSM is by definition a state holder, and its methods require synchronization. Also, the pre-enter mechanism (via listeners) was designed to prevent illegal state transitions, so pre-enter events are dispatched within the synchronized block(s) of code. Consequently, the first rule is violated, and we must absolutely make sure that the second rule holds at all times.Let's look at this in more detail. The FSM callbacks (via the listeners) will call other classes unknown at design time, and within a synchronized block of code. This implies that the FSM object will be locked at the time when another object (the listener) is called. So we are in the (possible) situation where a locked object calls another object, possibly violating the second rule. How can we make sure that this does not give deadlocks? The answer is not so hard: according to the second rule, the order of locking must always be the same for all threads that hold locks in several objects. In the case of the FSM, this means that we should assume that the order of locking is always going to be of the form:
We can't control whether or not other classes call back into the FSM. In fact, this is very likely to happen. However, we can avoid deadlocks if we respect the following rule at all times:
Because this applies to direct and indirect calls, we can restate this as follows:
To enforce this principle and make it clear in the code, please following this convention:
Some deadlocks have occurred in past releases, by violations of these basic rules: