JBAS-1836 - contentious transaction enlistment and pooling
adrian.brock Jun 24, 2005 12:08 PMSo I finally managed to produce a stress test that reliably reproduces the
race condition I knew exists in TxManager.
The race condition goes something like this (two enlists on the same thread):
Thread1: getConnection and enlist in transaction
Thread1: first use of transaction so flags == TMNOFLAGS
Thread1: unlock() but then a context switch before the XAResource.start()
Thread2: getConnection from sameRM and enlist in transaction
Thread2: second use in transaction so flags == TMJOIN
Thread2: does XAResource.start()
Oops, JOIN before START => XAER_PROTO (protocol error).
To avoid this problem, I have changed TxConnectionEventListener.enlist()
to serialize contentious enlistments.
This is a small overhead of an extra ArrayList allocation in the non-contentious
case (besides the transaction local locks that are required anyway).
Comment from the code:
public void enlist() throws SystemException
{
// This method is a bit convulted, but it has to be such because
// there is a race condition in the transaction manager where it
// unlocks during the enlist of the XAResource. It does this
// to avoid distributed deadlocks and to ensure the transaction
// timeout can fail a badly behaving resource during the enlist.
//
// When two threads in the same transaction are trying to enlist
// connections they could be from the same resource manager
// or even the same connection when tracking the connection by transaction.
//
// For the same connection, we only want to do the real enlist once.
// For two connections from the same resource manager we don't
// want the join before the initial start request.
//
// The solution is to build up a list of unenlisted resources
// in the TransactionSynchronizer and then choose one of the
// threads that is contending in the transaction to enlist them
// in order. The actual order doesn't really matter as it is the
// transaction manager that calculates the enlist flags and determines
// whether the XAResource was already enlisted.
//
// Once there are no unenlisted resources the threads are released
// to return the result of the enlistments.
//
// In practice, a thread just takes a snapshot to try to avoid one
// thread having to do all the work. If it did not do them all
// the next waiting thread will do the next snapshot until there
// there is either no snapshot or no waiting threads.
//
// A downside to this design is a thread could have its resource enlisted by
// an earlier thread while it enlists some later thread's resource.
// Since they are all a part of the same transaction, this is probably
// not a real issue.