diff --git a/btm/src/main/java/bitronix/tm/Configuration.java b/btm/src/main/java/bitronix/tm/Configuration.java
index de351e3e..0d6e735d 100644
--- a/btm/src/main/java/bitronix/tm/Configuration.java
+++ b/btm/src/main/java/bitronix/tm/Configuration.java
@@ -77,6 +77,7 @@ public class Configuration implements Service {
private volatile String resourceConfigurationFilename;
private volatile boolean conservativeJournaling;
private volatile String jdbcProxyFactoryClass;
+ private volatile boolean quickSuspend;
protected Configuration() {
try {
@@ -125,6 +126,7 @@ protected Configuration() {
resourceConfigurationFilename = getString(properties, "bitronix.tm.resource.configuration", null);
conservativeJournaling = getBoolean(properties, "bitronix.tm.conservativeJournaling", false);
jdbcProxyFactoryClass = getString(properties, "bitronix.tm.jdbcProxyFactoryClass", "auto");
+ quickSuspend = getBoolean(properties, "bitronix.tm.quickSuspend", false);
} catch (IOException ex) {
throw new InitializationException("error loading configuration", ex);
}
@@ -685,6 +687,29 @@ public void setJdbcProxyFactoryClass(String jdbcProxyFactoryClass) {
this.jdbcProxyFactoryClass = jdbcProxyFactoryClass;
}
+ /**
+ * Should suspend calls on the transaction manager use all local operations or involve the resources
+ *
Property name:
bitronix.tm.quickSuspend - (defaults to true)
+ * @return true if suspend calls should operate locally only, without resource notification
+ */
+ public boolean isQuickSuspend() {
+ return quickSuspend;
+ }
+
+ /**
+ * Set if suspend calls on the transaction manager use all local operations or involve the resources
+ * Property name:
bitronix.tm.quickSuspend - (defaults to true)
+ * @return true if suspend calls should operate locally only, without resource notification
+ * @see #isQuickSuspend()
+ * @param quickSuspend true if suspend calls should operate locally only, without resource notification
+ * @return this.
+ */
+ public Configuration setQuickSuspend(boolean quickSuspend) {
+ checkNotStarted();
+ this.quickSuspend = quickSuspend;
+ return this;
+ }
+
/**
* {@link bitronix.tm.resource.ResourceLoader} configuration file name. {@link bitronix.tm.resource.ResourceLoader}
diff --git a/btm/src/main/java/bitronix/tm/internal/XAResourceHolderState.java b/btm/src/main/java/bitronix/tm/internal/XAResourceHolderState.java
index e45842bf..3325e635 100644
--- a/btm/src/main/java/bitronix/tm/internal/XAResourceHolderState.java
+++ b/btm/src/main/java/bitronix/tm/internal/XAResourceHolderState.java
@@ -139,7 +139,35 @@ public boolean isSuspended() {
public boolean isFailed() {
return failed;
}
+
+ public void quickSuspend() throws XAException {
+ if (!this.started) {
+ throw new BitronixXAException("resource hasn't been started, cannot suspend it: " + this, XAException.XAER_PROTO);
+ }
+ if (this.suspended) {
+ throw new BitronixXAException("resource already suspended: " + this, XAException.XAER_PROTO);
+ }
+
+ if (log.isDebugEnabled()) { log.debug("quick suspending " + this); }
+
+ //take effect
+ this.suspended = true;
+ }
+
+ public void quickResume() throws XAException {
+ if (!this.started) {
+ throw new BitronixXAException("resource hasn't been started, cannot quick-resume it: " + this, XAException.XAER_PROTO);
+ }
+ if (!this.suspended) {
+ throw new BitronixXAException("resource hasn't been suspended, cannot quick resume it: " + this, XAException.XAER_PROTO);
+ }
+
+ if (log.isDebugEnabled()) { log.debug("quick resuming " + this); }
+ //take effect
+ this.suspended = false;
+ }
+
public void end(int flags) throws XAException {
boolean ended = this.ended;
boolean suspended = this.suspended;
diff --git a/btm/src/main/java/bitronix/tm/internal/XAResourceManager.java b/btm/src/main/java/bitronix/tm/internal/XAResourceManager.java
index 54a079c6..80b67865 100644
--- a/btm/src/main/java/bitronix/tm/internal/XAResourceManager.java
+++ b/btm/src/main/java/bitronix/tm/internal/XAResourceManager.java
@@ -144,12 +144,23 @@ public boolean delist(XAResourceHolderState xaResourceHolderState, int flag) thr
* @throws XAException if the resource threw an exception during suspend.
*/
public void suspend() throws XAException {
- for (XAResourceHolderState xaResourceHolderState : resources) {
- if (!xaResourceHolderState.isEnded()) {
- if (log.isDebugEnabled()) { log.debug("suspending " + xaResourceHolderState); }
- xaResourceHolderState.end(XAResource.TMSUCCESS);
- }
- } // while
+ if (!TransactionManagerServices.getConfiguration().isQuickSuspend()) {
+ //normal resource interactive suspend
+ for (XAResourceHolderState xaResourceHolderState : resources) {
+ if (!xaResourceHolderState.isEnded()) {
+ if (log.isDebugEnabled()) { log.debug("suspending " + xaResourceHolderState); }
+ xaResourceHolderState.end(XAResource.TMSUCCESS);
+ }
+ } // while
+ } else {
+ //quick suspend support
+ for (XAResourceHolderState xaResourceHolderState : resources) {
+ if (!xaResourceHolderState.isEnded()) {
+ if (log.isDebugEnabled()) { log.debug("quick suspending " + xaResourceHolderState); }
+ xaResourceHolderState.quickSuspend();
+ }
+ } // while
+ }
}
/**
@@ -157,29 +168,38 @@ public void suspend() throws XAException {
* @throws XAException if the resource threw an exception during resume.
*/
public void resume() throws XAException {
- // all XAResource needs to be re-enlisted but this must happen
- // outside the Scheduler's iteration as enlist() can change the
- // collection's content and confuse the iterator.
- List toBeReEnlisted = new ArrayList();
-
- for (XAResourceHolderState xaResourceHolderState : resources) {
- if (log.isDebugEnabled()) { log.debug("resuming " + xaResourceHolderState); }
-
- // If a prepared statement is (re-)used after suspend/resume is performed its XAResource needs to be
- // re-enlisted. This must be done outside this loop or that will confuse the iterator!
- toBeReEnlisted.add(new XAResourceHolderState(xaResourceHolderState));
- }
-
- if (toBeReEnlisted.size() > 0 && log.isDebugEnabled()) log.debug("re-enlisting " + toBeReEnlisted.size() + " resource(s)");
- for (XAResourceHolderState xaResourceHolderState : toBeReEnlisted) {
- if (log.isDebugEnabled()) { log.debug("re-enlisting resource " + xaResourceHolderState); }
- try {
- enlist(xaResourceHolderState);
- xaResourceHolderState.getXAResourceHolder().putXAResourceHolderState(xaResourceHolderState.getXid(), xaResourceHolderState);
- } catch (BitronixSystemException ex) {
- throw new BitronixXAException("error re-enlisting resource during resume: " + xaResourceHolderState, XAException.XAER_RMERR, ex);
- }
- }
+ //normal resource interactive suspend
+ if (!TransactionManagerServices.getConfiguration().isQuickSuspend()) {
+ // all XAResource needs to be re-enlisted but this must happen
+ // outside the Scheduler's iteration as enlist() can change the
+ // collection's content and confuse the iterator.
+ List toBeReEnlisted = new ArrayList();
+
+ for (XAResourceHolderState xaResourceHolderState : resources) {
+ if (log.isDebugEnabled()) { log.debug("resuming " + xaResourceHolderState); }
+
+ // If a prepared statement is (re-)used after suspend/resume is performed its XAResource needs to be
+ // re-enlisted. This must be done outside this loop or that will confuse the iterator!
+ toBeReEnlisted.add(new XAResourceHolderState(xaResourceHolderState));
+ }
+
+ if (toBeReEnlisted.size() > 0 && log.isDebugEnabled()) log.debug("re-enlisting " + toBeReEnlisted.size() + " resource(s)");
+ for (XAResourceHolderState xaResourceHolderState : toBeReEnlisted) {
+ if (log.isDebugEnabled()) { log.debug("re-enlisting resource " + xaResourceHolderState); }
+ try {
+ enlist(xaResourceHolderState);
+ xaResourceHolderState.getXAResourceHolder().putXAResourceHolderState(xaResourceHolderState.getXid(), xaResourceHolderState);
+ } catch (BitronixSystemException ex) {
+ throw new BitronixXAException("error re-enlisting resource during resume: " + xaResourceHolderState, XAException.XAER_RMERR, ex);
+ }
+ }
+ } else {
+ //quick suspend support - all XAResources need to be unsuspended
+ for (XAResourceHolderState xaResourceHolderState : resources) {
+ if (log.isDebugEnabled()) { log.debug("quick resuming " + xaResourceHolderState); }
+ xaResourceHolderState.quickResume();
+ }
+ }
}
/**
diff --git a/btm/src/test/java/bitronix/tm/ConfigurationTest.java b/btm/src/test/java/bitronix/tm/ConfigurationTest.java
index 91c6068e..7c0bd651 100644
--- a/btm/src/test/java/bitronix/tm/ConfigurationTest.java
+++ b/btm/src/test/java/bitronix/tm/ConfigurationTest.java
@@ -97,7 +97,7 @@ public void testToString() {
" forceBatchingEnabled=true, forcedWriteEnabled=true, gracefulShutdownInterval=10, jdbcProxyFactoryClass=auto," +
" jndiTransactionSynchronizationRegistryName=java:comp/TransactionSynchronizationRegistry," +
" jndiUserTransactionName=java:comp/UserTransaction, journal=disk," +
- " logPart1Filename=target/btm1.tlog, logPart2Filename=target/btm2.tlog, maxLogSizeInMb=2," +
+ " logPart1Filename=target/btm1.tlog, logPart2Filename=target/btm2.tlog, maxLogSizeInMb=2, quickSuspend=false," +
" resourceConfigurationFilename=null, serverId=null, skipCorruptedLogs=false, synchronousJmxRegistration=false," +
" warnAboutZeroResourceTransaction=true]";
diff --git a/btm/src/test/java/bitronix/tm/JtaTest.java b/btm/src/test/java/bitronix/tm/JtaTest.java
index b6bb0071..cca0d769 100644
--- a/btm/src/test/java/bitronix/tm/JtaTest.java
+++ b/btm/src/test/java/bitronix/tm/JtaTest.java
@@ -37,19 +37,28 @@ public class JtaTest extends TestCase {
private final static Logger log = LoggerFactory.getLogger(JtaTest.class);
- private BitronixTransactionManager btm;
+ private BitronixTransactionManager _btm;
protected void setUp() throws Exception {
TransactionManagerServices.getConfiguration().setGracefulShutdownInterval(1);
TransactionManagerServices.getConfiguration().setExceptionAnalyzer(DefaultExceptionAnalyzer.class.getName());
- btm = TransactionManagerServices.getTransactionManager();
+ _btm = null;
}
+ protected BitronixTransactionManager getBtm() {
+ if (_btm == null) {
+ _btm = TransactionManagerServices.getTransactionManager();
+ }
+ return _btm;
+ }
+
protected void tearDown() throws Exception {
+ BitronixTransactionManager btm = getBtm();
btm.shutdown();
}
public void testTransactionManagerGetTransaction() throws Exception {
+ BitronixTransactionManager btm = getBtm();
assertNull(btm.getTransaction());
btm.begin();
@@ -66,6 +75,22 @@ public void testTransactionManagerGetTransaction() throws Exception {
// this test also helps verifying MDC support but logs have to be manually checked
public void testSuspendResume() throws Exception {
+ BitronixTransactionManager btm = getBtm();
+ log.info("test starts");
+ btm.begin();
+ log.info("tx begun");
+ Transaction tx = btm.suspend();
+ log.info("tx suspended");
+ btm.resume(tx);
+ log.info("tx resumed");
+ btm.rollback();
+ log.info("test over");
+ }
+
+ // this test also helps verifying MDC support but logs have to be manually checked
+ public void testQuickSuspendResume() throws Exception {
+ TransactionManagerServices.getConfiguration().setQuickSuspend(true);
+ BitronixTransactionManager btm = getBtm();
log.info("test starts");
btm.begin();
log.info("tx begun");
@@ -78,6 +103,7 @@ public void testSuspendResume() throws Exception {
}
public void testTimeout() throws Exception {
+ BitronixTransactionManager btm = getBtm();
btm.setTransactionTimeout(1);
btm.begin();
CountingSynchronization sync = new CountingSynchronization();
@@ -97,6 +123,7 @@ public void testTimeout() throws Exception {
}
public void testMarkedRollback() throws Exception {
+ BitronixTransactionManager btm = getBtm();
btm.begin();
CountingSynchronization sync = new CountingSynchronization();
btm.getTransaction().registerSynchronization(sync);
@@ -115,6 +142,7 @@ public void testMarkedRollback() throws Exception {
}
public void testRecycleAfterSuspend() throws Exception {
+ BitronixTransactionManager btm = getBtm();
PoolingDataSource pds = new PoolingDataSource();
pds.setClassName(LrcXADataSource.class.getName());
pds.setUniqueName("lrc-pds");
@@ -150,7 +178,46 @@ public void testRecycleAfterSuspend() throws Exception {
pds.close();
}
+ public void testRecycleAfterQuickSuspend() throws Exception {
+ TransactionManagerServices.getConfiguration().setQuickSuspend(true);
+ BitronixTransactionManager btm = getBtm();
+ PoolingDataSource pds = new PoolingDataSource();
+ pds.setClassName(LrcXADataSource.class.getName());
+ pds.setUniqueName("lrc-pds");
+ pds.setMaxPoolSize(2);
+ pds.getDriverProperties().setProperty("driverClassName", MockDriver.class.getName());
+ pds.init();
+
+ btm.begin();
+
+ Connection c1 = pds.getConnection();
+ c1.createStatement();
+ c1.close();
+
+ Transaction tx = btm.suspend();
+
+ btm.begin();
+
+ Connection c11 = pds.getConnection();
+ c11.createStatement();
+ c11.close();
+
+ btm.commit();
+
+
+ btm.resume(tx);
+
+ Connection c2 = pds.getConnection();
+ c2.createStatement();
+ c2.close();
+
+ btm.commit();
+
+ pds.close();
+ }
+
public void testTransactionContextCleanup() throws Exception {
+ BitronixTransactionManager btm = getBtm();
assertEquals(Status.STATUS_NO_TRANSACTION, btm.getStatus());
btm.begin();
@@ -177,6 +244,7 @@ public void run() {
}
public void testBeforeCompletionAddsExtraSynchronizationInDifferentPriority() throws Exception {
+ BitronixTransactionManager btm = getBtm();
btm.begin();
btm.getCurrentTransaction().getSynchronizationScheduler().add(new SynchronizationRegisteringSynchronization(btm.getCurrentTransaction()), 5);
@@ -185,15 +253,15 @@ public void testBeforeCompletionAddsExtraSynchronizationInDifferentPriority() th
}
public void testDebugZeroResourceTransactionDisabled() throws Exception {
+ BitronixTransactionManager btm = getBtm();
btm.begin();
assertNull("Activation stack trace must not be available by default.", btm.getCurrentTransaction().getActivationStackTrace());
btm.commit();
}
public void testDebugZeroResourceTransaction() throws Exception {
- btm.shutdown(); // necessary to change the configuration
TransactionManagerServices.getConfiguration().setDebugZeroResourceTransaction(true);
- btm = TransactionManagerServices.getTransactionManager();
+ BitronixTransactionManager btm = getBtm();
btm.begin();
assertNotNull("Activation stack trace must be available.", btm.getCurrentTransaction().getActivationStackTrace());
@@ -201,6 +269,7 @@ public void testDebugZeroResourceTransaction() throws Exception {
}
public void testBeforeCompletionRuntimeExceptionRethrown() throws Exception {
+ BitronixTransactionManager btm = getBtm();
btm.begin();
btm.getTransaction().registerSynchronization(new Synchronization() {
diff --git a/btm/src/test/java/bitronix/tm/mock/AbstractMockJdbcTest.java b/btm/src/test/java/bitronix/tm/mock/AbstractMockJdbcTest.java
index e525ddc6..4e5af137 100644
--- a/btm/src/test/java/bitronix/tm/mock/AbstractMockJdbcTest.java
+++ b/btm/src/test/java/bitronix/tm/mock/AbstractMockJdbcTest.java
@@ -52,6 +52,9 @@ public abstract class AbstractMockJdbcTest extends TestCase {
@Override
protected void setUp() throws Exception {
+ // clear event recorder list
+ EventRecorder.clear();
+
Iterator it = ResourceRegistrar.getResourcesUniqueNames().iterator();
while (it.hasNext()) {
String name = it.next();
@@ -92,12 +95,6 @@ protected void setUp() throws Exception {
registerPoolEventListener(p2);
TransactionManagerServices.getConfiguration().setGracefulShutdownInterval(2);
-
- // start TM
- TransactionManagerServices.getTransactionManager();
-
- // clear event recorder list
- EventRecorder.clear();
}
@SuppressWarnings("unchecked")
diff --git a/btm/src/test/java/bitronix/tm/mock/JdbcSharedConnectionTest.java b/btm/src/test/java/bitronix/tm/mock/JdbcSharedConnectionTest.java
index 3818261c..c090e273 100644
--- a/btm/src/test/java/bitronix/tm/mock/JdbcSharedConnectionTest.java
+++ b/btm/src/test/java/bitronix/tm/mock/JdbcSharedConnectionTest.java
@@ -30,7 +30,7 @@
* @author Ludovic Orban
*/
public class JdbcSharedConnectionTest extends AbstractMockJdbcTest {
- private final static Logger log = LoggerFactory.getLogger(NewJdbcProperUsageMockTest.class);
+ private final static Logger log = LoggerFactory.getLogger(JdbcSharedConnectionTest.class);
public void testSharedConnectionMultithreaded() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** Starting testSharedConnectionMultithreaded: getting TM"); }
@@ -87,6 +87,63 @@ public void run() {
}
+ public void testSharedConnectionQuickSuspendEnabledResumeOnAnotherThread() throws Exception {
+ if (log.isDebugEnabled()) { log.debug("*** Starting testSharedConnectionQuickSuspendEnabledResumeOnAnotherThread: getting TM"); }
+ TransactionManagerServices.getConfiguration().setQuickSuspend(true);
+ final BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ tm.setTransactionTimeout(120);
+
+ if (log.isDebugEnabled()) { log.debug("*** before begin"); }
+ tm.begin();
+ if (log.isDebugEnabled()) { log.debug("*** after begin"); }
+
+ final Transaction suspended = tm.suspend();
+
+ final ArrayList twoConnections = new ArrayList();
+ Thread thread1 = new Thread() {
+ @Override
+ public void run() {
+ try {
+ tm.resume(suspended);
+ if (log.isDebugEnabled()) { log.debug("*** getting connection from DS1"); }
+ Connection connection = poolingDataSource1.getConnection();
+ connection.createStatement();
+ twoConnections.add(connection);
+ tm.suspend();
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+ };
+ thread1.start();
+ thread1.join();
+
+ Thread thread2 = new Thread() {
+ @Override
+ public void run() {
+ try {
+ tm.resume(suspended);
+ if (log.isDebugEnabled()) { log.debug("*** getting connection from DS1"); }
+ Connection connection = poolingDataSource1.getConnection();
+ connection.createStatement();
+ twoConnections.add(connection);
+ tm.commit();
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+ };
+ thread2.start();
+ thread2.join();
+
+ PooledConnectionProxy handle1 = (PooledConnectionProxy) twoConnections.get(0);
+ PooledConnectionProxy handle2 = (PooledConnectionProxy) twoConnections.get(1);
+ assertNotSame(handle1.getProxiedDelegate(), handle2.getProxiedDelegate());
+
+ }
+
public void testUnSharedConnection() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** Starting testUnSharedConnection: getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
diff --git a/btm/src/test/java/bitronix/tm/mock/NewJdbcProperUsageMockTest.java b/btm/src/test/java/bitronix/tm/mock/NewJdbcProperUsageMockTest.java
index 9170f5f7..fd439bb5 100644
--- a/btm/src/test/java/bitronix/tm/mock/NewJdbcProperUsageMockTest.java
+++ b/btm/src/test/java/bitronix/tm/mock/NewJdbcProperUsageMockTest.java
@@ -72,6 +72,8 @@ public void testSimpleWorkingCase() throws Exception {
Thread.currentThread().setName("testSimpleWorkingCase");
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.setTransactionTimeout(10);
tm.begin();
@@ -133,6 +135,8 @@ public void testOrderedCommitResources() throws Exception {
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.setTransactionTimeout(10);
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -199,6 +203,8 @@ public void testReversePhase2Order() throws Exception {
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.setTransactionTimeout(10);
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -272,6 +278,8 @@ public void testLrc() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.setTransactionTimeout(10);
tm.begin();
@@ -325,6 +333,8 @@ public void testStatementTimeout() throws Exception {
Thread.currentThread().setName("testStatementTimeout");
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.setTransactionTimeout(1);
tm.begin();
@@ -373,6 +383,8 @@ public void testCommitTimeout() throws Exception {
Thread.currentThread().setName("testCommitTimeout");
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.setTransactionTimeout(1);
tm.begin();
@@ -419,6 +431,8 @@ public void testGlobalAfterLocal() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** getting connection from DS1 in local ctx"); }
Connection connection1 = poolingDataSource1.getConnection();
@@ -487,6 +501,8 @@ public void testDeferredReleaseAfterMarkedRollback() throws Exception {
Thread.currentThread().setName("testDeferredReleaseAfterMarkedRollback");
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -527,6 +543,8 @@ public void testRollingBackSynchronization() throws Exception {
Thread.currentThread().setName("testRollingBackSynchronization");
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
final BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -593,6 +611,8 @@ public void testSuspendResume() throws Exception {
Thread.currentThread().setName("testSuspendResume");
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -649,10 +669,70 @@ public void testSuspendResume() throws Exception {
assertEquals(DATASOURCE2_NAME, ((ConnectionQueuedEvent) orderedEvents.get(i++)).getPooledConnectionImpl().getPoolingDataSource().getUniqueName());
}
+ public void testQuickSuspendResume() throws Exception {
+ Thread.currentThread().setName("testQuickSuspendResume");
+ if (log.isDebugEnabled()) { log.debug("*** setting QuickSuspend property"); }
+ TransactionManagerServices.getConfiguration().setQuickSuspend(true);
+ if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
+ BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
+ if (log.isDebugEnabled()) { log.debug("*** before begin"); }
+ tm.begin();
+ if (log.isDebugEnabled()) { log.debug("*** after begin"); }
+
+ if (log.isDebugEnabled()) { log.debug("*** getting connection from DS1"); }
+ Connection connection1 = poolingDataSource1.getConnection();
+ connection1.createStatement();
+ if (log.isDebugEnabled()) { log.debug("*** getting connection from DS2"); }
+ Connection connection2 = poolingDataSource2.getConnection();
+ connection2.createStatement();
+
+ if (log.isDebugEnabled()) { log.debug("*** suspending transaction"); }
+ Transaction tx = tm.suspend();
+ if (log.isDebugEnabled()) { log.debug("*** resuming transaction"); }
+ tm.resume(tx);
+
+ if (log.isDebugEnabled()) { log.debug("*** closing connection 1"); }
+ connection1.close();
+ if (log.isDebugEnabled()) { log.debug("*** closing connection 2"); }
+ connection2.close();
+
+ if (log.isDebugEnabled()) { log.debug("*** committing"); }
+ tm.commit();
+ if (log.isDebugEnabled()) { log.debug("*** TX is done"); }
+
+ // check flow
+ List orderedEvents = EventRecorder.getOrderedEvents();
+ log.info(EventRecorder.dumpToString());
+
+ assertEquals(17, orderedEvents.size());
+ int i=0;
+ assertEquals(Status.STATUS_ACTIVE, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(DATASOURCE1_NAME, ((ConnectionDequeuedEvent) orderedEvents.get(i++)).getPooledConnectionImpl().getPoolingDataSource().getUniqueName());
+ assertEquals(XAResource.TMNOFLAGS, ((XAResourceStartEvent) orderedEvents.get(i++)).getFlag());
+ assertEquals(DATASOURCE2_NAME, ((ConnectionDequeuedEvent) orderedEvents.get(i++)).getPooledConnectionImpl().getPoolingDataSource().getUniqueName());
+ assertEquals(XAResource.TMNOFLAGS, ((XAResourceStartEvent) orderedEvents.get(i++)).getFlag());
+ assertEquals(XAResource.TMSUCCESS, ((XAResourceEndEvent) orderedEvents.get(i++)).getFlag());
+ assertEquals(XAResource.TMSUCCESS, ((XAResourceEndEvent) orderedEvents.get(i++)).getFlag());
+ assertEquals(Status.STATUS_PREPARING, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(XAResource.XA_OK, ((XAResourcePrepareEvent) orderedEvents.get(i++)).getReturnCode());
+ assertEquals(XAResource.XA_OK, ((XAResourcePrepareEvent) orderedEvents.get(i++)).getReturnCode());
+ assertEquals(Status.STATUS_PREPARED, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(Status.STATUS_COMMITTING, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(false, ((XAResourceCommitEvent) orderedEvents.get(i++)).isOnePhase());
+ assertEquals(false, ((XAResourceCommitEvent) orderedEvents.get(i++)).isOnePhase());
+ assertEquals(Status.STATUS_COMMITTED, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(DATASOURCE1_NAME, ((ConnectionQueuedEvent) orderedEvents.get(i++)).getPooledConnectionImpl().getPoolingDataSource().getUniqueName());
+ assertEquals(DATASOURCE2_NAME, ((ConnectionQueuedEvent) orderedEvents.get(i++)).getPooledConnectionImpl().getPoolingDataSource().getUniqueName());
+ }
+
public void testLooseWorkingCaseOutsideOutside() throws Exception {
Thread.currentThread().setName("testLooseWorkingCaseOutsideOutside");
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** getting connection from DS1"); }
Connection connection1 = poolingDataSource1.getConnection();
@@ -703,6 +783,8 @@ public void testLooseWorkingCaseOutsideInside() throws Exception {
Thread.currentThread().setName("testLooseWorkingCaseOutsideInside");
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** getting connection from DS1"); }
Connection connection1 = poolingDataSource1.getConnection();
@@ -753,6 +835,8 @@ public void testLooseWorkingCaseInsideOutside() throws Exception {
Thread.currentThread().setName("testLooseWorkingCaseInsideOutside");
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
@@ -802,6 +886,7 @@ public void testLooseWorkingCaseInsideOutside() throws Exception {
public void testHeuristicCommitWorkingCase() throws Exception {
Thread.currentThread().setName("testHeuristicCommitWorkingCase");
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ EventRecorder.clear();
tm.begin();
Connection connection1 = poolingDataSource1.getConnection();
@@ -862,6 +947,7 @@ public void testHeuristicCommitWorkingCase() throws Exception {
public void testHeuristicRollbackWorkingCase() throws Exception {
Thread.currentThread().setName("testHeuristicRollbackWorkingCase");
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ EventRecorder.clear();
tm.begin();
Connection connection1 = poolingDataSource1.getConnection();
@@ -915,6 +1001,9 @@ public void testHeuristicRollbackWorkingCase() throws Exception {
public void testNonXaPool() throws Exception {
Thread.currentThread().setName("testNonXaPool");
+ TransactionManagerServices.getTransactionManager();
+ EventRecorder.clear();
+
for (int i=0; i orderedEvents = EventRecorder.getOrderedEvents();
+ log.info(EventRecorder.dumpToString());
+
+ assertEquals(15, orderedEvents.size());
+ int i=0;
+ assertEquals(Status.STATUS_ACTIVE, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(DATASOURCE1_NAME, ((ConnectionDequeuedEvent) orderedEvents.get(i++)).getPooledConnectionImpl().getPoolingDataSource().getUniqueName());
+ assertEquals(XAResource.TMNOFLAGS, ((XAResourceStartEvent) orderedEvents.get(i++)).getFlag());
+
+ assertEquals(Status.STATUS_ACTIVE, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(Status.STATUS_PREPARING, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(Status.STATUS_PREPARED, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(Status.STATUS_COMMITTING, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(Status.STATUS_COMMITTED, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+
+ assertEquals(XAResource.TMSUCCESS, ((XAResourceEndEvent) orderedEvents.get(i++)).getFlag());
+
+ assertEquals(Status.STATUS_PREPARING, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(Status.STATUS_PREPARED, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(Status.STATUS_COMMITTING, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(true, ((XAResourceCommitEvent) orderedEvents.get(i++)).isOnePhase());
+ assertEquals(Status.STATUS_COMMITTED, ((JournalLogEvent) orderedEvents.get(i++)).getStatus());
+ assertEquals(DATASOURCE1_NAME, ((ConnectionQueuedEvent) orderedEvents.get(i++)).getPooledConnectionImpl().getPoolingDataSource().getUniqueName());
+ }
+
+
}
diff --git a/btm/src/test/java/bitronix/tm/mock/NewJdbcSuspendResumeMockTest.java b/btm/src/test/java/bitronix/tm/mock/NewJdbcSuspendResumeMockTest.java
index 03b2bac1..11cd0d9c 100644
--- a/btm/src/test/java/bitronix/tm/mock/NewJdbcSuspendResumeMockTest.java
+++ b/btm/src/test/java/bitronix/tm/mock/NewJdbcSuspendResumeMockTest.java
@@ -78,6 +78,8 @@ public void testSimpleAssertions() throws Exception {
public void testSimpleWorkingCase() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -127,6 +129,8 @@ public void testNoTmJoin() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -176,6 +180,8 @@ public void testNoTmJoin() throws Exception {
public void testReEnlistmentAfterSuspend() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -242,6 +248,8 @@ public void testReEnlistmentAfterSuspend() throws Exception {
public void testClosingSuspendedConnections() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
@@ -310,6 +318,8 @@ public void testClosingSuspendedConnections() throws Exception {
public void testInterleavedLocalGlobalTransactions() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -363,6 +373,8 @@ public void testInterleavedLocalGlobalTransactions() throws Exception {
public void testInterleavedGlobalGlobalTransactionsWithDifferentConnections() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -431,6 +443,8 @@ public void testInterleavedGlobalGlobalTransactionsWithDifferentConnections() th
public void testInterleavedGlobalGlobalTransactionsWithDifferentConnectionsLateSuspend() throws Exception {
if (log.isDebugEnabled()) { log.debug("*** getting TM"); }
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
if (log.isDebugEnabled()) { log.debug("*** before begin"); }
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** after begin"); }
@@ -501,6 +515,8 @@ public void testInterleavedGlobalGlobalTransactionsWithDifferentConnectionsLateS
public void testJoinAfterSuspend() throws Exception {
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ if (log.isDebugEnabled()) { log.debug("*** clearing EventRecorder"); }
+ EventRecorder.clear();
tm.begin();
if (log.isDebugEnabled()) { log.debug("*** get C1"); }
diff --git a/btm/src/test/java/bitronix/tm/mock/NewJdbcWrongUsageMockTest.java b/btm/src/test/java/bitronix/tm/mock/NewJdbcWrongUsageMockTest.java
index 5e2e4740..aac977ce 100644
--- a/btm/src/test/java/bitronix/tm/mock/NewJdbcWrongUsageMockTest.java
+++ b/btm/src/test/java/bitronix/tm/mock/NewJdbcWrongUsageMockTest.java
@@ -58,6 +58,7 @@ public class NewJdbcWrongUsageMockTest extends AbstractMockJdbcTest {
public void testPrepareXAFailureCase() throws Exception {
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ EventRecorder.clear();
tm.begin();
Connection connection1 = poolingDataSource1.getConnection();
@@ -118,6 +119,7 @@ public void testPrepareXAFailureCase() throws Exception {
public void testPrepareRuntimeFailureCase() throws Exception {
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ EventRecorder.clear();
tm.begin();
Connection connection1 = poolingDataSource1.getConnection();
@@ -208,6 +210,42 @@ public void testIncorrectSuspendResume() throws Exception {
tm.commit();
}
+ public void testIncorrectSuspendResumeWithQuickSuspend() throws Exception {
+ TransactionManagerServices.getConfiguration().setQuickSuspend(true);
+ BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();
+ tm.begin();
+
+ Connection connection1 = poolingDataSource1.getConnection();
+ connection1.createStatement();
+ Connection connection2 = poolingDataSource2.getConnection();
+ connection2.createStatement();
+
+ Transaction tx = tm.suspend();
+
+ assertNull(tm.suspend());
+
+ try {
+ tm.resume(null);
+ fail("TM has allowed resuming a null TX context");
+ } catch (InvalidTransactionException ex) {
+ assertEquals("resumed transaction cannot be null", ex.getMessage());
+ }
+
+ tm.resume(tx);
+
+ try {
+ tm.resume(tx);
+ fail("TM has allowed resuming a TX context when another one is still running");
+ } catch (IllegalStateException ex) {
+ assertEquals("a transaction is already running on this thread", ex.getMessage());
+ }
+
+ connection1.close();
+ connection2.close();
+
+ tm.commit();
+ }
+
public void testEagerEnding() throws Exception {
BitronixTransactionManager tm = TransactionManagerServices.getTransactionManager();