Skip to content

Commit

Permalink
Quick suspend support. Enable tx.suspend without xaresource.end
Browse files Browse the repository at this point in the history
This is consistent with the suspend behavior of other open source
transaction managers.
  • Loading branch information
tsorgie committed Apr 2, 2015
1 parent 1072c30 commit 78306a4
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 30 deletions.
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
30 changes: 30 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,37 @@ 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;
this.started = false;
}

public void quickResume() throws XAException {
if (this.started) {
throw new BitronixXAException("resource already started: " + 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;
this.started = true;
}

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 XAResource needs 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

0 comments on commit 78306a4

Please sign in to comment.