From ce7ed4bd2886e3895b81c82465ce5643a7f91e1e Mon Sep 17 00:00:00 2001 From: tsorgie Date: Sun, 29 Mar 2015 13:17:35 -0400 Subject: [PATCH] Quick suspend support. Enable tx.suspend without xaresource.end This is consistent with the suspend behavior of other open source transaction managers. Upgraded unittests to enable configuration updates per testcase. --- .../main/java/bitronix/tm/Configuration.java | 25 + .../tm/internal/XAResourceHolderState.java | 28 + .../tm/internal/XAResourceManager.java | 78 ++- .../java/bitronix/tm/ConfigurationTest.java | 2 +- btm/src/test/java/bitronix/tm/JtaTest.java | 77 ++- .../tm/mock/AbstractMockJdbcTest.java | 9 +- .../tm/mock/JdbcSharedConnectionTest.java | 59 +- .../tm/mock/NewJdbcProperUsageMockTest.java | 112 ++++ .../NewJdbcQuickSuspendResumeMockTest.java | 630 ++++++++++++++++++ .../tm/mock/NewJdbcStrangeUsageMockTest.java | 95 ++- .../tm/mock/NewJdbcSuspendResumeMockTest.java | 16 + .../tm/mock/NewJdbcWrongUsageMockTest.java | 38 ++ 12 files changed, 1127 insertions(+), 42 deletions(-) create mode 100644 btm/src/test/java/bitronix/tm/mock/NewJdbcQuickSuspendResumeMockTest.java 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();