HHH-8739 - Tracking of JTA Synch registration thread

This commit is contained in:
Steve Ebersole 2013-11-22 16:28:43 -06:00
parent 1b3829a95d
commit 9c96e23488
2 changed files with 179 additions and 5 deletions

View File

@ -41,7 +41,6 @@ public class SynchronizationCallbackCoordinatorTrackingImpl extends Synchronizat
// magic numbers :(
private static final int NO_STATUS = -1;
private static final long NO_THREAD_ID = Long.MIN_VALUE;
private volatile long registrationThreadId;
private volatile int delayedCompletionHandlingStatus;
@ -58,11 +57,8 @@ public class SynchronizationCallbackCoordinatorTrackingImpl extends Synchronizat
// 1) on initialization, and
// 2) after "after completion" handling is finished.
//
// Here we use that to "clear out" all 'delayed after-completion" state. The registrationThreadId will
// "lazily" be re-populated on the next pulse call to allow for the potential of the next Session transaction
// occurring on a different thread (though that transaction would need to completely operate on that thread).
// Here we use that to "clear out" all 'delayed after-completion" state.
delayedCompletionHandlingStatus = NO_STATUS;
registrationThreadId = NO_THREAD_ID;
}
@Override

View File

@ -0,0 +1,178 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2013, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.jpa.test.transaction;
import javax.persistence.EntityManager;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.Ignore;
import org.junit.Test;
import org.hibernate.testing.jta.TestingJtaBootstrap;
import org.hibernate.testing.jta.TestingJtaPlatformImpl;
import static org.junit.Assert.fail;
/**
* Recreate test failure that occurs when three threads share the same entity manager and
* one of them calls set rollback only on its transaction.
*
* @author Scott Marlow
*/
public class TransactionRolledBackInDifferentThreadTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected void addConfigOptions(Map options) {
super.addConfigOptions(options);
TestingJtaBootstrap.prepare(options);
options.put(AvailableSettings.TRANSACTION_TYPE, "JTA");
}
@Test
public void testTransactionRolledBackInDifferentThreadFailure() throws Exception {
/**
* The three test threads share the same entity manager.
* The main test thread creates an EntityManager, joins it to the transaction and ends the transaction.
* Test thread 1 joins the EntityManager to its transaction, sets rollbackonly and ends the transaction.
* Test thread 2 attempts to join the EntityManager to its transaction but will fail with a
* HibernateException("Transaction was rolled back in a different thread!")
*/
// main test thread
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin();
final EntityManager em = entityManagerFactory().createEntityManager();
em.joinTransaction();
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().commit();
// will be set to the failing exception
final HibernateException[] transactionRolledBackInDifferentThreadException = new HibernateException[2];
transactionRolledBackInDifferentThreadException[0] = transactionRolledBackInDifferentThreadException[1] = null;
// background test thread 1
final Runnable run1 = new Runnable() {
@Override
public void run() {
try {
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin();
em.joinTransaction();
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().setRollbackOnly();
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().commit();
} catch (javax.persistence.PersistenceException e) {
if (e.getCause() instanceof HibernateException &&
e.getCause().getMessage().equals("Transaction was rolled back in a different thread!")) {
/**
* Save the exception for the main test thread to fail
*/
e.printStackTrace(); // show the error first
transactionRolledBackInDifferentThreadException[0] = (HibernateException) e.getCause();
}
} catch (RollbackException ignored) {
// expected to see RollbackException: ARJUNA016053: Could not commit transaction.
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
try {
if (TestingJtaPlatformImpl.INSTANCE.getTransactionManager().getStatus() != Status.STATUS_NO_TRANSACTION)
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().rollback();
} catch (SystemException ignore) {
}
}
}
};
// test thread 2
final Runnable run2 = new Runnable() {
@Override
public void run() {
try {
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin();
/**
* the following call to em.joinTransaction() will throw:
* org.hibernate.HibernateException: Transaction was rolled back in a different thread!
*/
em.joinTransaction();
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().commit();
} catch (javax.persistence.PersistenceException e) {
if (e.getCause() instanceof HibernateException &&
e.getCause().getMessage().equals("Transaction was rolled back in a different thread!")) {
/**
* Save the exception for the main test thread to fail
*/
e.printStackTrace(); // show the error first
transactionRolledBackInDifferentThreadException[1] = (HibernateException) e.getCause();
}
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
try {
if (TestingJtaPlatformImpl.INSTANCE.getTransactionManager().getStatus() != Status.STATUS_NO_TRANSACTION)
TestingJtaPlatformImpl.INSTANCE.getTransactionManager().rollback();
} catch (SystemException ignore) {
}
}
}
};
Thread thread = new Thread(run1, "test thread1");
thread.start();
thread.join();
Thread thread2 = new Thread(run2, "test thread2");
thread2.start();
thread2.join();
// show failure for exception caught in run2.run()
if (transactionRolledBackInDifferentThreadException[0] != null
|| transactionRolledBackInDifferentThreadException[1] != null)
{
fail("failure in test thread 1 = " +
(transactionRolledBackInDifferentThreadException[0] != null ? transactionRolledBackInDifferentThreadException[0].getMessage() : "(none)")
+ ", failure in test thread 2 = " +
(transactionRolledBackInDifferentThreadException[1] != null ? transactionRolledBackInDifferentThreadException[1].getMessage() : "(none)")
);
}
em.close();
}
@Override
public Class[] getAnnotatedClasses() {
return new Class[]{
};
}
}