This page explains the most important things you need to understand about concurrent threads and their activities that could interfere (or not).
Each transaction (and coordinator) has a number of threads that act on it:
| Thread | Description |
|---|---|
| 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 monitoring timeout of the transaction. This thread is started alongside the transaction and runs in the background. |
| The two-phase commit thread(s) | Imported transactions are subject to two-phase commit termination by the remote initiator of the transaction. As per standard remoting practices this typically involves separate threads different from the application thread. In normal cases, the remote will start termination only after all application threads have finished. However, there are special cases that involve pending "orphans" for which the remote got a network timeout. Our code checks for this and rejects incoming two-phase commit requests when a local application thread is still active for the transaction. |
| The main thread | The thread that starts and stops the transaction service. |
Our TransactionService class is the main container class for transaction, coordinator and root information. It maintains collections (maps, actually) that allow retrieval of these objects based on their IDs.
In order to avoid memory leaks, it is crucial that all additions to those maps are eventually followed by a removal. While this sounds easy, it can be tricky when there are race conditions of this form:
In order to avoid this, our design needs to respect one main invariant:
There is no meaningful manipulation of transaction state by other threads, as long as the application thread has not completed its work on behalf of a transaction. Completion of this work is signalled by the application thread's calls to either rollback or commit methods on the transaction. Upon completion, the application thread has finished its work for the transaction, meaning it will no longer attempt to call methods that add transaction information in any of the TransactionService's maps.
More precisely, the only state change allowed by other threads during this time lapse is marking the transaction for rollback as the only allowed outcome.
This invariant vastly simplifies the threading model and the code. The trade-off is that application threads must properly call either commit or rollback for every transaction they start, whatever exceptions occur.
During or after system shutdown, no new transactions are allowed to start. This is indicated by a shutdown flag, set by the shutdown method.
| Method | Application Thread(s) | Timer Thread | 2 Phase Commit Thread(s) | Main Thread |
|---|---|---|---|---|
| getCompositeCoordinator | READ ONLY ACCESS | |||
| getParticipant | READ ONLY ACCESS | |||
| getCompositeTransaction | READ ONLY ACCESS | |||
| createCompositeTransaction | READS rootId | - | - | - |
| ADDS rootId, coordId, tid | - | - | (**) | |
| recreateCompositeTransaction | READS rootId | - | - | - |
| ADDS rootId, coordId, tid | - | - | (**) | |
| createSubTransaction | READS rootId | - | - | - |
| ADDS rootId, coordId, tid | - | - | (**) | |
| committed | REMOVES tid | - | - | - |
| rolledback | REMOVES tid | - | - | - |
| entered | REMOVES coordId, rootId | REMOVES coordId, rootId (*) | REMOVES coordId, rootId (*) | - |
| shutdown | - | - | - | Sets shutdown flag |
This class contains only logic that is per-transaction and relevant only during the transaction's active lifetime. As such, there is no concurrency beyond regular application threads - each acting on transactions limited to their own thread's scope and not likely to interfere with each other in significant ways.
-- Guy Pardon - 15 Mar 2024