Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quick suspend support. Enable tx.suspend without xaresource.end #40

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions btm/src/main/java/bitronix/tm/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
* <p>Property name:<br><b>bitronix.tm.quickSuspend -</b> <i>(defaults to true)</i></p>
* @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
* <p>Property name:<br><b>bitronix.tm.quickSuspend -</b> <i>(defaults to true)</i></p>
* @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}
Expand Down
28 changes: 28 additions & 0 deletions btm/src/main/java/bitronix/tm/internal/XAResourceHolderState.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed my approach on quick suspend/remove slightly to leave the resource-holder in a started state. This seems to be more aligned with the state transitions below (it has not been ended, and start is not expected).

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;
Expand Down
78 changes: 49 additions & 29 deletions btm/src/main/java/bitronix/tm/internal/XAResourceManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,42 +144,62 @@ 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
}
}

/**
* Resume all enlisted resources in the current transaction context.
* @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<XAResourceHolderState> toBeReEnlisted = new ArrayList<XAResourceHolderState>();

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<XAResourceHolderState> toBeReEnlisted = new ArrayList<XAResourceHolderState>();

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();
}
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion btm/src/test/java/bitronix/tm/ConfigurationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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]";

Expand Down
77 changes: 73 additions & 4 deletions btm/src/test/java/bitronix/tm/JtaTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to support configuration updates per testcase.


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();
Expand All @@ -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");
Expand All @@ -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();
Expand All @@ -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);
Expand All @@ -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");
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -185,22 +253,23 @@ 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());
btm.commit();
}

public void testBeforeCompletionRuntimeExceptionRethrown() throws Exception {
BitronixTransactionManager btm = getBtm();
btm.begin();

btm.getTransaction().registerSynchronization(new Synchronization() {
Expand Down
9 changes: 3 additions & 6 deletions btm/src/test/java/bitronix/tm/mock/AbstractMockJdbcTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public abstract class AbstractMockJdbcTest extends TestCase {

@Override
protected void setUp() throws Exception {
// clear event recorder list
EventRecorder.clear();

Iterator<String> it = ResourceRegistrar.getResourcesUniqueNames().iterator();
while (it.hasNext()) {
String name = it.next();
Expand Down Expand Up @@ -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();
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the call to getTransactionManager() to make it easy to alter configuration in testcases. This also meant we needed to control the EventRecorder in each, but that felt more natural to me anyway since those tests expect very fine grain control over the activity in the ER.


@SuppressWarnings("unchecked")
Expand Down
Loading