[jboss-svn-commits] JBL Code SVN: r32861 - in labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP: ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna and 13 other directories.
jboss-svn-commits at lists.jboss.org
jboss-svn-commits at lists.jboss.org
Thu May 13 07:44:32 EDT 2010
Author: jhalliday
Date: 2010-05-13 07:44:31 -0400 (Thu, 13 May 2010)
New Revision: 32861
Added:
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/internal/arjuna/coordinator/ReaperElementManager.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/byteman-scripts/
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/byteman-scripts/objectstore.txt
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/byteman-scripts/reaper.txt
Modified:
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/build.xml
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/AtomicAction.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/coordinator/TransactionReaper.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/internal/arjuna/coordinator/ReaperElement.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase2.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase3.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTA/jta/classes/com/arjuna/ats/internal/jta/transaction/arjunacore/subordinate/SubordinateAtomicAction.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jtax/classes/com/arjuna/ats/internal/jta/transaction/jts/subordinate/jca/SubordinateAtomicTransaction.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/interposition/ServerFactory.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/orbspecific/TransactionFactoryImple.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/orbspecific/coordinator/ArjunaTransactionImple.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/jts/OTSManager.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/atsintegration/classes/com/arjuna/ats/jbossatx/jta/TransactionManagerService.java
labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/atsintegration/classes/com/arjuna/ats/jbossatx/jts/TransactionManagerService.java
Log:
Backport transaction reaper performance improvements to the 4.6.1.CP branch. JBTM-624
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/build.xml
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/build.xml 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/build.xml 2010-05-13 11:44:31 UTC (rev 32861)
@@ -439,28 +439,17 @@
<mkdir dir="${com.hp.mwlabs.ts.arjuna.reports.dest}"/>
<junit printsummary="yes">
<formatter type="plain"/>
- <classpath>
+ <!--jvmarg value="-Xdebug"/>
+ <jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000"/-->
+
+ <classpath>
<pathelement location="${com.hp.mwlabs.ts.arjuna.tests.dest}"/>
<path location="${com.hp.mwlabs.ts.arjuna.dest}"/>
<pathelement path="${build.classpath}"/>
<pathelement location="etc"/>
</classpath>
+
<batchtest haltonerror="yes" haltonfailure="yes" fork="yes"
- todir="${com.hp.mwlabs.ts.arjuna.reports.dest}">
- <fileset dir="${com.hp.mwlabs.ts.arjuna.tests.src}" includes="**/ReaperTestCase.java"/>
- </batchtest>
- <!-- decommissioned becaue of timing problems. thsi has been
- dealt with in trunk using byteman rules
- <batchtest haltonerror="yes" haltonfailure="yes" fork="yes"
- todir="${com.hp.mwlabs.ts.arjuna.reports.dest}">
- <fileset dir="${com.hp.mwlabs.ts.arjuna.tests.src}" includes="**/ReaperTestCase2.java"/>
- </batchtest>
- -->
- <batchtest haltonerror="yes" haltonfailure="yes" fork="yes"
- todir="${com.hp.mwlabs.ts.arjuna.reports.dest}">
- <fileset dir="${com.hp.mwlabs.ts.arjuna.tests.src}" includes="**/ReaperTestCase3.java"/>
- </batchtest>
- <batchtest haltonerror="yes" haltonfailure="yes" fork="yes"
todir="${com.hp.mwlabs.ts.arjuna.reports.dest}">
<fileset dir="${com.hp.mwlabs.ts.arjuna.tests.src}" includes="**/TxControlUnitTest.java"/>
</batchtest>
@@ -469,6 +458,28 @@
<fileset dir="${com.hp.mwlabs.ts.arjuna.tests.src}" includes="**/PersistenceUnitTest.java"/>
</batchtest>
</junit>
+
+ <junit printsummary="yes" fork="true" dir="${basedir}" showoutput="false">
+ <formatter type="plain"/>
+ <classpath>
+ <pathelement location="${com.hp.mwlabs.ts.arjuna.tests.dest}"/>
+ <path location="${com.hp.mwlabs.ts.arjuna.dest}"/>
+ <pathelement path="${build.classpath}"/>
+ <pathelement location="etc"/>
+ </classpath>
+
+ <batchtest haltonerror="yes" haltonfailure="yes" fork="yes"
+ todir="${com.hp.mwlabs.ts.arjuna.reports.dest}">
+ <fileset dir="${com.hp.mwlabs.ts.arjuna.tests.src}">
+ <include name="**/reaper/ReaperTestCase.java"/>
+ <include name="**/reaper/ReaperTestCase2.java"/>
+ <include name="**/reaper/ReaperTestCase3.java"/>
+ </fileset>
+ </batchtest>
+
+ <jvmarg value="-javaagent:../../ext/byteman.jar=script:tests/byteman-scripts/reaper.txt"/>
+ <jvmarg value="-Dorg.jboss.byteman.debug"/>
+ </junit>
</target>
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/AtomicAction.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/AtomicAction.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/AtomicAction.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -144,7 +144,7 @@
_timeout = TxControl.getDefaultTimeout();
if (_timeout > 0)
- TransactionReaper.transactionReaper(true).insert(this, _timeout);
+ TransactionReaper.transactionReaper().insert(this, _timeout);
}
return status;
@@ -182,7 +182,7 @@
ThreadActionData.popAction();
- TransactionReaper.create().remove(this);
+ TransactionReaper.transactionReaper().remove(this);
return status;
}
@@ -206,7 +206,7 @@
ThreadActionData.popAction();
- TransactionReaper.create().remove(this);
+ TransactionReaper.transactionReaper().remove(this);
return status;
}
@@ -220,7 +220,7 @@
* the thread-to-tx association though.
*/
- TransactionReaper.create().remove(this);
+ TransactionReaper.transactionReaper().remove(this);
return outcome;
}
@@ -234,7 +234,7 @@
* the thread-to-tx association though.
*/
- TransactionReaper.create().remove(this);
+ TransactionReaper.transactionReaper().remove(this);
return outcome;
}
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/coordinator/TransactionReaper.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/coordinator/TransactionReaper.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/coordinator/TransactionReaper.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -33,18 +33,16 @@
import com.arjuna.ats.arjuna.common.Environment;
import com.arjuna.ats.arjuna.common.arjPropertyManager;
-import com.arjuna.ats.arjuna.coordinator.Reapable;
-import com.arjuna.ats.arjuna.coordinator.ActionStatus;
+import com.arjuna.ats.arjuna.coordinator.listener.ReaperMonitor;
import com.arjuna.ats.internal.arjuna.coordinator.*;
import com.arjuna.ats.arjuna.logging.tsLogger;
-import com.arjuna.ats.arjuna.logging.FacilityCode;
-import com.arjuna.common.util.logging.*;
-
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Class to record transactions with non-zero timeout values, and class to
@@ -55,1098 +53,876 @@
* @version $Id: TransactionReaper.java 2342 2006-03-30 13:06:17Z $
* @since JTS 1.0.
*
- *
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_1
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_1] -
- * TransactionReaper - could not create transaction list. Out of
- * memory.
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_1] -
+ * TransactionReaper - attempting to insert an element that is already present.
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_2
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_2] -
- * TransactionReaper::check - comparing {0}
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_2] -
+ * TransactionReaper::check - comparing {0}
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_3
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_3] -
- * TransactionReaper::getTimeout for {0} returning {1}
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_3] -
+ * TransactionReaper::getTimeout for {0} returning {1}
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_17
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_17] -
- * TransactionReaper::getRemainingTimeoutMillis for {0} returning {1}
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_17] -
+ * TransactionReaper::getRemainingTimeoutMillis for {0} returning {1}
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_4
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_4] -
- * TransactionReaper::check interrupting cancel in progress for {0}
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_4] -
+ * TransactionReaper::check interrupting cancel in progress for {0}
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_5
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_5] -
- * TransactionReaper::check worker zombie count {0} exceeds specified limit
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_5] -
+ * TransactionReaper::check worker zombie count {0} exceeds specified limit
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_6
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_6] -
- * TransactionReaper::check worker {0} not responding to interrupt when cancelling TX {1} -- worker marked as zombie and TX scheduled for mark-as-rollback
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_6] -
+ * TransactionReaper::check worker {0} not responding to interrupt when cancelling TX {1} -- worker marked as zombie and TX scheduled for mark-as-rollback
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_7
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_7] -
- * TransactionReaper::doCancellations worker {0} successfully canceled TX {1}
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_7] -
+ * TransactionReaper::doCancellations worker {0} successfully canceled TX {1}
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_8
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_8] -
- * TransactionReaper::doCancellations worker {0} failed to cancel TX {1} -- rescheduling for mark-as-rollback
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_8] -
+ * TransactionReaper::doCancellations worker {0} failed to cancel TX {1} -- rescheduling for mark-as-rollback
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_9
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_9] -
- * TransactionReaper::doCancellations worker {0} exception during cancel of TX {1} -- rescheduling for mark-as-rollback
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_9] -
+ * TransactionReaper::doCancellations worker {0} exception during cancel of TX {1} -- rescheduling for mark-as-rollback
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_10
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_10] -
- * TransactionReaper::check successfuly marked TX {0} as rollback only
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_10] -
+ * TransactionReaper::check successfuly marked TX {0} as rollback only
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_11
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_11] -
- * TransactionReaper::check failed to mark TX {0} as rollback only
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_11] -
+ * TransactionReaper::check failed to mark TX {0} as rollback only
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_12
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_12] -
- * TransactionReaper::check exception while marking TX {0} as rollback only
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_12] -
+ * TransactionReaper::check exception while marking TX {0} as rollback only
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_13
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_13] -
- * TransactionReaper::doCancellations worker {0} missed interrupt when cancelling TX {1} -- exiting as zombie (zombie count decremented to {2})
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_13] -
+ * TransactionReaper::doCancellations worker {0} missed interrupt when cancelling TX {1} -- exiting as zombie (zombie count decremented to {2})
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_14
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_14] -
- * TransactionReaper::doCancellations worker {0} successfuly marked TX {1} as rollback only
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_14] -
+ * TransactionReaper::doCancellations worker {0} successfuly marked TX {1} as rollback only
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_15
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_15] -
- * TransactionReaper::doCancellations worker {0} failed to mark TX {1} as rollback only
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_15] -
+ * TransactionReaper::doCancellations worker {0} failed to mark TX {1} as rollback only
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_16
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_16] -
- * TransactionReaper::doCancellations worker {0} exception while marking TX {1} as rollback only
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_16] -
+ * TransactionReaper::doCancellations worker {0} exception while marking TX {1} as rollback only
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_18
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_18] -
- * TransactionReaper::check timeout for TX {0} in state {1}
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_18] -
+ * TransactionReaper::check timeout for TX {0} in state {1}
* @message com.arjuna.ats.arjuna.coordinator.TransactionReaper_19
- * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_19] -
- * TransactionReaper NORMAL mode is deprecated. Update config to use PERIODIC for equivalent behaviour.
+ * [com.arjuna.ats.arjuna.coordinator.TransactionReaper_19] -
+ * TransactionReaper NORMAL mode is deprecated. Update config to use PERIODIC for equivalent behaviour.
*/
public class TransactionReaper
{
- public static final String NORMAL = "NORMAL";
+ public static final String NORMAL = "NORMAL";
- public static final String DYNAMIC = "DYNAMIC";
+ public static final String DYNAMIC = "DYNAMIC";
public static final String PERIODIC = "PERIODIC"; // the new name for 'NORMAL'
- public TransactionReaper(long checkPeriod)
- {
- if (tsLogger.arjLogger.debugAllowed())
- {
- tsLogger.arjLogger.debug(DebugLevel.CONSTRUCTORS,
- VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION,
- "TransactionReaper::TransactionReaper ( " + checkPeriod
- + " )");
- }
+ private TransactionReaper(long checkPeriod)
+ {
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("TransactionReaper::TransactionReaper ( " + checkPeriod
+ + " )");
+ }
- _checkPeriod = checkPeriod;
+ _checkPeriod = checkPeriod;
+ }
- if (_transactions == null)
- {
- if (tsLogger.arjLoggerI18N.isFatalEnabled())
- {
- tsLogger.arjLoggerI18N
- .fatal("com.arjuna.ats.arjuna.coordinator.TransactionReaper_1");
- }
+ public final long checkingPeriod()
+ {
+ if (_dynamic) {
+ return nextDynamicCheckTime.get() - System.currentTimeMillis();
+ } else {
+ // if we have a cancel in progress which needs
+ // checking up on then we have to wake up in time
+ // for it whether we are using a static or
+ // dynamic model
- throw new OutOfMemoryError();
- }
- }
+ final ReaperElement head = _reaperElements.getFirst();
+ if(head != null) {
+ if (head._status != ReaperElement.RUN) {
+ long waitTime = head.getAbsoluteTimeout() - System.currentTimeMillis();
+ if (waitTime < _checkPeriod) {
+ return waitTime;
+ }
+ }
+ }
- public final long checkingPeriod()
- {
- if (_dynamic)
- {
- try
- {
- final ReaperElement head = (ReaperElement) _transactions.first(); //_list.peak();
- return head.getAbsoluteTimeout() - System.currentTimeMillis();
- }
- catch (final NoSuchElementException nsee)
- {
- return Long.MAX_VALUE; // list is empty, so we can sleep until something is inserted.
- }
- }
- else
- {
- // if we have a cancel in progress which needs
- // checking up on then we have to wake up in time
- // for it whether we are using a static or
- // dynamic model
+ return _checkPeriod;
+ }
+ }
- try
- {
- final ReaperElement head = (ReaperElement) _transactions.first(); //_list.peak();
- if (head._status != ReaperElement.RUN) {
- long waitTime = head.getAbsoluteTimeout() - System.currentTimeMillis();
- if (waitTime < _checkPeriod)
- {
- return head.getAbsoluteTimeout() - System.currentTimeMillis();
- }
- }
- }
- catch (final NoSuchElementException nsee) {}
+ /**
+ * process all entries in the timeout queue which have
+ * expired. entries for newly expired transactions are passed
+ * to a worker thread for cancellation and requeued for
+ * subsequent progress checks. the worker is given a kick if
+ * such checks find it is wedged.
+ * <p/>
+ * Timeout is given in milliseconds.
+ */
- return _checkPeriod;
- }
- }
+ public final void check()
+ {
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("TransactionReaper::check ()");
+ }
- /**
- * process all entries in the timeout queue which have
- * expired. entries for newly expired transactions are passed
- * to a worker thread for cancellation and requeued for
- * subsequent progress checks. the worker is given a kick if
- * such checks find it is wedged.
- *
- * Timeout is given in milliseconds.
- */
+ do {
+ final ReaperElement reaperElement;
- public final boolean check()
- {
- if (tsLogger.arjLogger.debugAllowed())
- {
- tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
- FacilityCode.FAC_ATOMIC_ACTION, "TransactionReaper::check ()");
- }
+ synchronized (this) {
+ final long now = System.currentTimeMillis();
+ final long next = nextDynamicCheckTime.get();
- do
- {
- final ReaperElement e ;
+ if (tsLogger.arjLoggerI18N.isDebugEnabled()) {
+ tsLogger.arjLoggerI18N.debug("com.arjuna.ats.arjuna.coordinator.TransactionReaper_2", new Object[]{Long.toString(next)});
+ }
- synchronized (this)
- {
- // purge the pending inerts before doing anything else. This may hold up other inserts
- // for a while. Future versions may prefer to insert only a portion of the pending set.
- Set<Map.Entry<Reapable,ReaperElement>> entrySet = _pendingInsertions.entrySet();
- if(entrySet != null) {
- Iterator<Map.Entry<Reapable,ReaperElement>> queueIter = entrySet.iterator();
- while(queueIter.hasNext()) {
- Map.Entry<Reapable,ReaperElement> entry = queueIter.next();
- ReaperElement element = entry.getValue();
- // inert is also locked on (this), but remove is not. So, we are careful to check that
- // we don't insert an element that's been removed from the pending set by a concurrent thread.
- if(entrySet.remove(entry)) {
- synchronousInsert(element);
+ if (now < next) {
+ break;
+ }
+
+ reaperElement = _reaperElements.getFirst();
+ // TODO close window where first can change - maybe record nextDynamicCheckTime before probing first,
+ // then use compareAndSet? Although something will need to check before sleeping anyhow...
+ if(reaperElement == null) {
+ nextDynamicCheckTime.set(Long.MAX_VALUE);
+ return;
+ } else {
+ if(reaperElement.getAbsoluteTimeout() > now) {
+ return; // nothing to do yet.
}
}
}
- try
- {
- e = (ReaperElement)_transactions.first();
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.TransactionReaper_18",
+ new Object[] {reaperElement._control.get_uid(), reaperElement.statusName()});
}
- catch (final NoSuchElementException nsee)
- {
- return true ;
- }
- if (tsLogger.arjLoggerI18N.isDebugEnabled())
- {
- tsLogger.arjLoggerI18N
- .debug(
- DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC,
- FacilityCode.FAC_ATOMIC_ACTION,
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_2",
- new Object[]
- { Long.toString(e.getAbsoluteTimeout()) });
- }
- final long now = System.currentTimeMillis();
+ // if we have to synchronize on multiple objects we always
+ // do so in a fixed order ReaperElement before Reaper and
+ // ReaperElement before Reaper._cancelQueue in order to
+ // ensure we don't deadlock. We never sychronize on the
+ // reaper and the cancel queue at the same time.
- if (now < e.getAbsoluteTimeout())
- {
- // go back to sleep
+ synchronized (reaperElement) {
+ switch (reaperElement._status) {
+ case ReaperElement.RUN: {
+ // this tx has just timed out. remove it from the
+ // TX list, update the timeout to take account of
+ // cancellation period and reinsert as a cancelled
+ // TX. this ensures we process it again if it does
+ // not get cancelled in time
- break;
- }
- }
+ reaperElement._status = ReaperElement.SCHEDULE_CANCEL;
- if (tsLogger.arjLoggerI18N.isWarnEnabled())
- {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_18",
- new Object[]
- { e._control.get_uid() , e.statusName() });
- }
+ reinsertElement(reaperElement, _cancelWaitPeriod);
- // if we have to synchronize on multiple objects we always
- // do so in a fixed order ReaperElement before Reaper and
- // ReaperElement before Reaper._cancelQueue in order to
- // ensure we don't deadlock. We never sychronize on the
- // reaper and the cancel queue at the same time.
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("Reaper scheduling TX for cancellation " + reaperElement._control.get_uid());
+ }
- synchronized(e) {
- switch (e._status)
- {
- case ReaperElement.RUN:
- {
- // this tx has just timed out. remove it from the
- // TX list, update the timeout to take account of
- // cancellation period and reinsert as a cancelled
- // TX. this ensures we process it again if it does
- // not get cancelled in time
+ // insert into cancellation queue for a worker
+ // thread to process and then make sure a worker
+ // thread is awake
- e._status = ReaperElement.SCHEDULE_CANCEL;
+ synchronized (_workQueue) {
+ _workQueue.add(reaperElement);
+ _workQueue.notifyAll();
+ }
+ }
+ break;
+ case ReaperElement.SCHEDULE_CANCEL: {
+ // hmm, a worker is taking its time to
+ // start processing this scheduled entry.
+ // we may just be running slow ... but the
+ // worker may be wedged under a cancel for
+ // some other TX. add an extra delay to
+ // give the worker more time to complete
+ // its current task and progress this
+ // entry to the CANCEL state. if the
+ // worker *is* wedged then this will
+ // ensure the wedged TX entry comes to the
+ // front of the queue.
- synchronized (this)
- {
- _transactions.remove(e);
+ reinsertElement(reaperElement, _cancelWaitPeriod);
- e.setAbsoluteTimeout((System.currentTimeMillis() + _cancelWaitPeriod));
- _transactions.add(e);
- }
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("Reaper deferring interrupt for TX scheduled for cancel " + reaperElement._control.get_uid());
+ }
+ }
+ break;
+ case ReaperElement.CANCEL: {
+ // ok, the worker must be wedged under a
+ // call to cancel() -- kick the thread and
+ // reschedule the element for a later
+ // check to ensure the thread responded to
+ // the kick
- if (tsLogger.arjLogger.debugAllowed())
- {
- tsLogger.arjLogger
- .debug(
- DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION,
- "Reaper scheduling TX for cancellation " + e._control.get_uid());
- }
+ reaperElement._status = ReaperElement.CANCEL_INTERRUPTED;
- // insert into cancellation queue for a worker
- // thread to process and then make sure a worker
- // thread is awake
+ reaperElement._worker.interrupt();
- synchronized (_workQueue)
- {
- _workQueue.add(e);
- _workQueue.notifyAll();
- }
- }
- break;
- case ReaperElement.SCHEDULE_CANCEL:
- {
- // hmm, a worker is taking its time to
- // start processing this scheduled entry.
- // we may just be running slow ... but the
- // worker may be wedged under a cancel for
- // some other TX. add an extra delay to
- // give the worker more time to complete
- // its current task and progress this
- // entry to the CANCEL state. if the
- // worker *is* wedged then this will
- // ensure the wedged TX entry comes to the
- // front of the queue.
+ reinsertElement(reaperElement, _cancelFailWaitPeriod);
- synchronized (this)
- {
- _transactions.remove(e);
+ // log that we interrupted cancel()
- e.setAbsoluteTimeout((System.currentTimeMillis() + _cancelWaitPeriod));
+ if (tsLogger.arjLoggerI18N.isDebugEnabled()) {
+ tsLogger.arjLoggerI18N.debug("com.arjuna.ats.arjuna.coordinator.TransactionReaper_4", new Object[]{reaperElement._control.get_uid()});
+ }
+ }
+ break;
+ case ReaperElement.CANCEL_INTERRUPTED: {
+ // cancellation got truly wedged -- mark
+ // the element as a zombie so the worker
+ // exits when (if?) it wakes up and create
+ // a new worker thread to handle further
+ // cancellations. then mark the
+ // transaction as rollback only.
- _transactions.add(e);
- }
+ reaperElement._status = ReaperElement.ZOMBIE;
- if (tsLogger.arjLogger.debugAllowed())
- {
- tsLogger.arjLogger
- .debug(
- DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION,
- "Reaper deferring interrupt for TX scheduled for cancel " + e._control.get_uid());
- }
- }
- break;
- case ReaperElement.CANCEL:
- {
- // ok, the worker must be wedged under a
- // call to cancel() -- kick the thread and
- // reschedule the element for a later
- // check to ensure the thread responded to
- // the kick
+ synchronized (this) {
+ _zombieCount++;
- e._status = ReaperElement.CANCEL_INTERRUPTED;
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("Reaper " + Thread.currentThread() + " got a zombie " + reaperElement._worker + " (zombie count now " + _zombieCount + ") cancelling " + reaperElement._control.get_uid());
+ }
- e._worker.interrupt();
+ if (_zombieCount == _zombieMax) {
+ // log zombie overflow error call()
- synchronized (this)
- {
- _transactions.remove(e);
+ if (tsLogger.arjLoggerI18N.isErrorEnabled()) {
+ tsLogger.arjLoggerI18N.error(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_5",
+ new Object[]{new Integer(_zombieCount)});
+ }
+ }
+ }
- e.setAbsoluteTimeout((System.currentTimeMillis() + _cancelFailWaitPeriod));
+ _reaperWorkerThread = new ReaperWorkerThread(TransactionReaper._theReaper);
+ _reaperWorkerThread.setDaemon(true);
- _transactions.add(e);
- }
+ _reaperWorkerThread.start();
- // log that we interrupted cancel()
+ // log a failed cancel()
- if (tsLogger.arjLoggerI18N.isDebugEnabled())
- {
- tsLogger.arjLoggerI18N
- .debug(
- DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC,
- FacilityCode.FAC_ATOMIC_ACTION,
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_4",
- new Object[]{e._control.get_uid()});
- }
- }
- break;
- case ReaperElement.CANCEL_INTERRUPTED:
- {
- // cancellation got truly wedged -- mark
- // the element as a zombie so the worker
- // exits when (if?) it wakes up and create
- // a new worker thread to handle further
- // cancellations. then mark the
- // transaction as rollback only.
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N.warn(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_6",
+ new Object[]{reaperElement._worker,
+ reaperElement._control.get_uid()});
+ }
- e._status = ReaperElement.ZOMBIE;
+ // ok, since the worker was wedged we need to
+ // remove the entry from the timeouts and
+ // transactions lists then mark this tx as
+ // rollback only. we have to log a message
+ // whether we succeed, fail or get interrupted
- synchronized(this)
- {
- _zombieCount++;
+ removeElementReaper(reaperElement);
- if (tsLogger.arjLogger.isDebugEnabled())
- {
- tsLogger.arjLogger
- .debug(
- DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC,
- FacilityCode.FAC_ATOMIC_ACTION, "Reaper " + Thread.currentThread() + " got a zombie " + e._worker + " (zombie count now " + _zombieCount + ") cancelling " + e._control.get_uid());
- }
+ try {
+ if (reaperElement._control.preventCommit()) {
- if (_zombieCount == _zombieMax)
- {
- // log zombie overflow error call()
+ // log a successful preventCommit()
- if (tsLogger.arjLoggerI18N.isErrorEnabled())
- {
- tsLogger.arjLoggerI18N
- .error(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_5",
- new Object[]{new Integer(_zombieCount)});
- }
- }
- }
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N.warn(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_10",
+ new Object[]{reaperElement._control.get_uid()});
+ }
- _reaperWorkerThread = new ReaperWorkerThread(TransactionReaper._theReaper);
- _reaperWorkerThread.setDaemon(true);
+ notifyListeners(reaperElement._control, false);
+ } else {
+ // log a failed preventCommit()
- _reaperWorkerThread.start();
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N.warn(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_11",
+ new Object[]{reaperElement._control.get_uid()});
+ }
+ }
+ }
+ catch (Exception e1) {
+ // log an exception under preventCommit()
- // log a failed cancel()
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N
+ .warn("com.arjuna.ats.arjuna.coordinator.TransactionReaper_12",
+ new Object[]{reaperElement._control.get_uid()}, e1);
+ }
+ }
+ }
+ break;
+ case ReaperElement.FAIL:
+ case ReaperElement.COMPLETE: {
+ // ok, the worker should remove the tx
+ // from the transactions queue very soon
+ // but we need to progress to the next
+ // entry so we will steal in and do it
+ // first
- if (tsLogger.arjLoggerI18N.isWarnEnabled())
- {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_6",
- new Object[]{e._worker,
- e._control.get_uid()});
- }
+ removeElementReaper(reaperElement);
+ }
+ break;
- // ok, since the worker was wedged we need to
- // remove the entry from the timeouts and
- // transactions lists then mark this tx as
- // rollback only. we have to log a message
- // whether we succeed, fail or get interrupted
+ }
+ }
+ } while (true);
- synchronized(this)
- {
- removeElement(e);
- }
+ }
- try
- {
- if (e._control.preventCommit()) {
+ /**
+ * called by check, this method removes and reinserts an element in the timeout
+ * ordered set, recalculating the next wakeup time accordingly.
+ */
+ private void reinsertElement(ReaperElement e, long delay)
+ {
+ synchronized (this) {
+ long newWakeup = _reaperElements.reorder(e, delay);
+ nextDynamicCheckTime.set(newWakeup); // TODO - set should be atomic with reorder?
+ }
+ }
- // log a successful preventCommit()
-
- if (tsLogger.arjLoggerI18N.isWarnEnabled())
- {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_10",
- new Object[]{e._control.get_uid()});
- }
- }
- else
- {
- // log a failed preventCommit()
-
- if (tsLogger.arjLoggerI18N.isWarnEnabled())
- {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_11",
- new Object[]{e._control.get_uid()});
- }
- }
- }
- catch(Exception e1)
- {
- // log an exception under preventCommit()
-
- if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_12",
- new Object[]{e._control.get_uid()},
- e1);
- }
- }
- }
- break;
- case ReaperElement.FAIL:
- case ReaperElement.COMPLETE:
- {
- // ok, the worker should remove the tx
- // from the transactions queue very soon
- // but we need to progress to the next
- // entry so we will steal in and do it
- // first
-
- synchronized(this)
- {
- removeElement(e);
- }
- }
- break;
-
- }
- }
- } while(true) ;
-
- return true;
- }
-
- public final void waitForCancellations()
- {
- synchronized(_workQueue)
- {
- try
- {
- while (_workQueue.isEmpty())
- {
- _workQueue.wait();
- }
- }
- catch (InterruptedException e)
- {
- }
- }
+ public final void waitForCancellations()
+ {
+ synchronized (_workQueue) {
+ try {
+ while (_workQueue.isEmpty()) {
+ _workQueue.wait();
+ }
+ }
+ catch (InterruptedException e) {
+ }
}
+ }
- public final void doCancellations()
- {
- for (;;)
- {
- ReaperElement e;
+ public final void doCancellations()
+ {
+ for (; ;) {
+ ReaperElement e;
- // see if we have any cancellations to process
+ // see if we have any cancellations to process
- synchronized(_workQueue)
- {
- try
- {
- e = (ReaperElement)_workQueue.remove(0);
- }
- catch (IndexOutOfBoundsException ioobe) {break;}
- }
+ synchronized (_workQueue) {
+ try {
+ e = _workQueue.remove(0);
+ }
+ catch (IndexOutOfBoundsException ioobe) {
+ break;
+ }
+ }
+ // ok, current status must be SCHEDULE_CANCEL.
+ // progress state to CANCEL and call cancel()
- // ok, current status must be SCHEDULE_CANCEL.
- // progress state to CANCEL and call cancel()
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("Reaper Worker " + Thread.currentThread() + " attempting to cancel " + e._control.get_uid());
+ }
+ boolean cancelled = false;
+ Exception exception = null;
- if (tsLogger.arjLogger.debugAllowed())
- {
- tsLogger.arjLogger
- .debug(
- DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
- FacilityCode.FAC_ATOMIC_ACTION, "Reaper Worker " + Thread.currentThread() + " attempting to cancel " + e._control.get_uid());
- }
+ synchronized (e) {
+ e._worker = Thread.currentThread();
+ e._status = ReaperElement.CANCEL;
+ e.notifyAll();
+ }
- boolean cancelled = false;
- Exception exception = null;
+ // we are now exposed to at most one interrupt from
+ // the reaper. test for running and try the cancel if
+ // required
- synchronized(e)
- {
- e._worker = Thread.currentThread();
- e._status = ReaperElement.CANCEL;
- e.notifyAll();
- }
+ try {
+ if (e._control.running()) {
- // we are now exposed to at most one interrupt from
- // the reaper. test for running and try the cancel if
- // required
+ // try to cancel the transaction
- try
- {
- if (e._control.running()) {
+ if (e._control.cancel() == ActionStatus.ABORTED) {
+ cancelled = true;
+
+ if (TxControl.enableStatistics) {
+ // note that we also count timeouts as application rollbacks via
+ // the stats unpdate in the TwoPhaseCoordinator cancel() method.
+ TxStats.incrementTimeouts();
+ }
- // try to cancel the transaction
-
- if (e._control.cancel() == ActionStatus.ABORTED)
- {
- cancelled = true;
-
- if (TxControl.enableStatistics) {
- // note that we also count timeouts as application rollbacks via
- // the stats unpdate in the TwoPhaseCoordinator cancel() method.
- TxStats.incrementTimeouts();
+ notifyListeners(e._control, true);
}
}
}
- }
- catch (Exception e1)
- {
- exception = e1;
- }
+ catch (Exception e1) {
+ exception = e1;
+ }
- // ok, close the interrupt window by resetting the
- // state -- unless we have been told to go away by
- // being set to ZOMBIE
+ // ok, close the interrupt window by resetting the
+ // state -- unless we have been told to go away by
+ // being set to ZOMBIE
- synchronized (e)
- {
- if (e._status == ReaperElement.ZOMBIE)
- {
- // we need to decrement the zombie count and
- // force an immediate thread exit. the reaper
- // will have removed the entry from the
- // transactions list and started another
- // worker thread.
+ synchronized (e) {
+ if (e._status == ReaperElement.ZOMBIE) {
+ // we need to decrement the zombie count and
+ // force an immediate thread exit. the reaper
+ // will have removed the entry from the
+ // transactions list and started another
+ // worker thread.
- ReaperWorkerThread worker = (ReaperWorkerThread)Thread.currentThread();
- worker.shutdown();
+ ReaperWorkerThread worker = (ReaperWorkerThread) Thread.currentThread();
+ worker.shutdown();
- synchronized(this)
- {
- _zombieCount--;
- }
+ synchronized (this) {
+ _zombieCount--;
+ }
- if (tsLogger.arjLoggerI18N.isWarnEnabled())
- {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_13",
- new Object[]{Thread.currentThread(),
- e._control.get_uid(),
- new Integer(_zombieCount)});
- }
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N
+ .warn(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_13",
+ new Object[]{Thread.currentThread(),
+ e._control.get_uid(),
+ new Integer(_zombieCount)});
+ }
- // this gets us out of the for(;;) loop and
- // the shutdown call above makes sure we exit
- // after returning
+ // this gets us out of the for(;;) loop and
+ // the shutdown call above makes sure we exit
+ // after returning
- break;
- }
- else if (cancelled &&
- e._status == ReaperElement.CANCEL_INTERRUPTED)
- {
- // ok the call to cancel() returned true but
- // we cannot trust it because the reaper sent
- // the thread an interrupt
+ break;
+ } else if (cancelled &&
+ e._status == ReaperElement.CANCEL_INTERRUPTED) {
+ // ok the call to cancel() returned true but
+ // we cannot trust it because the reaper sent
+ // the thread an interrupt
- cancelled = false;
- e._status = ReaperElement.FAIL;
- e.notifyAll();
- }
- else
- {
- e._status = (cancelled
- ? ReaperElement.COMPLETE
- : ReaperElement.FAIL);
- e.notifyAll();
- }
- }
+ cancelled = false;
+ e._status = ReaperElement.FAIL;
+ e.notifyAll();
+ } else {
+ e._status = (cancelled
+ ? ReaperElement.COMPLETE
+ : ReaperElement.FAIL);
+ e.notifyAll();
+ }
+ }
- // log a message notifying success, failure or
- // exception during cancel(), remove the element from
- // the transactions queue and mark TX as rollback only
+ // log a message notifying success, failure or
+ // exception during cancel(), remove the element from
+ // the transactions queue and mark TX as rollback only
- if (cancelled)
- {
- if (tsLogger.arjLoggerI18N.isWarnEnabled())
- {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_7",
- new Object[]{Thread.currentThread(),
- e._control.get_uid()});
- }
- }
- else if (e._control.running())
- {
- if (exception != null)
- {
- if (tsLogger.arjLoggerI18N.isWarnEnabled())
- {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_9",
- new Object[]{Thread.currentThread(),
- e._control.get_uid()},
- exception);
- }
- }
- else
- {
- if (tsLogger.arjLoggerI18N.isWarnEnabled())
- {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_8",
- new Object[]{Thread.currentThread(),
- e._control.get_uid()});
- }
- }
+ if (cancelled) {
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N
+ .warn(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_7",
+ new Object[]{Thread.currentThread(),
+ e._control.get_uid()});
+ }
+ } else if (e._control.running()) {
+ if (exception != null) {
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N
+ .warn(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_9",
+ new Object[]{Thread.currentThread(),
+ e._control.get_uid()},
+ exception);
+ }
+ } else {
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N
+ .warn(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_8",
+ new Object[]{Thread.currentThread(),
+ e._control.get_uid()});
+ }
+ }
- try
- {
- if (e._control.preventCommit()) {
- // log a successful preventCommit()
+ try {
+ if (e._control.preventCommit()) {
+ // log a successful preventCommit()
- if (tsLogger.arjLoggerI18N.isWarnEnabled())
- {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_14",
- new Object[]{Thread.currentThread(),
- e._control.get_uid()});
- }
- }
- else
- {
- // log a failed preventCommit()
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N
+ .warn(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_14",
+ new Object[]{Thread.currentThread(),
+ e._control.get_uid()});
+ }
- if (tsLogger.arjLoggerI18N.isWarnEnabled())
- {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_15",
- new Object[]{Thread.currentThread(),
- e._control.get_uid()});
- }
- }
- }
- catch(Exception e1)
- {
- // log an exception under preventCommit()
+ notifyListeners(e._control, false);
+ } else {
+ // log a failed preventCommit()
- if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
- tsLogger.arjLoggerI18N
- .warn(
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_16",
- new Object[]{Thread.currentThread(),
- e._control.get_uid()},
- e1);
- }
- }
- }
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N
+ .warn(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_15",
+ new Object[]{Thread.currentThread(),
+ e._control.get_uid()});
+ }
+ }
+ }
+ catch (Exception e1) {
+ // log an exception under preventCommit()
- synchronized(this)
- {
- removeElement(e);
- }
+ if (tsLogger.arjLoggerI18N.isWarnEnabled()) {
+ tsLogger.arjLoggerI18N
+ .warn(
+ "com.arjuna.ats.arjuna.coordinator.TransactionReaper_16",
+ new Object[]{Thread.currentThread(),
+ e._control.get_uid()},
+ e1);
+ }
+ }
+ }
- }
+ removeElementReaper(e);
}
+ }
- /**
- * @return the number of items in the reaper's list.
- * @since JTS 2.2.
- */
+ /**
+ * @return the number of items in the reaper's list.
+ * @since JTS 2.2.
+ *
+ * Note: this is a) expensive and b) an approximation. Should be called only by test code.
+ */
+ public final long numberOfTransactions()
+ {
+ return _reaperElements.size();
+ }
- public final long numberOfTransactions()
- {
- return _transactions.size() + _pendingInsertions.size();
- }
-
- /**
- * Return the number of timeouts registered.
- * @return The number of timeouts registered.
- */
- public final long numberOfTimeouts()
- {
- return _timeouts.size() + _pendingInsertions.size();
- }
-
-
/**
+ * Return the number of timeouts registered.
+ * Note: this is a) expensive and b) an approximation. Should be called only by test code.
+ *
+ * @return The number of timeouts registered.
+ */
+ public final long numberOfTimeouts()
+ {
+ return _timeouts.size();
+ }
+
+ /**
* timeout is given in seconds, but we work in milliseconds.
+ *
+ * Attempting to insert an element that is already present is an error (IllegalStateException)
*/
- public final boolean insert(Reapable control, int timeout)
+ public final void insert(Reapable control, int timeout)
{
- if (tsLogger.arjLogger.debugAllowed())
- {
- tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION,
- "TransactionReaper::insert ( " + control + ", " + timeout
- + " )");
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("TransactionReaper::insert ( " + control + ", " + timeout
+ + " )");
}
/*
* Ignore if the timeout is zero, since this means the transaction
* should never timeout.
*/
-
if (timeout == 0)
- return true;
+ return;
+ ReaperElement reaperElement = new ReaperElement(control, timeout);
+ _lifetime.addAndGet(timeout);
- ReaperElement e = new ReaperElement(control, timeout);
+ // insert the element only if it's not already present. We check _timeouts first, as elements
+ // maybe temporarily removed and reinserted in _reaperElements, so that is not as good a check.
+ // We use lazy eval to ensure we insert to _reaperElements only if we inserted to _timeouts.
+ // Note: removal works in reverse order i.e. _reaperElements then _timeouts.
+ if ((_timeouts.putIfAbsent(reaperElement._control, reaperElement) == null)) {
+ _reaperElements.add(reaperElement);
+ } else {
+ throw new IllegalStateException(tsLogger.arjLoggerI18N.getString("com.arjuna.ats.arjuna.coordinator.TransactionReaper_1"));
+ }
- boolean asyncInsert = false;
+ if (_dynamic && reaperElement.getAbsoluteTimeout() < nextDynamicCheckTime.get()) {
+ updateCheckTimeForEarlierInsert(reaperElement.getAbsoluteTimeout());
+ }
+ }
- synchronized (this)
- {
- if(_transactions.size() > 0) {
- ReaperElement first = (ReaperElement)_transactions.first();
- // if the new element would timeout after the earliest one we already have,
- // we can delay its insertion until that earlier timeout. Hopefully the new tx will
- // complete before then and we'll never have to insert it at all.
- if (first != null && e.compareTo(first) > 0) {
- // first make sure we have not seen this control already
- if (_timeouts.containsKey(control)) {
- // hmm, this probably means that the hash or equals implementation on the element has been
- // coded wrong
- return false;
- }
- // put it in in the pending list for later insertion but also
- // check the return value in case this entry already exists
- ReaperElement old = _pendingInsertions.put(control, e);
- if (old != null) {
- // hmm, this probably means that the hash or equals implementation on the element has been
- // coded wrong -- restore the old entry and return false. n.b. checking for a duplicate
- // this way avoids having to do a containsKey test in the normal case.
- _pendingInsertions.put(control, old);
- return false;
- }
- asyncInsert = true;
+ /**
+ * Reset the next wakeup time, when a new element has a timeout earlier than the currently scheduled wakeup.
+ *
+ * @param newCheckTime absolute time in ms.
+ */
+ private void updateCheckTimeForEarlierInsert(long newCheckTime)
+ {
+ synchronized (this) {
+ long oldCheckTime = nextDynamicCheckTime.get();
+ while (newCheckTime < oldCheckTime) {
+ if (nextDynamicCheckTime.compareAndSet(oldCheckTime, newCheckTime)) {
+ notifyAll(); // force recalc of next wakeup time, taking into account the newly inserted element(s)
+ } else {
+ oldCheckTime = nextDynamicCheckTime.get();
}
}
-
- if(asyncInsert) {
- return true;
- } else {
- return synchronousInsert(e);
- }
}
}
-
- private final boolean synchronousInsert(ReaperElement elementToInsert)
- {
- synchronized (this)
- {
- TransactionReaper._lifetime += elementToInsert._timeout;
-
- ReaperElement old = (ReaperElement)_timeouts.put(elementToInsert._control, elementToInsert);
- if (old != null) {
- // hmm, this probably means that the hash or equals implementation on the element has been
- // coded wrong -- restore the old entry and return false. n.b. checking for a duplicate
- // this way avoids having to do a containsKey test in the normal case.
- _timeouts.put(elementToInsert._control, old);
- return false;
- }
-
- // we should not get an error here unless the user has coded the compareTo test wrong
-
- boolean rtn = _transactions.add(elementToInsert);
-
- if(_dynamic && _transactions.first() == elementToInsert)
- {
- notifyAll(); // force recalc of next wakeup time, taking into account the newly inserted element
- }
-
- return rtn;
- }
- }
-
- public final boolean remove(java.lang.Object control)
+ // takes an Object because OTSManager.destroyControl(Control|ControlImple) uses PseudoControlWrapper not Reapable
+ public final void remove(Object control)
{
- // _pendingInsertions is a concurrent structure, so we don't lock it here. That means we need to be careful
- // when transferring elements from pending to the real structures, see check() above. The synchronous remove
- // must also take care to lock, or things may leak if a remove attempt happens concurrent to a pending copy.
-
- if(_pendingInsertions.remove(control) != null) {
- return true;
- } else {
- return synchronousRemove(control);
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("TransactionReaper::remove ( " + control + " )");
}
- }
- public final boolean synchronousRemove(java.lang.Object control)
- {
- if (tsLogger.arjLogger.debugAllowed())
- {
- tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION,
- "TransactionReaper::remove ( " + control + " )");
- }
+ if (control == null)
+ return;
- if (control == null)
- return false;
+ ReaperElement key = _timeouts.get(control);
+ if (key == null) {
+ return;
+ }
- ReaperElement key;
+ // if a cancellation is in progress then we have to
+ // see it through as we have to ensure that the worker
+ // thread does not get wedged. so we have to tell the
+ // control has gone away. in order to test the status
+ // we need to synchronize on the element before we
+ // synchronize on this so we can ensure that we don't
+ // deadlock ourselves.
- synchronized(this)
- {
- key = (ReaperElement)_timeouts.remove(control);
- if(key == null) {
- return false;
- }
- }
+ synchronized (key) {
+ if (key._status != ReaperElement.RUN) {
+ // we are cancelling this TX anyway and need
+ // to track the progress of the cancellation
+ // using this entry so we cnanot remove it
+ return;
+ }
- // if a cancellation is in progress then we have to
- // see it through as we have to ensure that the worker
- // thread does not get wedged. so we have to tell the
- // control has gone away. in order to test the status
- // we need to synchronize on the element before we
- // synchronize on this so we can ensure that we don't
- // deadlock ourselves.
-
- synchronized(key)
- {
- if (key._status != ReaperElement.RUN)
- {
- // we are cancelling this TX anyway and need
- // to track the progress of the cancellation
- // using this entry so we cnanot remove it
-
- return false;
- }
-
- synchronized(this)
- {
- removeElement(key);
-
- return true;
- }
+ removeElementClient(key);
}
- }
+ }
/**
- * Given the transaction instance, this will return the time left before the
- * transaction is automatically rolled back if it has not been terminated.
- *
- * @param control
- * @return the remaining time in milliseconds.
- */
+ * Given the transaction instance, this will return the time left before the
+ * transaction is automatically rolled back if it has not been terminated.
+ *
+ * @param control
+ * @return the remaining time in milliseconds.
+ */
+ public final long getRemainingTimeoutMills(Object control)
+ {
+ // arg is an Object because ArjunaTransactionImple.propagationContext does not have a Reapable
- public final long getRemainingTimeoutMills(Object control)
- {
- if ((_transactions.size() == 0) || (control == null))
- {
- if (tsLogger.arjLogger.debugAllowed())
- {
- tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC,
- FacilityCode.FAC_ATOMIC_ACTION,
- "TransactionReaper::getRemainingTimeout for " + control
- + " returning 0");
+ if ((_timeouts.isEmpty()) || (control == null)) {
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("TransactionReaper::getRemainingTimeout for " + control
+ + " returning 0");
}
return 0;
}
- final ReaperElement reaperElement = (ReaperElement)_timeouts.get(control);
+ final ReaperElement reaperElement = _timeouts.get(control);
long timeout = 0;
- if (reaperElement == null)
- {
+ if (reaperElement == null) {
timeout = 0;
- }
- else
- {
+ } else {
// units are in milliseconds at this stage.
timeout = reaperElement.getAbsoluteTimeout() - System.currentTimeMillis();
}
if (tsLogger.arjLoggerI18N.isDebugEnabled()) {
- tsLogger.arjLoggerI18N
- .debug(
- DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC,
- FacilityCode.FAC_ATOMIC_ACTION,
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_17",
- new Object[]
- { control, timeout });
+ tsLogger.arjLoggerI18N.debug("com.arjuna.ats.arjuna.coordinator.TransactionReaper_17", new Object[]
+ {control, timeout});
}
return timeout;
- }
+ }
- /**
- * Given a Control, return the associated timeout, or 0 if we do not know
- * about it.
- *
- * Return in seconds!
- */
+ /**
+ * Given a Control, return the associated timeout, or 0 if we do not know
+ * about it.
+ * <p/>
+ * Return in seconds!
+ *
+ * Takes an Object because TransactionFactoryImple.getTransactionInfo and
+ * ArjunaTransactionImple.propagationContext use it and don't have a Reapable.
+ */
+ public final int getTimeout(Object control)
+ {
+ if ((_timeouts.isEmpty()) || (control == null)) {
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("TransactionReaper::getTimeout for " + control
+ + " returning 0");
+ }
- public final int getTimeout(Object control)
- {
- if ((_transactions.size() == 0) || (control == null))
- {
- if (tsLogger.arjLogger.debugAllowed())
- {
- tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC,
- FacilityCode.FAC_ATOMIC_ACTION,
- "TransactionReaper::getTimeout for " + control
- + " returning 0");
- }
+ return 0;
+ }
- return 0;
- }
+ final ReaperElement reaperElement = _timeouts.get(control);
- final ReaperElement reaperElement = (ReaperElement)_timeouts.get(control);
+ int timeout = (reaperElement == null ? 0 : reaperElement._timeout);
- final Integer timeout ;
- if(reaperElement == null) {
- timeout = new Integer(0);
- } else {
- timeout = new Integer(reaperElement._timeout) ;
- }
+ tsLogger.arjLoggerI18N.debug("com.arjuna.ats.arjuna.coordinator.TransactionReaper_3", new Object[]
+ {control, timeout});
- tsLogger.arjLoggerI18N
- .debug(
- DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC,
- FacilityCode.FAC_ATOMIC_ACTION,
- "com.arjuna.ats.arjuna.coordinator.TransactionReaper_3",
- new Object[]
- { control, timeout });
+ return timeout;
+ }
- return timeout.intValue();
- }
+ /*
+ * Terminate the transaction reaper. This is a synchronous operation
+ * and will only return once the reaper has been shutdown cleanly.
+ *
+ * Note, this method assumes that the transaction system has been
+ * shutdown already so no new transactions can be created, or we
+ * could be here for a long time!
+ *
+ * @param waitForTransactions if <code>true</code> then the reaper will
+ * wait until all transactions have terminated (or been terminated by it).
+ * If <code>false</code> then the reaper will call setRollbackOnly on all
+ * the transactions.
+ */
- /*
- * Terminate the transaction reaper. This is a synchronous operation
- * and will only return once the reaper has been shutdown cleanly.
- *
- * Note, this method assumes that the transaction system has been
- * shutdown already so no new transactions can be created, or we
- * could be here for a long time!
- *
- * @param waitForTransactions if <code>true</code> then the reaper will
- * wait until all transactions have terminated (or been terminated by it).
- * If <code>false</code> then the reaper will call setRollbackOnly on all
- * the transactions.
- */
-
- private final void shutdown (boolean waitForTransactions)
- {
+ private final void shutdown(boolean waitForTransactions)
+ {
// the reaper thread synchronizes and waits on this
- synchronized (this)
- {
- _inShutdown = true;
+ synchronized (this) {
+ _inShutdown = true;
- /*
- * If the caller does not want to wait for the normal transaction timeout
- * periods to elapse before terminating, then we first start by enabling
- * our time machine!
- */
+ /*
+ * If the caller does not want to wait for the normal transaction timeout
+ * periods to elapse before terminating, then we first start by enabling
+ * our time machine!
+ */
- if (!waitForTransactions)
- {
- Iterator iter = _transactions.iterator();
- ReaperElement e;
+ if (!waitForTransactions) {
+ _reaperElements.setAllTimeoutsToZero();
+ }
- while (iter.hasNext())
- {
- e = (ReaperElement) iter.next();
+ /*
+ * Wait for all of the transactions to
+ * terminate normally.
+ */
+ while (!_reaperElements.isEmpty()) {
+ try {
+ this.wait();
+ }
+ catch (final Exception ex) {
+ }
+ }
- e.setAbsoluteTimeout(0);
- }
- }
-
- /*
- * Wait for all of the transactions to
- * terminate normally.
- */
-
- while (_transactions.size() > 0)
- {
- try
- {
- this.wait();
- }
- catch (final Exception ex)
- {
- }
- }
-
-
_reaperThread.shutdown();
notifyAll();
}
- try
- {
+ try {
_reaperThread.join();
}
- catch (final Exception ex)
- {
+ catch (final Exception ex) {
}
_reaperThread = null;
// the reaper worker thread synchronizes and wais on the work queue
- synchronized(_workQueue) {
+ synchronized (_workQueue) {
_reaperWorkerThread.shutdown();
_workQueue.notifyAll();
// hmm, not sure we really need to do this but . . .
_reaperWorkerThread.interrupt();
}
- try
- {
+ try {
_reaperWorkerThread.join();
}
- catch (final Exception ex)
- {
+ catch (final Exception ex) {
}
_reaperWorkerThread = null;
- }
+ }
- /*
- * Remove element from list and trigger waiter if we are
- * being shutdown.
- *
- * n.b. must only be called when synchronized on this
- */
+ // called (indirectly) by user code doing removals on e.g. commit/rollback
+ // does not reset the wakeup time - we prefer leaving an unnecessary wakeup as it's
+ // cheaper than locking to recalculate the new time here.
+ private final void removeElementClient(ReaperElement reaperElement)
+ {
+ _reaperElements.remove(reaperElement);
+ _timeouts.remove(reaperElement._control);
- private final void removeElement (ReaperElement e)
- {
- _timeouts.remove(e._control);
- _transactions.remove(e);
+ // don't recalc time, just wake up as planned
- if (_inShutdown && (_transactions.size() == 0))
- {
- this.notifyAll();
+ if(_inShutdown) {
+ synchronized (this) {
+ this.notifyAll(); // TODO: use different lock for shutdown?
+ }
}
- }
+ }
- /**
- * Currently we let the reaper thread run at same priority as other threads.
- * Could get priority from environment.
- */
+ /*
+ * Remove element from list and trigger waiter if we are
+ * being shutdown.
+ *
+ */
+ // called internally by the reaper when removing elements - note the different
+ // behaviour with regard to check time recalculation. Here we need to ensure the
+ // new time is correct.
+ private final void removeElementReaper(ReaperElement reaperElement)
+ {
+ _reaperElements.remove(reaperElement);
+ _timeouts.remove(reaperElement._control);
- public static synchronized TransactionReaper create(long checkPeriod)
- {
- if (tsLogger.arjLogger.debugAllowed())
- {
- tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS,
- VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION,
- "TransactionReaper::create ( " + checkPeriod + " )");
- }
+ synchronized (this) {
- if (TransactionReaper._theReaper == null)
- {
+ // TODO set needs tobe atomic to getFirst?
+ ReaperElement first = _reaperElements.getFirst();
+ if(first != null) {
+ nextDynamicCheckTime.set(first.getAbsoluteTimeout());
+ } else {
+ nextDynamicCheckTime.set(Long.MAX_VALUE);
+ if(_inShutdown) {
+ this.notifyAll(); // TODO: use different lock for shutdown?
+ }
+ }
+ }
+ }
+
+
+
+ private final void notifyListeners(Reapable element, boolean rollback)
+ {
+ // notify listeners. Ignore errors.
+
+ for (int i = 0; i < _listeners.size(); i++) {
+ try {
+ if (rollback)
+ _listeners.get(i).rolledBack(element.get_uid());
+ else
+ _listeners.get(i).markedRollbackOnly(element.get_uid());
+ }
+ catch (final Throwable ex) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Currently we let the reaper thread run at same priority as other threads.
+ * Could get priority from environment.
+ */
+ public static synchronized void instantiate()
+ {
+ if (TransactionReaper._theReaper == null)
+ {
+ if (tsLogger.arjLogger.isDebugEnabled()) {
+ tsLogger.arjLogger.debug("TransactionReaper::instantiate()");
+ }
+
// default to dynamic mode
TransactionReaper._dynamic = true;
- String mode = arjPropertyManager.propertyManager
- .getProperty(Environment.TX_REAPER_MODE);
-
- if (mode != null)
+ String mode = arjPropertyManager.propertyManager.getProperty(Environment.TX_REAPER_MODE);
+
+ if (mode != null)
{
if (mode.compareTo(TransactionReaper.PERIODIC) == 0) {
TransactionReaper._dynamic = false;
@@ -1162,246 +938,234 @@
}
}
- if (!TransactionReaper._dynamic)
- {
- String timeoutEnv = arjPropertyManager.propertyManager
- .getProperty(Environment.TX_REAPER_TIMEOUT);
+ long checkPeriod = Long.MAX_VALUE;
+ if (!TransactionReaper._dynamic)
+ {
+ String timeoutEnv = arjPropertyManager.propertyManager
+ .getProperty(Environment.TX_REAPER_TIMEOUT);
if (timeoutEnv != null)
{
Long l = null;
-
+
try
{
l = new Long(timeoutEnv);
checkPeriod = l.longValue();
-
+
l = null;
}
catch (NumberFormatException e)
{
- if (tsLogger.arjLogger.isWarnEnabled()) {
- tsLogger.arjLogger.warn("TransactionReaper::create - "
+ if (tsLogger.arjLogger.isWarnEnabled()) {
+ tsLogger.arjLogger.warn("TransactionReaper::create - "
+ e);
- }
- }
+ }
+ }
}
- else
- {
- checkPeriod = defaultCheckPeriod;
- }
- }
- else
- checkPeriod = Long.MAX_VALUE;
+ else
+ {
+ checkPeriod = defaultCheckPeriod;
+ }
+ }
+ TransactionReaper._theReaper = new TransactionReaper(checkPeriod);
- TransactionReaper._theReaper = new TransactionReaper(checkPeriod);
-
- String cancelWait = arjPropertyManager.propertyManager
- .getProperty(Environment.TX_REAPER_CANCEL_WAIT_PERIOD);
+ String cancelWait = arjPropertyManager.propertyManager
+ .getProperty(Environment.TX_REAPER_CANCEL_WAIT_PERIOD);
+
if (cancelWait != null)
{
- try
- {
- TransactionReaper._theReaper._cancelWaitPeriod = Long.valueOf(cancelWait).longValue();
- }
- catch (NumberFormatException e)
- {
- TransactionReaper._theReaper._cancelWaitPeriod = defaultCancelWaitPeriod;
- }
+ try
+ {
+ TransactionReaper._theReaper._cancelWaitPeriod = Long.valueOf(cancelWait).longValue();
+ }
+ catch (NumberFormatException e)
+ {
+ TransactionReaper._theReaper._cancelWaitPeriod = defaultCancelWaitPeriod;
+ }
// must give TX at least 10 millisecs to
// respond to cancel
- if (TransactionReaper._theReaper._cancelWaitPeriod < 10) {
- TransactionReaper._theReaper._cancelWaitPeriod = 10;
- }
+ if (TransactionReaper._theReaper._cancelWaitPeriod < 10) {
+ TransactionReaper._theReaper._cancelWaitPeriod = 10;
+ }
}
- else
- {
- TransactionReaper._theReaper._cancelWaitPeriod = defaultCancelWaitPeriod;
- }
+ else
+ {
+ TransactionReaper._theReaper._cancelWaitPeriod = defaultCancelWaitPeriod;
+ }
String cancelFailWait = arjPropertyManager.propertyManager
- .getProperty(Environment.TX_REAPER_CANCEL_FAIL_WAIT_PERIOD);
+ .getProperty(Environment.TX_REAPER_CANCEL_FAIL_WAIT_PERIOD);
if (cancelFailWait != null)
{
- try
- {
- TransactionReaper._theReaper._cancelFailWaitPeriod = Long.valueOf(cancelFailWait).longValue();
- }
- catch (NumberFormatException e)
- {
- TransactionReaper._theReaper._cancelFailWaitPeriod = defaultCancelFailWaitPeriod;
- }
+ try
+ {
+ TransactionReaper._theReaper._cancelFailWaitPeriod = Long.valueOf(cancelFailWait).longValue();
+ }
+ catch (NumberFormatException e)
+ {
+ TransactionReaper._theReaper._cancelFailWaitPeriod = defaultCancelFailWaitPeriod;
+ }
+ // must give TX at least 10 millisecs to
+ // respond to cancel
- // must give TX at least 10 millisecs to
- // respond to cancel
-
- if (TransactionReaper._theReaper._cancelFailWaitPeriod < 10) {
- TransactionReaper._theReaper._cancelFailWaitPeriod = 10;
- }
+ if (TransactionReaper._theReaper._cancelFailWaitPeriod < 10)
+ {
+ TransactionReaper._theReaper._cancelFailWaitPeriod = 10;
+ }
}
- else
- {
- TransactionReaper._theReaper._cancelFailWaitPeriod = defaultCancelFailWaitPeriod;
- }
+ else
+ {
+ TransactionReaper._theReaper._cancelFailWaitPeriod = defaultCancelFailWaitPeriod;
+ }
String zombieMax = arjPropertyManager.propertyManager
- .getProperty(Environment.TX_REAPER_ZOMBIE_MAX);
+ .getProperty(Environment.TX_REAPER_ZOMBIE_MAX);
if (zombieMax != null)
{
- try
- {
- TransactionReaper._theReaper._zombieMax = Integer.valueOf(zombieMax).intValue();
- }
- catch (NumberFormatException e)
- {
- TransactionReaper._theReaper._zombieMax = defaultZombieMax;
- }
- // we start bleating if the zombie count
- // reaches zombieMax so it has to be at
- // least 1
-
- if (TransactionReaper._theReaper._zombieMax <= 0) {
- TransactionReaper._theReaper._zombieMax = 1;
- }
+ try
+ {
+ TransactionReaper._theReaper._zombieMax = Integer.valueOf(zombieMax).intValue();
+ }
+ catch (NumberFormatException e)
+ {
+ TransactionReaper._theReaper._zombieMax = defaultZombieMax;
+ }
+ // we start bleating if the zombie count
+ // reaches zombieMax so it has to be at
+ // least 1
+
+ if (TransactionReaper._theReaper._zombieMax <= 0) {
+ TransactionReaper._theReaper._zombieMax = 1;
+ }
}
- else
- {
- TransactionReaper._theReaper._zombieMax = defaultZombieMax;
- }
+ else
+ {
+ TransactionReaper._theReaper._zombieMax = defaultZombieMax;
+ }
+ TransactionReaper._theReaper._cancelWaitPeriod = defaultCancelWaitPeriod;
+ TransactionReaper._theReaper._cancelFailWaitPeriod = defaultCancelFailWaitPeriod;
+ TransactionReaper._theReaper._zombieMax = defaultZombieMax;
- // use defaults for now
-
- TransactionReaper._theReaper._cancelWaitPeriod = defaultCancelWaitPeriod;
- TransactionReaper._theReaper._cancelFailWaitPeriod = defaultCancelFailWaitPeriod;
- TransactionReaper._theReaper._zombieMax = defaultZombieMax;
-
_reaperThread = new ReaperThread(TransactionReaper._theReaper);
- // _reaperThread.setPriority(Thread.MIN_PRIORITY);
+ // _reaperThread.setPriority(Thread.MIN_PRIORITY);
- _reaperThread.setDaemon(true);
+ _reaperThread.setDaemon(true);
- _reaperWorkerThread = new ReaperWorkerThread(TransactionReaper._theReaper);
- _reaperWorkerThread.setDaemon(true);
+ _reaperWorkerThread = new ReaperWorkerThread(TransactionReaper._theReaper);
+ _reaperWorkerThread.setDaemon(true);
- _reaperThread.start();
+ _reaperThread.start();
- _reaperWorkerThread.start();
- }
+ _reaperWorkerThread.start();
+ }
+ }
- return TransactionReaper._theReaper;
- }
+ /**
+ * Starting with 4.8, this method will always return an instance, will never return null.
+ * This causes the reaper to be instantiated unnecessarily in some cases, but that's cheaper
+ * than the alternatives.
+ *
+ * @return a TransactionReaper singleton.
+ */
+ public static TransactionReaper transactionReaper() {
+ if(_theReaper == null) {
+ instantiate();
+ }
+ return _theReaper;
+ }
- public static TransactionReaper create()
- {
- return create(TransactionReaper.defaultCheckPeriod);
- }
+ /**
+ * Terminate the transaction reaper. This is a synchronous operation
+ * and will only return once the reaper has been shutdown cleanly.
+ * <p/>
+ * Note, this method assumes that the transaction system has been
+ * shutdown already so no new transactions can be created, or we
+ * could be here for a long time!
+ *
+ * @param waitForTransactions if <code>true</code> then the reaper will
+ * wait until all transactions have terminated (or been terminated by it).
+ * If <code>false</code> then the reaper will call setRollbackOnly on all
+ * the transactions.
+ */
- public static TransactionReaper transactionReaper()
- {
- return transactionReaper(false);
- }
+ public static synchronized void terminate(boolean waitForTransactions)
+ {
+ if (_theReaper != null) {
+ _theReaper.shutdown(waitForTransactions);
+ _theReaper = null;
+ }
+ }
- /*
- * If parameter is true then do a create.
- */
-
- public static synchronized TransactionReaper transactionReaper(
- boolean createReaper)
- {
- if (createReaper)
- return create();
- else
- return _theReaper;
- }
-
- /**
- * Terminate the transaction reaper. This is a synchronous operation
- * and will only return once the reaper has been shutdown cleanly.
- *
- * Note, this method assumes that the transaction system has been
- * shutdown already so no new transactions can be created, or we
- * could be here for a long time!
- *
- * @param waitForTransactions if <code>true</code> then the reaper will
- * wait until all transactions have terminated (or been terminated by it).
- * If <code>false</code> then the reaper will call setRollbackOnly on all
- * the transactions.
- */
-
- public static synchronized void terminate (boolean waitForTransactions)
- {
- if (_theReaper != null)
- {
- _theReaper.shutdown(waitForTransactions);
- _theReaper = null;
- }
- }
-
- public static boolean isDynamic() {
+ public static boolean isDynamic()
+ {
return _dynamic;
}
- /*
- * Don't bother synchronizing as this is only an estimate anyway.
- */
+ public static synchronized long transactionLifetime()
+ {
+ return _lifetime.get();
+ }
- public static final synchronized long transactionLifetime()
- {
- return TransactionReaper._lifetime;
- }
+ public static final long defaultCheckPeriod = 120000; // in milliseconds
+ public static final long defaultCancelWaitPeriod = 500; // in milliseconds
+ public static final long defaultCancelFailWaitPeriod = 500; // in milliseconds
+ public static final int defaultZombieMax = 8;
- public static final long defaultCheckPeriod = 120000; // in milliseconds
- public static final long defaultCancelWaitPeriod = 500; // in milliseconds
- public static final long defaultCancelFailWaitPeriod = 500; // in milliseconds
- public static final int defaultZombieMax = 8;
+ static final synchronized void reset()
+ {
+ _theReaper = null;
+ }
- static final void reset()
- {
- _theReaper = null;
- }
+ private final ReaperElementManager _reaperElements = new ReaperElementManager();
- private SortedSet _transactions = Collections.synchronizedSortedSet(new TreeSet()); // C of ReaperElement
- private Map _timeouts = Collections.synchronizedMap(new HashMap()); // key = Reapable, value = ReaperElement
+ // The keys are actually Reapable, as that's what insert takes. However, some functions use get(Object)
+ // and rely on clever hashcode/equals behaviour, especially for the JTS. Thus the generics key type is Object.
+ private final ConcurrentMap<Object, ReaperElement> _timeouts = new ConcurrentHashMap<Object, ReaperElement>();
- private final Map<Reapable, ReaperElement> _pendingInsertions = new ConcurrentHashMap<Reapable, ReaperElement>();
+ private final List<ReaperElement> _workQueue = new LinkedList<ReaperElement>();
- private List _workQueue = new LinkedList(); // C of ReaperElement
+ private final Vector<ReaperMonitor> _listeners = new Vector<ReaperMonitor>(); // TODO sync properly
- private long _checkPeriod = 0;
+ private long _checkPeriod = 0;
- /**
- * number of millisecs delay afer a cancel() is scheduled
- * before the reaper tries to interrupt the worker thread
- * executing the cancel()
- */
- private long _cancelWaitPeriod = 0;
+ // Although it is atomic, writes (but not reads) need to by synchronized(this) i.e. on the TransactionReaper instance
+ // in order to ensure proper timing with respect to wait/notify and wakeups on the _reaperElements queue.
+ private final AtomicLong nextDynamicCheckTime = new AtomicLong(Long.MAX_VALUE);
- /**
- * number of millisecs delay afer a worker thread is
- * interrupted before the reaper writes the it off as a zombie
- * and starts a new thread
- */
- private long _cancelFailWaitPeriod = 0;
+ /**
+ * number of millisecs delay afer a cancel() is scheduled
+ * before the reaper tries to interrupt the worker thread
+ * executing the cancel()
+ */
+ private long _cancelWaitPeriod = 0;
- /**
- * threshold for count of non-exited zombies at which system
- * starts logging error messages
- */
- private int _zombieMax = 0;
+ /**
+ * number of millisecs delay afer a worker thread is
+ * interrupted before the reaper writes the it off as a zombie
+ * and starts a new thread
+ */
+ private long _cancelFailWaitPeriod = 0;
- private static TransactionReaper _theReaper = null;
+ /**
+ * threshold for count of non-exited zombies at which system
+ * starts logging error messages
+ */
+ private int _zombieMax = 0;
- private static ReaperThread _reaperThread = null;
+ private static volatile TransactionReaper _theReaper = null;
- private static ReaperWorkerThread _reaperWorkerThread = null;
+ private static ReaperThread _reaperThread = null;
- private static boolean _dynamic = true;
+ private static ReaperWorkerThread _reaperWorkerThread = null;
- private static long _lifetime = 0;
+ private static boolean _dynamic = true;
- private static int _zombieCount = 0;
+ private static AtomicLong _lifetime = new AtomicLong(0);
+ private static int _zombieCount = 0;
+
private boolean _inShutdown = false;
}
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/internal/arjuna/coordinator/ReaperElement.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/internal/arjuna/coordinator/ReaperElement.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/internal/arjuna/coordinator/ReaperElement.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -31,13 +31,15 @@
package com.arjuna.ats.internal.arjuna.coordinator;
-import com.arjuna.common.util.logging.*;
+import java.util.concurrent.atomic.AtomicInteger;
-import com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.arjuna.coordinator.Reapable;
import com.arjuna.ats.arjuna.logging.FacilityCode;
+import com.arjuna.ats.arjuna.logging.tsLogger;
+import com.arjuna.common.util.logging.DebugLevel;
+import com.arjuna.common.util.logging.VisibilityLevel;
-public class ReaperElement implements Comparable
+public class ReaperElement implements Comparable<ReaperElement>
{
/*
@@ -48,7 +50,7 @@
public ReaperElement(Reapable control, int timeout)
{
- if (tsLogger.arjLogger.debugAllowed())
+ if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug(DebugLevel.CONSTRUCTORS,
VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION,
@@ -72,19 +74,22 @@
_bias = getBiasCounter();
}
+
+ public String toString ()
+ {
+ return "ReaperElement < "+_control+", "+_timeout+", "+statusName()+", "+_worker+" >";
+ }
/**
* Order by absoluteTimeout first, then by Uid.
* This is required so that the set maintained by the TransactionReaper
* is in timeout order for efficient processing.
*
- * @param o
- * @return
+ * @param other the ReaperElement to compare
+ * @return 0 if equal, 1 if this is greater, -1 if this is smaller
*/
- public int compareTo(Object o)
+ public int compareTo(ReaperElement other)
{
- ReaperElement other = (ReaperElement)o;
-
if(this == other) {
return 0;
}
@@ -114,16 +119,20 @@
// bias is used to distinguish/sort instances with the same _absoluteTimeoutMills
// as using Uid for this purpose is expensive. JBTM-611
- private static int biasCounter = 0;
+ private static int MAX_BIAS = 1000000;
+ private static AtomicInteger biasCounter = new AtomicInteger();
- public static synchronized int getBiasCounter()
+ private static int getBiasCounter()
{
- if(biasCounter >= 1000000-1) {
- biasCounter = 0;
- } else {
- biasCounter++;
- }
- return biasCounter;
+ int value = 0;
+ do {
+ value = biasCounter.getAndIncrement();
+ if(value == MAX_BIAS) {
+ biasCounter.set(0);
+ }
+ } while(value >= MAX_BIAS);
+
+ return value; // range 0 to MAX_BIAS-1 inclusive.
}
public int _timeout;
Added: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/internal/arjuna/coordinator/ReaperElementManager.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/internal/arjuna/coordinator/ReaperElementManager.java (rev 0)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/classes/com/arjuna/ats/internal/arjuna/coordinator/ReaperElementManager.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -0,0 +1,175 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags.
+ * See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ * This copyrighted material is made available to anyone wishing to use,
+ * modify, copy, or redistribute it subject to the terms and conditions
+ * of the GNU Lesser General Public License, v. 2.1.
+ * This program is distributed in the hope that it will be useful, but WITHOUT A
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ * You should have received a copy of the GNU Lesser General Public License,
+ * v.2.1 along with this distribution; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * (C) 2009,
+ * @author JBoss by Red Hat.
+ */
+package com.arjuna.ats.internal.arjuna.coordinator;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/*
+ * Encapsulation of a specialised data structure with API and performance characteristics
+ * designed specifically for use by the transaction reaper.
+ *
+ * ReaperElements represent transactions which need timing out. To do this, the reaper needs
+ * to wake periodically and process any timeouts that are due. New elements are added on transaction
+ * creation and will be removed prior to their timeout if they terminate normally.
+ *
+ * For high concurrency, normal inserts and removes should not block. However, to determine the next element
+ * which needs (will need) processing, the elements must be ordered or at least searched. These requirements
+ * are in conflict, since ordering/searching requires stability i.e. locking.
+ *
+ * To achieve the desired performance characteristics, we combine two data structures: an unsorted, concurrent
+ * collection and a sorted, non-threadsafe one which is guarded by the the ReaperElementManager instance lock.
+ *
+ * Inserts are done, potentially concurrently, to the unsorted hash set. Removes likewise check this first
+ * and can return successfully without blocking if the element is found in this collection. Thus the insert/remove
+ * are cheap operations.
+ *
+ * When it is required to know the smallest (i.e. earliest to timeout) element, the contents
+ * of the unsorted set are moved to the sorted set. Since this happens infrequently compared to the insert/delete,
+ * only a fraction of the elements inserted should ever be copied - most will be removed without ever migrating.
+ *
+ * Note that additional external synchronization will be needed to ensure first element does not change
+ * between getFirst and any operation depending on its timeout value. This is the TransactionReaper's problem.
+ *
+ * The sorted set is maintained manually, rather than using Collections.sort or other comparator based structure.
+ * This is because compareTo on reaper elements is relatively expensive and we wish to avoid liner scans to minimise
+ * the number of such calls. Hence we prefer ArrayList with binary search, despite the higher insert/remove cost
+ * compared to LinkedList.
+ *
+ * Pay careful attention to locking and performance characteristics if altering this class.
+ *
+ *
+ * @author Jonathan Halliday (jonathan.halliday at redhat.com) 2009-10
+ */
+public class ReaperElementManager
+{
+ /**
+ * @return the first (i.e. earliest to time out) element of the colleciton or null if empty
+ */
+ public synchronized ReaperElement getFirst() {
+ flushPending(); // we need to order the elements before we can tell which is first.
+ if(elementsOrderedByTimeout.isEmpty()) {
+ return null;
+ } else {
+ return elementsOrderedByTimeout.get(0);
+ }
+ }
+
+ // Note - unsynchronized for performance.
+ public void add(ReaperElement reaperElement) throws IllegalStateException {
+ if(pendingInsertions.putIfAbsent(reaperElement, reaperElement) != null) {
+ // note this is best effort - we'll allow double inserts if the element is also in the ordered set.
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * @param reaperElement the reaper element to reorder in the sorted set.
+ * @param delayMillis the amount of time to increment the element's timeout by.
+ * @return the new soonest timeout in the set (not necessarily that of the reordered element)
+ */
+ public synchronized long reorder(ReaperElement reaperElement, long delayMillis) {
+ // assume it must be in the sorted list, as it was likely obtained via getFirst...
+ removeSorted(reaperElement);
+ // we could add delay to the original timeout, but using current time is probably safer.
+ reaperElement.setAbsoluteTimeout((System.currentTimeMillis() + delayMillis));
+ // reinsert into its new position.
+ insertSorted(reaperElement);
+
+ // getFirst takes care of flushing the pending set for us.
+ return getFirst().getAbsoluteTimeout();
+ }
+
+ // use only for testing, it's nasty from a performance perspective.
+ public synchronized int size() {
+ return (elementsOrderedByTimeout.size() + pendingInsertions.size());
+ }
+
+ public synchronized boolean isEmpty() {
+ return (elementsOrderedByTimeout.isEmpty() && pendingInsertions.isEmpty());
+ }
+
+ // strange hack to force instant expire of tx during shutdown.
+ public synchronized void setAllTimeoutsToZero() {
+ flushPending();
+ for(ReaperElement reaperElement : elementsOrderedByTimeout) {
+ reaperElement.setAbsoluteTimeout(0);
+ }
+ }
+
+ // Note - mostly unsynchronized for performance.
+ public void remove(ReaperElement reaperElement) {
+ if(pendingInsertions.remove(reaperElement) != null) {
+ return;
+ }
+
+ // we missed finding it in the unsorted set - perhaps it has already been copied to the sorted set...
+ synchronized(this) {
+ removeSorted(reaperElement);
+ }
+ }
+
+ ////////////
+
+ // Private methods and structures are guarded where needed by ReaperElementManager instance locks in the
+ // public methods - see class header doc comments for concurrency/performance info.
+
+ private final ArrayList<ReaperElement> elementsOrderedByTimeout = new ArrayList<ReaperElement>();
+ private final ConcurrentHashMap<ReaperElement, ReaperElement> pendingInsertions = new ConcurrentHashMap<ReaperElement, ReaperElement>();
+
+ private void removeSorted(ReaperElement reaperElement) {
+ int location = Collections.binarySearch(elementsOrderedByTimeout, reaperElement);
+ if(location >= 0) {
+ elementsOrderedByTimeout.remove(location);
+ }
+ }
+
+ private void insertSorted(ReaperElement reaperElement) {
+ int location = Collections.binarySearch(elementsOrderedByTimeout, reaperElement);
+ if(location >= 0) {
+ throw new IllegalStateException();
+ }
+ int insertionPoint = -(location + 1);
+ elementsOrderedByTimeout.add(insertionPoint, reaperElement);
+ }
+
+ private void flushPending() {
+
+ // purge the pending inserts before doing anything else. This is potentially expensive.
+ // Future versions may prefer to insert only a portion of the pending set, or
+ // iterate it each time to determine the smallest (head) element.
+ Set<Map.Entry<ReaperElement,ReaperElement>> entrySet = pendingInsertions.entrySet();
+ if(entrySet != null) {
+ Iterator<Map.Entry<ReaperElement, ReaperElement>> queueIter = entrySet.iterator();
+ // iterator is weakly consistent - will traverse elements present at its time of creation,
+ // may or may not see later updates.
+ while(queueIter.hasNext()) {
+ Map.Entry<ReaperElement,ReaperElement> entry = queueIter.next();
+ ReaperElement element = entry.getValue();
+ // insert/remove not locked, so we are careful to check that we don't insert
+ // an element that has been removed from the pending set by a concurrent thread.
+ if(entrySet.remove(entry)) {
+ insertSorted(element);
+ }
+ }
+ }
+ }
+}
Added: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/byteman-scripts/objectstore.txt
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/byteman-scripts/objectstore.txt (rev 0)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/byteman-scripts/objectstore.txt 2010-05-13 11:44:31 UTC (rev 32861)
@@ -0,0 +1,109 @@
+########################################################################
+#
+# byteman script used to ensure that core tests can sequence various
+# operations which normally depend upon waiting around for a background
+# thread to be scheduled.
+#
+
+#########################################################################
+# 3 rules to stop the log purger from proceeding until it is signalled
+# at a suitable point during a test. the log purger also signals in
+# order to allow the test code to detect that a purge has completed
+# should it wish to do so
+
+# rule to ensure the transaction log purger uses a very small timeout
+# so that it is ready to proceed straight away when signalled
+
+RULE override TRANSACTION_LOG_PURGE_TIME
+CLASS com.arjuna.ats.internal.arjuna.objectstore.LogStore
+METHOD <clinit>
+AT ENTRY
+BIND NOTHING
+IF TRUE
+DO com.arjuna.ats.arjuna.common.arjPropertyManager.getObjectStoreEnvironmentBean().setPurgeTime(100)
+ENDRULE
+
+# rule to ensure that the LogStore does not proceed to purge any
+# logs until the test signals it do so
+
+RULE delay log purge
+CLASS com.arjuna.ats.internal.arjuna.objectstore.LogPurger
+METHOD run()
+AT CALL writeRemovalEntries
+BIND NOTHING
+IF TRUE
+DO debug("LogStore waiting before purge"),
+ waitFor("LogStore.purge"),
+ flag("LogStore do purge"),
+ debug("LogStore proceeding with purge")
+ENDRULE
+
+# matching rule to ensure that the LogStore signals any thread which
+# is waiting for it to finish purging the logs
+
+RULE done log purge
+CLASS com.arjuna.ats.internal.arjuna.objectstore.LogPurger
+METHOD run()
+AFTER CALL truncateLogs
+BIND NOTHING
+IF TRUE
+DO debug("Signalling purge complete"),
+ signalWake("LogStore.purged", true),
+ flag("LogStore done purge"),
+ debug("Signalled purge complete")
+ENDRULE
+
+#########################################################################
+#
+# rules appropriate to specific tests
+#
+# n.b. several of the test don't want the purger to run. this happens by
+# default since the purger hist the waitFor and never gets signalled
+#
+
+#########################################################################
+# LogStoreRecoveryTest wants to delay the purge until it is ready for it
+# and then delay proceeding with the test until the purge has actually
+# happened
+
+RULE log store recovery test allow purge to proceed
+CLASS com.hp.mwtests.ts.arjuna.objectstore.LogStoreRecoveryTest
+METHOD test()
+AT CALL InputObjectState.<init>
+BIND NOTHING
+IF TRUE
+DO debug("Signalling purge to proceed"),
+ signalWake("LogStore.purge", true),
+ debug("Signalled purge to proceed -- waiting for completion"),
+ waitFor("LogStore.purged"),
+ debug("Purge completed")
+ENDRULE
+
+# throw an error if the purge did not start and finish correctly
+
+RULE log store recovery test throw error if not purged
+CLASS com.hp.mwtests.ts.arjuna.objectstore.LogStoreRecoveryTest
+METHOD test()
+AT EXIT
+BIND NOTHING
+IF !(flagged("LogStore do purge") && flagged("LogStore done purge"))
+DO throw RuntimeException("failed to run purge")
+ENDRULE
+
+#########################################################################
+# LogStoreTest2 wants to delay the purge until it is ready for it
+# and then delay proceeding with the test until the purge has actually
+# happened
+
+RULE log store recovery test allow purge to proceed 2
+CLASS com.hp.mwtests.ts.arjuna.objectstore.LogStoreTest2
+METHOD test()
+AT CALL InputObjectState.<init>
+BIND NOTHING
+IF TRUE
+DO debug("Signalling purge to proceed"),
+ signalWake("LogStore.purge", true),
+ debug("Signalled purge to proceed -- waiting for completion"),
+ waitFor("LogStore.purged"),
+ debug("Purge completed")
+ENDRULE
Added: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/byteman-scripts/reaper.txt
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/byteman-scripts/reaper.txt (rev 0)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/byteman-scripts/reaper.txt 2010-05-13 11:44:31 UTC (rev 32861)
@@ -0,0 +1,359 @@
+########################################################################
+#
+# byteman script used to ensure that core transaction reaper tests can
+# sequence various operations which normally depend upon waiting around
+# for the reaper thread and reaper worker thread to be scheduled.
+#
+
+#########################################################################
+# rules to control progress of the transaction reaper thread
+#
+
+# for each possible point at which a client may want to pause the
+# reaper check if a rendezvous has been set up and if so make the
+# reaper rendezvous twice so the client can latch it and then unleash it
+
+# rendezvous before checking the reaper element queue
+
+RULE pause transaction reaper 1
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD check
+AT SYNCHRONIZE
+BIND NOTHING
+IF isRendezvous("reaper1", 2)
+DO debug("reaper1"),
+ rendezvous("reaper1"),
+ debug("reaper1"),
+ rendezvous("reaper1")
+ENDRULE
+
+# rendezvous and set a flag to record which reaper element is about to be timed out
+# if a client enables this rendezvous then it must also check and clear the flag
+RULE track next element to be processed
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD check
+AT SYNCHRONIZE 2
+BIND NOTHING
+IF isRendezvous("reaper element", 2)
+DO debug("reaper element"),
+ flag($reaperElement._control),
+ rendezvous("reaper element"),
+ debug("reaper element"),
+ rendezvous("reaper element")
+ENDRULE
+
+# rendezvous before processing a timed out reaper element
+
+RULE pause transaction reaper 2
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD check
+AT READ _status
+BIND NOTHING
+IF isRendezvous("reaper2", 2)
+DO debug("reaper2"),
+ rendezvous("reaper2"),
+ debug("reaper2"),
+ rendezvous("reaper2")
+ENDRULE
+
+# rendezvous before queueing in the worker queue a timed out reaper element tagged for cancellation
+
+RULE pause transaction reaper 3
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD check
+AT CALL add 2
+BIND NOTHING
+IF isRendezvous("reaper3", 2)
+DO debug("reaper3"),
+ rendezvous("reaper3"),
+ debug("reaper3"),
+ rendezvous("reaper3")
+ENDRULE
+
+# rendezvous before rescheduling checkup on a timed out reaper element tagged for cancellation
+
+RULE pause transaction reaper 4
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD check
+AT CALL add 3
+BIND NOTHING
+IF isRendezvous("reaper4", 2)
+DO debug("reaper4"),
+ rendezvous("reaper4"),
+ debug("reaper4"),
+ rendezvous("reaper4")
+ENDRULE
+
+# rendezvous before interrupting a timed out reaper element tagged for cancellation
+
+RULE pause transaction reaper 5
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD check
+AT CALL interrupt
+BIND NOTHING
+IF isRendezvous("reaper5", 2)
+DO debug("reaper5"),
+ rendezvous("reaper5"),
+ debug("reaper5"),
+ rendezvous("reaper5")
+ENDRULE
+
+# rendezvous before setting a worker thread to be a zombie
+
+RULE pause transaction reaper 6
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD check
+AT WRITE _status 3
+BIND NOTHING
+IF isRendezvous("reaper6", 2)
+DO debug("reaper6"),
+ rendezvous("reaper6"),
+ debug("reaper6"),
+ rendezvous("reaper6")
+ENDRULE
+
+# rendezvous before calling prevent commit on a reaper element tagged cancel interrupted
+
+RULE pause transaction reaper 7
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD check
+AT WRITE _status 3
+BIND NOTHING
+IF isRendezvous("reaper7", 2)
+DO debug("reaper7"),
+ rendezvous("reaper7"),
+ debug("reaper7"),
+ rendezvous("reaper7")
+ENDRULE
+
+#########################################################################
+# rules to control progress of the transaction reaper worker thread
+#
+
+# for each possible point at which a client may want to pause the
+# reaper worker thread check if a rendezvous has been set up and if so
+# make the reaper worker rendezvous twice so the client can latch it and
+# then unleash it
+
+# rendezvous before removing an element from the reaper element queue
+
+RULE pause transaction reaper worker 1
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD doCancellations
+AT SYNCHRONIZE
+BIND NOTHING
+IF isRendezvous("reaperworker1", 2)
+DO debug("reaperworker1"),
+ rendezvous("reaperworker1"),
+ debug("reaperworker1"),
+ rendezvous("reaperworker1")
+ENDRULE
+
+# rendezvous before marking an element as CANCEL
+
+RULE pause transaction reaper worker 2
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD doCancellations
+AT SYNCHRONIZE 2
+BIND NOTHING
+IF isRendezvous("reaperworker2", 2)
+DO debug("reaperworker2"),
+ rendezvous("reaperworker2"),
+ debug("reaperworker2"),
+ rendezvous("reaperworker2")
+ENDRULE
+
+# rendezvous before calling cancel on an element
+
+RULE pause transaction reaper worker 3
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD doCancellations
+AT CALL cancel
+BIND NOTHING
+IF isRendezvous("reaperworker3", 2)
+DO debug("reaperworker3"),
+ rendezvous("reaperworker3"),
+ debug("reaperworker3"),
+ rendezvous("reaperworker3")
+ENDRULE
+
+# rendezvous before calling prevent_commit on an element
+
+RULE pause transaction reaper worker 4
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD doCancellations
+AT CALL prevent_commit
+BIND NOTHING
+IF isRendezvous("reaperworker4", 2)
+DO debug("reaperworker4"),
+ rendezvous("reaperworker4"),
+ debug("reaperworker4"),
+ rendezvous("reaperworker4")
+ENDRULE
+
+#########################################################################
+# rules to track and flag actions by the reaper and reaper worker
+
+RULE ReaperTestCase2 flag interrupt
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD check
+AFTER CALL interrupt
+BIND NOTHING
+IF TRUE
+DO debug("reaper called interrupt on " + $reaperElement),
+ flag("interrupted")
+ENDRULE
+
+RULE ReaperTestCase2 flag zombie
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD check
+AFTER WRITE _zombieCount
+BIND NOTHING
+IF TRUE
+DO debug("reaper incremented zombie count " + $reaperElement),
+ flag("zombied")
+ENDRULE
+
+#########################################################################
+#
+# rules appropriate to specific tests
+
+#########################################################################
+# ReaperMonitorTest wants remote control of the reaper thread
+#
+
+RULE ReaperMonitorTest reaper remote control
+CLASS com.hp.mwtests.ts.arjuna.reaper.ReaperMonitorTest
+METHOD test()
+AT CALL TransactionReaper.insert
+BIND NOTHING
+IF TRUE
+DO createRendezvous("reaper1", 2, true)
+ENDRULE
+
+# ReaperMonitorTest wants to delay the reaper check until it has inserted
+# a reapable
+
+RULE ReaperMonitorTest unlatch reaper thread
+CLASS com.hp.mwtests.ts.arjuna.reaper.ReaperMonitorTest
+METHOD test()
+AFTER CALL TransactionReaper.insert
+BIND NOTHING
+IF TRUE
+DO debug("reaper1"),
+ rendezvous("reaper1"),
+ # ensure timed out
+ delay(1000),
+ debug("reaper1"),
+ rendezvous("reaper1")
+ENDRULE
+
+#########################################################################
+# ReaperTestCaseControl provides methods used by its three
+# ReaperTestCase<N> subclasses to enable, disable and trigger rule
+# activity during the course of the test.
+
+RULE ReaperTestCaseControl enable trigger rendezvous
+CLASS com.hp.mwtests.ts.arjuna.reaper.ReaperTestCaseControl
+METHOD enableRendezvous(Object, boolean)
+AT ENTRY
+BIND NOTHING
+IF TRUE
+DO createRendezvous($1, 2, $2)
+ENDRULE
+
+RULE ReaperTestCaseControl disable trigger rendezvous
+CLASS com.hp.mwtests.ts.arjuna.reaper.ReaperTestCaseControl
+METHOD disableRendezvous(Object)
+AT ENTRY
+BIND NOTHING
+IF TRUE
+DO deleteRendezvous($1, 2)
+ENDRULE
+
+# if the supplied string matches a known rendezvous then trigger it
+
+RULE ReaperTestCaseControl rendezvous
+CLASS com.hp.mwtests.ts.arjuna.reaper.ReaperTestCaseControl
+METHOD triggerRendezvous(Object)
+AT ENTRY
+BIND thread = Thread.currentThread()
+IF isRendezvous($1, 2)
+DO debug("" + $1 + " " + thread),
+ rendezvous($1),
+ return
+ENDRULE
+
+RULE ReaperTestCaseControl rendezvous 2
+CLASS com.hp.mwtests.ts.arjuna.reaper.ReaperTestCaseControl
+METHOD triggerRendezvous(Object)
+AT ENTRY
+BIND NOTHING
+IF NOT isRendezvous($1, 2)
+DO throw RuntimeException("invalid rendezvous for trigger " + $1)
+ENDRULE
+
+# trigger a delay
+
+RULE ReaperTestCaseControl wait
+CLASS com.hp.mwtests.ts.arjuna.reaper.ReaperTestCaseControl
+METHOD triggerWait(int)
+AT ENTRY
+BIND NOTHING
+IF TRUE
+DO debug("wait " + $1),
+ delay($1),
+ return
+ENDRULE
+
+RULE ReaperTestCaseControl check and clear
+CLASS com.hp.mwtests.ts.arjuna.reaper.ReaperTestCaseControl
+METHOD checkAndClearFlag(Object)
+AT ENTRY
+BIND value = flagged($1)
+IF TRUE
+DO debug("setAndClear(" + $1 + ") => " + value),
+ clear($1),
+ return value
+ENDRULE
+
+#########################################################################
+# ReaperTestCase wants to ensure that the reaper is single stepped
+# through processing of inserted reaper elements
+#
+
+# debug tracing rules
+# RULE ReaperTestCase trace element remove
+# CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+# METHOD doCancellations
+# AT CALL removeElement
+# BIND NOTHING
+# IF TRUE
+# DO debug("removing reapable " + $e)
+# ENDRULE
+
+# RULE ReaperTestCase trace element add
+# CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+# METHOD check
+# AT CALL List.add
+# BIND NOTHING
+# IF TRUE
+# DO debug("adding reapable " + $e)
+# ENDRULE
+
+#########################################################################
+# ReaperTestCase3 wants the reaper and reaper worker threads to
+# be suspended until it has reset the timeouts for the reapables
+# to zero forcing them to be cancelled early
+#
+
+RULE ReaperTestCase3 unlatch reaper and reaper worker
+CLASS com.arjuna.ats.arjuna.coordinator.TransactionReaper
+METHOD shutdown
+AT CALL wait
+BIND NOTHING
+IF TRUE
+DO debug("removing latches on reaper and reaper worker"),
+ deleteRendezvous("reaper1", 2),
+ deleteRendezvous("reaperworker1", 2)
+ENDRULE
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -20,9 +20,6 @@
*/
package com.hp.mwtests.ts.arjuna.reaper;
-import junit.framework.TestCase;
-import junit.framework.Test;
-import junit.framework.TestSuite;
import com.arjuna.ats.arjuna.coordinator.TransactionReaper;
import com.arjuna.ats.arjuna.coordinator.Reapable;
import com.arjuna.ats.arjuna.common.Uid;
@@ -33,109 +30,151 @@
/**
* Exercises some aspects of the TransactionReaper functionality.
+ *
* @author jonathan.halliday at redhat.com, 2007-04-30
*/
-public class ReaperTestCase extends TestCase
+public class ReaperTestCase extends ReaperTestCaseControl
{
- public static Test suite() {
- return new TestSuite(ReaperTestCase.class);
- }
+ public void testReaper() throws Exception
+ {
- public void testReaper() throws Exception {
+ TransactionReaper reaper = TransactionReaper.transactionReaper();
- // test set+readback of interval
- TransactionReaper.create(100);
- TransactionReaper reaper = TransactionReaper.transactionReaper();
- // set value is ignored in default DYNAMIC mode, it uses max long instead.
- assertEquals(Long.MAX_VALUE, reaper.checkingPeriod());
+ Reapable reapable = new MockReapable(new Uid());
+ Reapable reapable2 = new MockReapable(new Uid());
+ Reapable reapable3 = new MockReapable(new Uid());
- Reapable reapable = new MockReapable(new Uid());
- Reapable reapable2 = new MockReapable(new Uid());
- Reapable reapable3 = new MockReapable(new Uid());
+ ReaperElement reaperElement = new ReaperElement(reapable, 30);
+ ReaperElement reaperElement2 = new ReaperElement(reapable2, 20);
+ ReaperElement reaperElement3 = new ReaperElement(reapable3, 10);
- ReaperElement reaperElement = new ReaperElement(reapable, 30);
- ReaperElement reaperElement2 = new ReaperElement(reapable2, 20);
- ReaperElement reaperElement3 = new ReaperElement(reapable3, 10);
+ // test that ordering is by timeout, regardless of insertion order
+ SortedSet sortedSet = new TreeSet();
+ sortedSet.add(reaperElement);
+ sortedSet.add(reaperElement3);
+ sortedSet.add(reaperElement2);
- // test that ordering is by timeout, regardless of insertion order
- SortedSet sortedSet = new TreeSet();
- sortedSet.add(reaperElement);
- sortedSet.add(reaperElement3);
- sortedSet.add(reaperElement2);
+ assertEquals(sortedSet.first(), reaperElement3);
+ assertEquals(sortedSet.last(), reaperElement);
- assertEquals(sortedSet.first(), reaperElement3);
- assertEquals(sortedSet.last(), reaperElement);
+ // test insertion of timeout=0 is a nullop
+ reaper.insert(reapable, 0);
+ assertEquals(0, reaper.numberOfTransactions());
+ assertEquals(0, reaper.numberOfTimeouts());
+ reaper.remove(reapable);
- // test insertion of timeout=0 is a nullop
- assertTrue(reaper.insert(reapable, 0));
- assertEquals(0, reaper.numberOfTransactions());
- assertEquals(0, reaper.numberOfTimeouts());
- assertFalse(reaper.remove(reapable));
+ // test that duplicate insertion fails
+ reaper.insert(reapable, 10);
+ assertEquals(1, reaper.numberOfTransactions());
+ assertEquals(1, reaper.numberOfTimeouts());
+ try {
+ reaper.insert(reapable, 10);
+ fail("duplicate insert failed to blow up");
+ } catch(Exception e) {
+ }
+ reaper.remove(reapable);
+ assertEquals(0, reaper.numberOfTransactions());
+ assertEquals(0, reaper.numberOfTimeouts());
- // test that duplicate insertion fails
- assertTrue(reaper.insert(reapable, 10));
- assertFalse(reaper.insert(reapable, 10));
- assertEquals(1, reaper.numberOfTransactions());
- assertEquals(1, reaper.numberOfTimeouts());
- assertTrue(reaper.remove(reapable));
- assertEquals(0, reaper.numberOfTransactions());
- assertEquals(0, reaper.numberOfTimeouts());
+ // test that timeout change fails
+ reaper.insert(reapable, 10);
+ try {
+ reaper.insert(reapable, 20);
+ fail("timeout change insert failed to blow up");
+ } catch(Exception e) {
+ }
+ assertEquals(1, reaper.numberOfTransactions());
+ assertEquals(1, reaper.numberOfTimeouts());
+ assertEquals(10, reaper.getTimeout(reapable));
+ reaper.remove(reapable);
+ assertEquals(0, reaper.numberOfTransactions());
+ assertEquals(0, reaper.numberOfTimeouts());
- // test that timeout change fails
- assertTrue(reaper.insert(reapable, 10));
- assertFalse(reaper.insert(reapable, 20));
- assertEquals(1, reaper.numberOfTransactions());
- assertEquals(1, reaper.numberOfTimeouts());
- assertEquals(10, reaper.getTimeout(reapable));
- assertTrue(reaper.remove(reapable));
- assertEquals(0, reaper.numberOfTransactions());
- assertEquals(0, reaper.numberOfTimeouts());
+ // enable a repeatable rendezvous before checking the reapable queue
+ enableRendezvous("reaper1", true);
+ // enable a repeatable rendezvous before scheduling a reapable in the worker queue for cancellation
+ enableRendezvous("reaper2", true);
+ // enable a repeatable rendezvous before checking the worker queue
+ enableRendezvous("reaperworker1", true);
- // test reaping
- reaper.insert(reapable, 1); // seconds
- reaper.insert(reapable2, 5);
- assertEquals(2, reaper.numberOfTransactions());
- assertEquals(2, reaper.numberOfTimeouts());
- reaper.check();
- assertEquals(2, reaper.numberOfTransactions());
- Thread.sleep(2*1000);
- reaper.check();
- assertEquals(1, reaper.numberOfTransactions());
- assertEquals(1, reaper.numberOfTimeouts());
- Thread.sleep(4*1000);
- reaper.check();
- assertEquals(0, reaper.numberOfTransactions());
- assertEquals(0, reaper.numberOfTimeouts());
+ // test reaping
+ reaper.insert(reapable, 1); // seconds
+ reaper.insert(reapable2, 2);
- }
+ // the reaper will be latched before it processes any of the reapables
+ triggerRendezvous("reaper1");
+ assertEquals(2, reaper.numberOfTransactions());
+ assertEquals(2, reaper.numberOfTimeouts());
+ // ensure we have waited at lest 1 second so the first reapable is timed out
+ triggerWait(1000);
+ // let the reaper proceed with the dequeue and add the entry to the work queue
+ triggerRendezvous("reaper1");
+ triggerRendezvous("reaper2");
+ triggerRendezvous("reaper2");
+ // now latch the reaper worker at the dequeue
+ triggerRendezvous("reaperworker1");
+ // we shoudl still have two reapables in the reaper queue
+ assertEquals(2, reaper.numberOfTransactions());
+ assertEquals(2, reaper.numberOfTimeouts());
+ // now let the worker process the work queue element -- it should not call cancel since the
+ // mock reapable will not claim to be running
+ triggerRendezvous("reaperworker1");
+ // latch the reaper and reaper worker before they check their respective queues
+ // latch the reaper before it dequeues the next reapable
+ triggerRendezvous("reaper1");
+ triggerRendezvous("reaperworker1");
+ // we should now have only 1 element in the reaper queue
+ assertEquals(1, reaper.numberOfTransactions());
+ assertEquals(1, reaper.numberOfTimeouts());
+ // ensure we have waited at lest 1 second so the second reapable is timed out
+ triggerWait(1000);
+ // now let the reaper proceed with the next dequeue and enqueue the reapable for the worker to process
+ triggerRendezvous("reaper1");
+ triggerRendezvous("reaper2");
+ triggerRendezvous("reaper2");
+ // relatch the reaper next time round the loop so we can be sure it is not monkeying around
+ // with the transactions queue
+ triggerRendezvous("reaper1");
+ // the worker is still latched so we should still have one entry in the work queue
+ assertEquals(1, reaper.numberOfTransactions());
+ assertEquals(1, reaper.numberOfTimeouts());
+ // now let the worker process the work queue element -- it should not call cancel since the
+ // mock reapable wil not claim to be running
+ triggerRendezvous("reaperworker1");
+ // latch reaper worker again so we know it has finished processing the element
+ triggerRendezvous("reaperworker1");
+ assertEquals(0, reaper.numberOfTransactions());
+ assertEquals(0, reaper.numberOfTimeouts());
+ }
- public class MockReapable implements Reapable
- {
- public MockReapable(Uid uid) {
- this.uid = uid;
- }
+ public class MockReapable implements Reapable
+ {
+ public MockReapable(Uid uid)
+ {
+ this.uid = uid;
+ }
- public boolean running()
- {
- return false; //To change body of implemented methods use File | Settings | File Templates.
- }
+ public boolean running()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
- public boolean preventCommit()
- {
- return false; //To change body of implemented methods use File | Settings | File Templates.
- }
+ public boolean preventCommit()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
- public int cancel()
- {
- return 0; //To change body of implemented methods use File | Settings | File Templates.
- }
+ public int cancel()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
- public Uid get_uid()
- {
- return uid;
- }
+ public Uid get_uid()
+ {
+ return uid;
+ }
- private Uid uid;
- }
+ private Uid uid;
+ }
}
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase2.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase2.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase2.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -20,286 +20,441 @@
*/
package com.hp.mwtests.ts.arjuna.reaper;
-import junit.framework.TestCase;
-import junit.framework.Test;
-import junit.framework.TestSuite;
import com.arjuna.ats.arjuna.coordinator.TransactionReaper;
-import com.arjuna.ats.arjuna.coordinator.Reapable;
-import com.arjuna.ats.arjuna.coordinator.ActionStatus;
import com.arjuna.ats.arjuna.common.Uid;
-import com.arjuna.ats.internal.arjuna.coordinator.ReaperElement;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
/**
* Exercise cancellation behaviour of TransactionReaper with resources
* that time out and, optionally, get wedged either when a cancel is
* tried and/or when an interrupt is delivered
*
- * @author Andrwe Dinn (adinn at redhat.com), 2007-07-09
+ * @author Andrew Dinn (adinn at redhat.com), 2007-07-09
*/
-public class ReaperTestCase2 extends TestCase
+public class ReaperTestCase2 extends ReaperTestCaseControl
{
- public static Test suite()
- {
- return new TestSuite(ReaperTestCase2.class);
- }
-
public void testReaper() throws Exception
{
- TransactionReaper.create(500);
- TransactionReaper reaper = TransactionReaper.transactionReaper();
+ TransactionReaper reaper = TransactionReaper.transactionReaper();
- // give the reaper worker time to start too
-
- Thread.sleep(1000);
-
- // create slow reapables some of which will not respond immediately
+ // create slow reapables some of which will not respond immediately
// to cancel requests and ensure that they get cancelled
// and that the reaper does not get wedged
- SlowReapable reapable1 = new SlowReapable(new Uid(), 2000, 0, true, true, false);
- SlowReapable reapable2 = new SlowReapable(new Uid(), 0, 0, true, true, false);
- SlowReapable reapable3 = new SlowReapable(new Uid(), 100, 2000, false, true, false);
- SlowReapable reapable4 = new SlowReapable(new Uid(), 1000, 1000, false, false, false);
+ // the rendezvous for the reapables are keyed by the reapable's uid
- // insert reapables so they timeout at 1 second intervals then
- // check progress of cancellations and rollbacks
+ Uid uid0 = new Uid();
+ Uid uid1 = new Uid();
+ Uid uid2 = new Uid();
+ Uid uid3 = new Uid();
- assertTrue(reaper.insert(reapable1, 1));
+ // reapable0 will return CANCELLED from cancel and will rendezvous inside the cancel call
+ // so we can delay it. prevent_commit should not get called so we don't care about the arguments
+ TestReapable reapable0 = new TestReapable(uid0, true, true, false, false);
+ // reapable1 will return CANCELLED from cancel and will not rendezvous inside the cancel call
+ // prevent_commit should not get called so we don't care about the arguments
+ TestReapable reapable1 = new TestReapable(uid1, true, false, false, false);
+ // reapable2 will return RUNNING from cancel and will rendezvous inside the cancel call
+ // the call will get delayed causing the worker to exit as a zombie
+ // prevent_commit will be called from the reaper thread and will fail but will not rendezvous
+ TestReapable reapable2 = new TestReapable(uid2, false, true, false, false);
+ // reapable3 will return RUNNING from cancel and will not rendezvous inside the cancel call
+ // prevent_commit should get called and should return true without a rendezvous
+ TestReapable reapable3 = new TestReapable(uid3, false, false, true, false);
- assertTrue(reaper.insert(reapable2, 2));
+ // enable a repeatable rendezvous before checking the reapable queue
+ enableRendezvous("reaper1", true);
+ // enable a repeatable rendezvous when synchronizing on a timed out reapoer element so we can check that
+ // the element is the one we expect.
+ enableRendezvous("reaper element", true);
+ // enable a repeatable rendezvous before processing a timed out reapable
+ // enableRendezvous("reaper2", true);
+ // enable a repeatable rendezvous before scheduling a reapable in the worker queue for cancellation
+ // enableRendezvous("reaper3", true);
+ // enable a repeatable rendezvous before rescheduling a reapable in the worker queue for cancellation
+ // enableRendezvous("reaper4", true);
+ // enable a repeatable rendezvous before interrupting a cancelled reapable
+ // enableRendezvous("reaper5", true);
+ // enable a repeatable rendezvous before marking a worker thread as a zombie
+ // enableRendezvous("reaper6", true);
+ // enable a repeatable rendezvous before marking a reapable as rollback only from the reaper thread
+ // enableRendezvous("reaper7", true);
+ // enable a repeatable rendezvous before checking the worker queue
+ enableRendezvous("reaperworker1", true);
+ // enable a repeatable rendezvous before marking a reapable as cancel
+ // enableRendezvous("reaperworker2", true);
+ // enable a repeatable rendezvous before calling cancel
+ // enableRendezvous("reaperworker3", true);
+ // enable a repeatable rendezvous before marking a reapable as rollback only from the worker thread
+ // enableRendezvous("reaperworker4", true);
- assertTrue(reaper.insert(reapable3, 3));
+ // enable a repeatable rendezvous for each of the test reapables which we have marked to
+ // perform a rendezvous
- assertTrue(reaper.insert(reapable4, 4));
+ enableRendezvous(uid0, true);
+ enableRendezvous(uid2, true);
- // make sure they were all registered
+ // STAGE I
+ // insert two reapables so they timeout at 1 second intervals then stall the first one and
+ // check progress of cancellations and rollbacks for both
- assertEquals(4, reaper.numberOfTransactions());
- assertEquals(4, reaper.numberOfTimeouts());
+ reaper.insert(reapable0, 1);
- // n.b. the reaper will not operate in dynamic mode by default
- // so we have to allow an extra checkPeriod millisecs for it
- // to detect timeouts (it may go back to sleep a few
- // milliseconds before a transaction times out). also by
- // default the reaper waits 500 msecs for a cancel to take
- // effect before interrupting and 500 msecs for an interrupt
- // to take effect before making a wedged worker a zombie. so
- // these need to be factored into this thread's delays when
- // tetsing the state of the reapables.
+ reaper.insert(reapable1, 1);
- // wait at most 2 seconds for the first reapable to be cancelled
+ //assertTrue(reaper.insert(reapable2, 1));
- int count = 0;
+ //assertTrue(reaper.insert(reapable3, 1));
- while (!reapable1.getCancelTried() && count < 20) {
- count++;
- Thread.sleep(100);
- }
+ // latch the reaper before it tries to process the queue
- assertTrue(count < 20);
+ triggerRendezvous("reaper1");
- // ensure that the second one gets cancelled even if the
- // first one is wedged
+ // make sure they were all registered
+ // the transactions queue should be
+ // UID0 RUNNING
+ // UID1 RUNNING
- count = 0;
+ assertEquals(2, reaper.numberOfTransactions());
+ assertEquals(2, reaper.numberOfTimeouts());
- while (reapable2.getRunning() && count < 15) {
- count++;
- Thread.sleep(100);
- }
+ // wait long enough to ensure both reapables have timed out
- assertTrue(count < 15);
+ triggerWait(1000);
- // ensure also that the second one gave up at the cancel and
- // not the mark for rollback
+ // now let the reaper dequeue the first reapable, process it and queue it for the worker thread
+ // to deal with
- assertTrue(reapable2.getCancelTried());
- assertTrue(!reapable2.getRollbackTried());
+ triggerRendezvous("reaper1");
- // ensure that the first one responded to the interrupt and
- // marks itself for rollback only
+ // latch the reaper at the reaper element check
- count = 0;
+ triggerRendezvous("reaper element");
- while (reapable1.getRunning() && count < 10) {
- count++;
- Thread.sleep(100);
- }
+ // check that we have dequeued reapable0
- assertTrue(count < 10);
- // the first one should not be running because it marks itself ActionStatus.ABORTED
- // - the reaper should not attempt to roll it back in this case
- assertTrue(!reapable1.getRollbackTried());
+ assertTrue(checkAndClearFlag(reapable0));
- // check that the third one refuses the cancel and gets marked
- // for rollback instead
+ // unlatch the reaper so it can process the element
- count = 0;
+ triggerRendezvous("reaper element");
- while (!reapable3.getCancelTried() && count < 25) {
- count++;
- Thread.sleep(100);
- }
+ // latch the reaper before it tests the queue again
- assertTrue(count < 25);
+ triggerRendezvous("reaper1");
- // ensure that it gets marked for rollback
+ // latch the reaperworker before it tries to dequeue from the worker queue
- count = 0;
+ triggerRendezvous("reaperworker1");
- while (reapable3.getRunning() && count < 10) {
- count++;
- Thread.sleep(100);
- }
+ // the transactions queue should be
+ // UID1 RUNNING
+ // UID0 CANCEL
- assertTrue(count < 10);
+ assertEquals(2, reaper.numberOfTransactions());
+ assertEquals(2, reaper.numberOfTimeouts());
- assertTrue(reapable3.getRollbackTried());
+ // now let the worker dequeue a reapable and proceed to call cancel
- // ensure the fourth one gets cancelled and marked for rolback
- // even though it does not play ball
+ triggerRendezvous("reaperworker1");
- count = 0;
+ // latch the first reapable inside cancel
- while (reapable4.getRunning() && count < 25) {
- count++;
- Thread.sleep(100);
- }
+ triggerRendezvous(uid0);
- assertTrue(count < 25);
+ // now let the reaper check the queue for the second reapable, dequeue it and add it to the
+ // worker queue
- assertTrue(reapable4.getCancelTried());
- assertTrue(reapable4.getRollbackTried());
+ triggerRendezvous("reaper1");
- // we should have a zombie worker so check that the thread
- // which tried the cancel is still runnning then sleep a bit
- // to give it time to complete and check it has exited
+ // latch the reaper at the reaper element check
- Thread worker = reapable4.getCancelThread();
+ triggerRendezvous("reaper element");
- assertTrue(worker.isAlive());
+ // check that we have dequeued reapable1
- count = 0;
+ assertTrue(checkAndClearFlag(reapable1));
- while (worker.isAlive() && count < 30) {
- count++;
- Thread.sleep(100);
- }
+ // unlatch the reaper so it can process the element
- assertTrue(count < 30);
- }
+ triggerRendezvous("reaper element");
- public class SlowReapable implements Reapable
- {
- public SlowReapable(Uid uid, int callDelay, int interruptDelay, boolean doCancel, boolean doRollback, boolean doComplete)
- {
- this.uid = uid;
- this.callDelay = callDelay;
- this.interruptDelay = interruptDelay;
- this.doCancel = doCancel;
- this.doRollback = doRollback;
- this.doComplete = doComplete;
- cancelTried = false;
- rollbackTried = false;
- running = true;
- }
+ // latch the reaper before it tests the queue again
- public boolean running()
- {
- return getRunning();
- }
+ triggerRendezvous("reaper1");
- public boolean preventCommit()
- {
- setRollbackTried();
- clearRunning();
- return doRollback;
- }
+ // the transactions queue should be
+ // UID0 CANCEL
+ // UID1 SCHEDULE_CANCEL
- public int cancel()
- {
- boolean interrupted = false;
+ assertEquals(2, reaper.numberOfTransactions());
+ assertEquals(2, reaper.numberOfTimeouts());
- setCancelTried();
+ // ensure we wait long enough for the cancel to time out
- // track the worker trying to do the cancel so we can
- // detect if it becomes a zombie
+ triggerWait(500);
- setCancelThread(Thread.currentThread());
+ // now let the reaper check the queue and interrupt the cancel for UID1
- if (callDelay > 0) {
- try {
- Thread.sleep(callDelay);
- } catch (InterruptedException e) {
- interrupted = true;
- }
- }
- if (interrupted && interruptDelay > 0) {
- try {
- Thread.sleep(interruptDelay);
- } catch (InterruptedException e) {
- }
- }
+ triggerRendezvous("reaper1");
- if (doCancel) {
- clearRunning();
- return ActionStatus.ABORTED;
- } else {
- return ActionStatus.RUNNING;
- }
- }
+ // latch the reaper at the reaper element check
- public Uid get_uid()
- {
- return uid;
- }
+ triggerRendezvous("reaper element");
- private Uid uid;
- private int callDelay; // in milliseconds
- private int interruptDelay; // in milliseconds
- private boolean doCancel;
- private boolean doRollback;
- private boolean doComplete;
- private boolean cancelTried;
- private boolean rollbackTried;
- private boolean running;
- private Thread cancelThread;
+ // check that we have dequeued reapable0
- public synchronized void setCancelTried()
- {
- cancelTried = true;
- }
- public synchronized boolean getCancelTried()
- {
- return cancelTried;
- }
- public synchronized void setCancelThread(Thread cancelThread)
- {
- this.cancelThread = cancelThread;
- }
- public synchronized Thread getCancelThread()
- {
- return cancelThread;
- }
- public synchronized void setRollbackTried()
- {
- rollbackTried = true;
- }
- public synchronized boolean getRollbackTried()
- {
- return rollbackTried;
- }
- public synchronized void clearRunning()
- {
- running = false;
- }
- public synchronized boolean getRunning()
- {
- return running;
- }
+ assertTrue(checkAndClearFlag(reapable0));
+
+ // unlatch the reaper so it can process the element
+
+ triggerRendezvous("reaper element");
+
+ // latch the reaper before it tests the queue again
+
+ triggerRendezvous("reaper1");
+
+ // unlatch the first reapable inside cancel
+
+ triggerRendezvous(uid0);
+
+ // latch the worker as it is about to process the queue again
+
+ triggerRendezvous("reaperworker1");
+
+ // the transactions queue should be
+ // UID1 SCHEDULE_CANCEL
+
+ assertEquals(1, reaper.numberOfTransactions());
+ assertEquals(1, reaper.numberOfTimeouts());
+
+ // let the worker clear and cancel the 2nd reapable
+
+ triggerRendezvous("reaperworker1");
+
+ // latch the worker before it reads the worker queue
+
+ triggerRendezvous("reaperworker1");
+
+ // the transactions queue should be empty
+
+ assertEquals(0, reaper.numberOfTransactions());
+ assertEquals(0, reaper.numberOfTimeouts());
+
+ // ensure that cancel was tried on reapable1 and that set rollback only was not tried on either
+ // we know cancel was tried on reapable0 because we got through the rendezvous
+
+ assertTrue(reapable1.getCancelTried());
+ assertTrue(!reapable0.getRollbackTried());
+ assertTrue(!reapable1.getRollbackTried());
+ assertTrue(checkAndClearFlag("interrupted"));
+
+ // STAGE II
+ // now use the next pair of reapables to check that a wedged reaperworker gets tuirned into a zombie and
+ // a new worker gets created to cancel the remaining reapables.
+ // insert reapables so they timeout at 1 second intervals then
+ // check progress of cancellations and rollbacks
+
+ reaper.insert(reapable2, 1);
+
+ reaper.insert(reapable3, 1);
+
+ // make sure they were all registered
+ // the transactions queue should be
+ // UID2 RUNNING
+ // UID3 RUNNING
+
+ assertEquals(2, reaper.numberOfTransactions());
+ assertEquals(2, reaper.numberOfTimeouts());
+
+ // wait long enough to ensure both reapables have timed out
+
+ triggerWait(1000);
+
+ // now let the reaper dequeue the first reapable, process it and queue it for the worker thread
+ // to deal with
+
+ triggerRendezvous("reaper1");
+
+ // latch the reaper at the reaper element check
+
+ triggerRendezvous("reaper element");
+
+ // check that we have dequeued reapable2
+
+ assertTrue(checkAndClearFlag(reapable2));
+
+ // unlatch the reaper so it can process the element
+
+ triggerRendezvous("reaper element");
+
+ // latch the reaper before it tests the queue again
+
+ triggerRendezvous("reaper1");
+
+ // the transactions queue should be
+ // UID3 RUNNING
+ // UID2 CANCEL
+
+ assertEquals(2, reaper.numberOfTransactions());
+ assertEquals(2, reaper.numberOfTimeouts());
+
+ // now let the worker dequeue the fourth reapable and proceed to call cancel
+
+ triggerRendezvous("reaperworker1");
+
+ // latch the third reapable inside cancel
+
+ triggerRendezvous(uid2);
+
+ // now let the reaper check the queue for the fourth reapable, dequeue it and add it to the
+ // worker queue
+
+ triggerRendezvous("reaper1");
+
+ // latch the reaper at the reaper element check
+
+ triggerRendezvous("reaper element");
+
+ // check that we have dequeued reapable3
+
+ assertTrue(checkAndClearFlag(reapable3));
+
+ // unlatch the reaper so it can process the element
+
+ triggerRendezvous("reaper element");
+
+ // latch the reaper before it tests the queue again
+
+ triggerRendezvous("reaper1");
+
+ // the transactions queue should be
+ // UID2 CANCEL
+ // UID3 SCHEDULE_CANCEL
+
+ assertEquals(2, reaper.numberOfTransactions());
+ assertEquals(2, reaper.numberOfTimeouts());
+
+ // ensure we wait long enough for the cancel to time out
+
+ triggerWait(500);
+
+ // now let the reaper check the queue and interrupt the cancel for UID3
+
+ triggerRendezvous("reaper1");
+
+ // latch the reaper at the reaper element check
+
+ triggerRendezvous("reaper element");
+
+ // check that we have dequeued reapable2
+
+ assertTrue(checkAndClearFlag(reapable2));
+
+ // unlatch the reaper so it can process the element
+
+ triggerRendezvous("reaper element");
+
+ // latch the reaper before it tests the queue again
+
+ triggerRendezvous("reaper1");
+
+ assertTrue(checkAndClearFlag("interrupted"));
+
+ // ensure we wait long enough for the cancel to time out
+
+ triggerWait(500);
+
+ // the transactions queue should be
+ // UID3 SCHEDULE_CANCEL
+ // UID2 CANCEL_INTERRUPTED
+
+ assertEquals(2, reaper.numberOfTransactions());
+ assertEquals(2, reaper.numberOfTimeouts());
+
+ // let the reaper check the queue and reschedule the fourth reapable
+
+ triggerRendezvous("reaper1");
+
+ // latch the reaper at the reaper element check
+
+ triggerRendezvous("reaper element");
+
+ // check that we have dequeued reapable3
+
+ assertTrue(checkAndClearFlag(reapable3));
+
+ // unlatch the reaper so it can process the element
+
+ triggerRendezvous("reaper element");
+
+ // latch the reaper before it tests the queue again
+
+ triggerRendezvous("reaper1");
+
+ // the transactions queue should be
+ // UID2 CANCEL_INTERRUPTED
+ // UID3 SCHEDULE_CANCEL
+
+ assertEquals(2, reaper.numberOfTransactions());
+ assertEquals(2, reaper.numberOfTimeouts());
+
+ // let the reaper check the queue and mark the reaper worker as a zombie
+
+ triggerRendezvous("reaper1");
+
+ // latch the reaper at the reaper element check
+
+ triggerRendezvous("reaper element");
+
+ // check that we have dequeued reapable2
+
+ assertTrue(checkAndClearFlag(reapable2));
+
+ // unlatch the reaper so it can process the element
+
+ triggerRendezvous("reaper element");
+
+ // latch the reaper before it tests the queue again
+
+ triggerRendezvous("reaper1");
+
+ // the reaper should have marked the thread as a zombie
+
+ assertTrue(checkAndClearFlag("zombied"));
+
+ // the transactions queue should be
+ // UID3 SCHEDULE_CANCEL
+
+ assertEquals(1, reaper.numberOfTransactions());
+ assertEquals(1, reaper.numberOfTimeouts());
+
+ // unlatch the third reapable inside cancel
+
+ triggerRendezvous(uid2);
+
+ // latch the new worker as it is about to process the queue again
+
+ triggerRendezvous("reaperworker1");
+
+ // let the worker clear and cancel the 2nd reapable
+
+ triggerRendezvous("reaperworker1");
+
+ // latch the worker before it reads the worker queue
+
+ triggerRendezvous("reaperworker1");
+
+ // the transactions queue should be empty
+
+ assertEquals(0, reaper.numberOfTransactions());
+ assertEquals(0, reaper.numberOfTimeouts());
+
+ // ensure that cancel was tried on reapable3 and that set rollback only was tried on reapable2
+ // and reapable3 we know cancel was tried on reapable2 because we got through the rendezvous
+
+ assertTrue(reapable3.getCancelTried());
+ assertTrue(reapable2.getRollbackTried());
+ assertTrue(reapable3.getRollbackTried());
}
}
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase3.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase3.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaCore/arjuna/tests/classes/com/hp/mwtests/ts/arjuna/reaper/ReaperTestCase3.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -20,228 +20,345 @@
*/
package com.hp.mwtests.ts.arjuna.reaper;
-import junit.framework.TestCase;
-import junit.framework.Test;
-import junit.framework.TestSuite;
import com.arjuna.ats.arjuna.coordinator.TransactionReaper;
import com.arjuna.ats.arjuna.coordinator.Reapable;
import com.arjuna.ats.arjuna.coordinator.ActionStatus;
import com.arjuna.ats.arjuna.common.Uid;
-import com.arjuna.ats.internal.arjuna.coordinator.ReaperElement;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-
-public class ReaperTestCase3 extends TestCase
+public class ReaperTestCase3 extends ReaperTestCaseControl
{
- public static Test suite()
+ public void testReaperWait() throws Exception
{
- return new TestSuite(ReaperTestCase3.class);
- }
+ TransactionReaper reaper = TransactionReaper.transactionReaper();
- public void testReaperWait () throws Exception
- {
- TransactionReaper.create(500);
- TransactionReaper reaper = TransactionReaper.transactionReaper();
+ // give the reaper worker time to start too
- // give the reaper worker time to start too
+ Thread.sleep(1000);
- Thread.sleep(1000);
+ // create test reapables some of which will not respond immediately to cancel requests
- // create slow reapables some of which will not respond immediately
- // to cancel requests and ensure that they get cancelled
- // and that the reaper does not get wedged
+ Uid uid0 = new Uid();
+ Uid uid1 = new Uid();
+ Uid uid2 = new Uid();
+ Uid uid3 = new Uid();
- SlowReapable reapable1 = new SlowReapable(new Uid(), 2000, 0, true, true);
- SlowReapable reapable2 = new SlowReapable(new Uid(), 0, 0, true, true);
- SlowReapable reapable3 = new SlowReapable(new Uid(), 100, 2000, false, true);
- SlowReapable reapable4 = new SlowReapable(new Uid(), 1000, 1000, false, false);
+ // reapable0 will return CANCELLED from cancel and will rendezvous inside the cancel call
+ // so we can delay it. prevent_commit should not get called so we don't care about the arguments
+ TestReapable reapable0 = new TestReapable(uid0, true, false, false, false);
+ // reapable1 will return CANCELLED from cancel and will not rendezvous inside the cancel call
+ // prevent_commit should not get called so we don't care about the arguments
+ TestReapable reapable1 = new TestReapable(uid1, true, false, false, false);
+ // reapable2 will return CANCELLED from cancel and will not rendezvous inside the cancel call
+ // prevent_commit should not get called so we don't care about the arguments
+ TestReapable reapable2 = new TestReapable(uid2, true, false, false, false);
+ // reapable3 will return CANCELLED from cancel and will not rendezvous inside the cancel call
+ // prevent_commit should not get called so we don't care about the arguments
+ TestReapable reapable3 = new TestReapable(uid3, true, false, false, false);
+ // enable a repeatable rendezvous before checking the reapable queue
+ enableRendezvous("reaper1", true);
+ // enable a repeatable rendezvous before processing a timed out reapable
+ // enableRendezvous("reaper2", true);
+ // enable a repeatable rendezvous before scheduling a reapable in the worker queue for cancellation
+ // enableRendezvous("reaper3", true);
+ // enable a repeatable rendezvous before rescheduling a reapable in the worker queue for cancellation
+ // enableRendezvous("reaper4", true);
+ // enable a repeatable rendezvous before interrupting a cancelled reapable
+ // enableRendezvous("reaper5", true);
+ // enable a repeatable rendezvous before marking a worker thread as a zombie
+ // enableRendezvous("reaper6", true);
+ // enable a repeatable rendezvous before marking a reapable as rollback only from the reaper thread
+ // enableRendezvous("reaper7", true);
+ // enable a repeatable rendezvous before checking the worker queue
+ enableRendezvous("reaperworker1", true);
+ // enable a repeatable rendezvous before marking a reapable as cancel
+ // enableRendezvous("reaperworker2", true);
+ // enable a repeatable rendezvous before calling cancel
+ // enableRendezvous("reaperworker3", true);
+ // enable a repeatable rendezvous before marking a reapable as rollback only from the worker thread
+ // enableRendezvous("reaperworker4", true);
- // insert reapables so they timeout at 1 second intervals then
- // check progress of cancellations and rollbacks
+ // enable a repeatable rendezvous for each of the test reapables which we have marked to
+ // perform a rendezvous
- assertTrue(reaper.insert(reapable1, 1));
+ // enableRendezvous(uid0, true);
- assertTrue(reaper.insert(reapable2, 2));
+ // insert reapables so they timeout at 1 second intervals then
+ // check progress of cancellations and rollbacks
- assertTrue(reaper.insert(reapable3, 3));
+ reaper.insert(reapable0, 1);
- assertTrue(reaper.insert(reapable4, 4));
+ reaper.insert(reapable1, 2);
- // make sure they were all registered
+ reaper.insert(reapable2, 3);
- assertEquals(4, reaper.numberOfTransactions());
- assertEquals(4, reaper.numberOfTimeouts());
+ reaper.insert(reapable3, 4);
- // force a termination but wait for the transactions to timeout
-
- TransactionReaper.terminate(true);
-
- assertEquals(0, reaper.numberOfTransactions());
-
- assertTrue(reapable1.getCancelTried());
- assertTrue(reapable2.getCancelTried());
- assertTrue(reapable3.getCancelTried());
- assertTrue(reapable4.getCancelTried());
+ // latch the reaper before it checks the queue
+
+ triggerRendezvous("reaper1");
+
+ // make sure they were all registered
+
+ assertEquals(4, reaper.numberOfTransactions());
+ assertEquals(4, reaper.numberOfTimeouts());
+
+ // ensure the first reapable is ready
+
+ triggerWait(1000);
+
+ // let the reaper process the first reapable then latch it again before it checks the queue
+
+ triggerRendezvous("reaper1");
+
+ triggerRendezvous("reaper1");
+
+ // latch the worker before it checks the worker queue
+
+ triggerRendezvous("reaperworker1");
+
+ // let the worker process the first reapable then latch it again before it checks the queue
+
+ triggerRendezvous("reaperworker1");
+
+ triggerRendezvous("reaperworker1");
+
+ // force a termination waiting for the normal timeout periods
+ // byteman rules will ensure that the reaper and reaperworker rendezvous get deleted
+ // under this call
+
+ TransactionReaper.terminate(true);
+
+ assertEquals(0, reaper.numberOfTransactions());
+
+ assertTrue(reapable0.getCancelTried());
+ assertTrue(reapable1.getCancelTried());
+ assertTrue(reapable2.getCancelTried());
+ assertTrue(reapable3.getCancelTried());
}
-
- public void testReaperForce () throws Exception
+
+ public void testReaperForce() throws Exception
{
- TransactionReaper.create(5000);
TransactionReaper reaper = TransactionReaper.transactionReaper();
// give the reaper worker time to start too
Thread.sleep(1000);
- // create slow reapables some of which will not respond immediately
- // to cancel requests and ensure that they get cancelled
- // and that the reaper does not get wedged
+ // create test reapables some of which will not respond immediately to cancel requests
- SlowReapable reapable1 = new SlowReapable(new Uid(), 2000, 0, true, true);
- SlowReapable reapable2 = new SlowReapable(new Uid(), 0, 0, true, true);
- SlowReapable reapable3 = new SlowReapable(new Uid(), 100, 2000, false, true);
- SlowReapable reapable4 = new SlowReapable(new Uid(), 1000, 1000, false, false);
+ Uid uid0 = new Uid();
+ Uid uid1 = new Uid();
+ Uid uid2 = new Uid();
+ Uid uid3 = new Uid();
- // insert reapables so they timeout at 1 second intervals then
- // check progress of cancellations and rollbacks
+ // reapable0 will return CANCELLED from cancel and will rendezvous inside the cancel call
+ // so we can delay it. prevent_commit should not get called so we don't care about the arguments
+ TestReapable reapable0 = new TestReapable(uid0, true, false, false, false);
+ // reapable1 will return CANCELLED from cancel and will not rendezvous inside the cancel call
+ // prevent_commit should not get called so we don't care about the arguments
+ TestReapable reapable1 = new TestReapable(uid1, true, false, false, false);
+ // reapable2 will return CANCELLED from cancel and will not rendezvous inside the cancel call
+ // prevent_commit should not get called so we don't care about the arguments
+ TestReapable reapable2 = new TestReapable(uid2, true, false, false, false);
+ // reapable3 will return CANCELLED from cancel and will not rendezvous inside the cancel call
+ // prevent_commit should not get called so we don't care about the arguments
+ TestReapable reapable3 = new TestReapable(uid3, true, false, false, false);
+ // enable a repeatable rendezvous before checking the reapable queue
+ enableRendezvous("reaper1", true);
+ // enable a repeatable rendezvous before processing a timed out reapable
+ // enableRendezvous("reaper2", true);
+ // enable a repeatable rendezvous before scheduling a reapable in the worker queue for cancellation
+ // enableRendezvous("reaper3", true);
+ // enable a repeatable rendezvous before rescheduling a reapable in the worker queue for cancellation
+ // enableRendezvous("reaper4", true);
+ // enable a repeatable rendezvous before interrupting a cancelled reapable
+ // enableRendezvous("reaper5", true);
+ // enable a repeatable rendezvous before marking a worker thread as a zombie
+ // enableRendezvous("reaper6", true);
+ // enable a repeatable rendezvous before marking a reapable as rollback only from the reaper thread
+ // enableRendezvous("reaper7", true);
+ // enable a repeatable rendezvous before checking the worker queue
+ enableRendezvous("reaperworker1", true);
+ // enable a repeatable rendezvous before marking a reapable as cancel
+ // enableRendezvous("reaperworker2", true);
+ // enable a repeatable rendezvous before calling cancel
+ // enableRendezvous("reaperworker3", true);
+ // enable a repeatable rendezvous before marking a reapable as rollback only from the worker thread
+ // enableRendezvous("reaperworker4", true);
- assertTrue(reaper.insert(reapable1, 1));
+ // enable a repeatable rendezvous for each of the test reapables which we have marked to
+ // perform a rendezvous
- assertTrue(reaper.insert(reapable2, 2));
+ // enableRendezvous(uid0, true);
- assertTrue(reaper.insert(reapable3, 3));
+ reaper.insert(reapable0, 1);
- assertTrue(reaper.insert(reapable4, 4));
+ reaper.insert(reapable1, 2);
+ reaper.insert(reapable2, 3);
+
+ reaper.insert(reapable3, 4);
+
+ // latch the reaper before it checks the queue
+
+ triggerRendezvous("reaper1");
+
// make sure they were all registered
assertEquals(4, reaper.numberOfTransactions());
assertEquals(4, reaper.numberOfTimeouts());
+ // ensure the first reapable is ready
+
+ triggerWait(1000);
+
+ // let the reaper process the first reapable then latch it again before it checks the queue
+
+ triggerRendezvous("reaper1");
+
+ triggerRendezvous("reaper1");
+
+ // latch the worker before it checks the worker queue
+
+ triggerRendezvous("reaperworker1");
+
+ // let the worker process the first reapable then latch it again before it checks the queue
+
+ triggerRendezvous("reaperworker1");
+
+ triggerRendezvous("reaperworker1");
+
// force a termination and don't wait for the normal timeout periods
-
+ // byteman rules will ensure that the reaper and reaperworker rendezvous gte deleted
+ // under this call
+
TransactionReaper.terminate(false);
-
+
assertEquals(0, reaper.numberOfTransactions());
-
+
+ assertTrue(reapable0.getCancelTried());
assertTrue(reapable1.getCancelTried());
assertTrue(reapable2.getCancelTried());
assertTrue(reapable3.getCancelTried());
- assertTrue(reapable4.getCancelTried());
-
+
/*
- * Since we've (hopefully) just run two tests with new reapers in the same VM
- * we've also shown that it's possible to start/terminate/start again!
- */
+ * Since we've (hopefully) just run two tests with new reapers in the same VM
+ * we've also shown that it's possible to start/terminate/start again!
+ */
}
public class SlowReapable implements Reapable
{
- public SlowReapable(Uid uid, int callDelay, int interruptDelay, boolean doCancel, boolean doRollback)
- {
- this.uid = uid;
+ public SlowReapable(Uid uid, int callDelay, int interruptDelay, boolean doCancel, boolean doRollback)
+ {
+ this.uid = uid;
this.callDelay = callDelay;
this.interruptDelay = interruptDelay;
this.doCancel = doCancel;
this.doRollback = doRollback;
- cancelTried = false;
- rollbackTried = false;
- running = true;
+ cancelTried = false;
+ rollbackTried = false;
+ running = true;
}
- public boolean running()
- {
- return getRunning();
- }
+ public boolean running()
+ {
+ return getRunning();
+ }
- public boolean preventCommit()
- {
- setRollbackTried();
- clearRunning();
- return doRollback;
- }
+ public boolean preventCommit()
+ {
+ setRollbackTried();
+ clearRunning();
+ return doRollback;
+ }
- public int cancel()
- {
- boolean interrupted = false;
+ public int cancel()
+ {
+ boolean interrupted = false;
- setCancelTried();
+ setCancelTried();
- // track the worker trying to do the cancel so we can
- // detect if it becomes a zombie
+ // track the worker trying to do the cancel so we can
+ // detect if it becomes a zombie
- setCancelThread(Thread.currentThread());
+ setCancelThread(Thread.currentThread());
- if (callDelay > 0) {
- try {
- Thread.sleep(callDelay);
- } catch (InterruptedException e) {
- interrupted = true;
- }
- }
- if (interrupted && interruptDelay > 0) {
- try {
- Thread.sleep(interruptDelay);
- } catch (InterruptedException e) {
- }
- }
+ if (callDelay > 0) {
+ try {
+ Thread.sleep(callDelay);
+ } catch (InterruptedException e) {
+ interrupted = true;
+ }
+ }
+ if (interrupted && interruptDelay > 0) {
+ try {
+ Thread.sleep(interruptDelay);
+ } catch (InterruptedException e) {
+ }
+ }
- if (doCancel) {
- clearRunning();
- return ActionStatus.ABORTED;
- } else {
- return ActionStatus.RUNNING;
- }
- }
+ if (doCancel) {
+ clearRunning();
+ return ActionStatus.ABORTED;
+ } else {
+ return ActionStatus.RUNNING;
+ }
+ }
- public Uid get_uid()
- {
- return uid;
- }
+ public Uid get_uid()
+ {
+ return uid;
+ }
- private Uid uid;
+ private Uid uid;
private int callDelay; // in milliseconds
private int interruptDelay; // in milliseconds
private boolean doCancel;
private boolean doRollback;
- private boolean cancelTried;
- private boolean rollbackTried;
- private boolean running;
- private Thread cancelThread;
+ private boolean cancelTried;
+ private boolean rollbackTried;
+ private boolean running;
+ private Thread cancelThread;
- public synchronized void setCancelTried()
- {
- cancelTried = true;
- }
- public synchronized boolean getCancelTried()
- {
- return cancelTried;
- }
- public synchronized void setCancelThread(Thread cancelThread)
- {
- this.cancelThread = cancelThread;
- }
- public synchronized Thread getCancelThread()
- {
- return cancelThread;
- }
- public synchronized void setRollbackTried()
- {
- rollbackTried = true;
- }
- public synchronized boolean getRollbackTried()
- {
- return rollbackTried;
- }
- public synchronized void clearRunning()
- {
- running = false;
- }
- public synchronized boolean getRunning()
- {
- return running;
- }
+ public synchronized void setCancelTried()
+ {
+ cancelTried = true;
+ }
+
+ public synchronized boolean getCancelTried()
+ {
+ return cancelTried;
+ }
+
+ public synchronized void setCancelThread(Thread cancelThread)
+ {
+ this.cancelThread = cancelThread;
+ }
+
+ public synchronized Thread getCancelThread()
+ {
+ return cancelThread;
+ }
+
+ public synchronized void setRollbackTried()
+ {
+ rollbackTried = true;
+ }
+
+ public synchronized boolean getRollbackTried()
+ {
+ return rollbackTried;
+ }
+
+ public synchronized void clearRunning()
+ {
+ running = false;
+ }
+
+ public synchronized boolean getRunning()
+ {
+ return running;
+ }
}
}
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTA/jta/classes/com/arjuna/ats/internal/jta/transaction/arjunacore/subordinate/SubordinateAtomicAction.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTA/jta/classes/com/arjuna/ats/internal/jta/transaction/arjunacore/subordinate/SubordinateAtomicAction.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTA/jta/classes/com/arjuna/ats/internal/jta/transaction/arjunacore/subordinate/SubordinateAtomicAction.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -61,7 +61,7 @@
// if it has a non-negative timeout, add it to the reaper.
if (timeout > AtomicAction.NO_TIMEOUT)
- TransactionReaper.transactionReaper(true).insert(this, timeout);
+ TransactionReaper.transactionReaper().insert(this, timeout);
}
/**
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jtax/classes/com/arjuna/ats/internal/jta/transaction/jts/subordinate/jca/SubordinateAtomicTransaction.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jtax/classes/com/arjuna/ats/internal/jta/transaction/jts/subordinate/jca/SubordinateAtomicTransaction.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jtax/classes/com/arjuna/ats/internal/jta/transaction/jts/subordinate/jca/SubordinateAtomicTransaction.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -54,7 +54,7 @@
if (timeout > 0)
{
- TransactionReaper reaper = TransactionReaper.transactionReaper(true);
+ TransactionReaper reaper = TransactionReaper.transactionReaper();
reaper.insert(super.getControlWrapper(), timeout);
}
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/interposition/ServerFactory.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/interposition/ServerFactory.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/interposition/ServerFactory.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -247,9 +247,6 @@
{
TransactionReaper reaper = TransactionReaper.transactionReaper();
- if (reaper == null)
- reaper = TransactionReaper.create();
-
reaper.insert(new ServerControlWrapper((ControlImple) tranControl), time_out);
}
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/orbspecific/TransactionFactoryImple.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/orbspecific/TransactionFactoryImple.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/orbspecific/TransactionFactoryImple.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -177,9 +177,6 @@
TransactionReaper reaper = TransactionReaper.transactionReaper();
- if (reaper == null)
- reaper = TransactionReaper.create();
-
reaper.insert(new ControlWrapper((ControlImple) tranControl), theTimeout);
}
@@ -659,7 +656,7 @@
TransactionReaper reaper = TransactionReaper.transactionReaper();
- if (reaper == null)
+ if (reaper.checkingPeriod() == Long.MAX_VALUE)
info.reaperTimeout = 0;
else
info.reaperTimeout = (int) reaper.checkingPeriod();
@@ -716,16 +713,8 @@
TransactionReaper reaper = TransactionReaper.transactionReaper();
- /*
- * If the reaper has not been created yet, then all
- * transactions so far must have 0 timeout.
- */
+ info.timeout = reaper.getTimeout(ctx);
- if (reaper == null)
- info.timeout = 0;
- else
- info.timeout = (int) reaper.getTimeout(ctx);
-
info.numberOfThreads = ctx.getImplHandle().activeThreads();
return info;
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/orbspecific/coordinator/ArjunaTransactionImple.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/orbspecific/coordinator/ArjunaTransactionImple.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/internal/jts/orbspecific/coordinator/ArjunaTransactionImple.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -2138,21 +2138,14 @@
* versions, there's a configurable option.
*/
- if (TransactionReaper.transactionReaper() != null)
- {
- if (_propagateRemainingTimeout)
- {
- long timeInMills = TransactionReaper.transactionReaper().getRemainingTimeoutMills(control);
- context.timeout = (int)(timeInMills/1000L);
- }
- else
- {
- context.timeout = TransactionReaper.transactionReaper().getTimeout(control);
- }
+ if (_propagateRemainingTimeout)
+ {
+ long timeInMills = TransactionReaper.transactionReaper().getRemainingTimeoutMills(control);
+ context.timeout = (int)(timeInMills/1000L);
}
else
{
- context.timeout = 0;
+ context.timeout = TransactionReaper.transactionReaper().getTimeout(control);
}
}
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/jts/OTSManager.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/jts/OTSManager.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/ArjunaJTS/jts/classes/com/arjuna/ats/jts/OTSManager.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -129,53 +129,47 @@
/*
* Just in case control is a top-level transaction, and has
* been registered with the reaper, we need to get it removed.
- *
- * Don't bother if the reaper has not been created.
*/
-
- if (TransactionReaper.transactionReaper() != null)
+ Coordinator coord = null;
+
+ try
+ {
+ coord = control.get_coordinator();
+ }
+ catch (Exception e)
+ {
+ coord = null; // nothing else we can do!
+ }
+
+ if (coord != null)
+ {
+ try
{
- Coordinator coord = null;
-
- try
+ if (coord.is_top_level_transaction())
{
- coord = control.get_coordinator();
- }
- catch (Exception e)
- {
- coord = null; // nothing else we can do!
- }
+ /*
+ * Transaction is local, but was registered as
+ * a Control. If this is a performance hit then
+ * add explicit add/removes for local instances.
+ */
- if (coord != null)
- {
- try
+ if (jtsLogger.logger.isDebugEnabled())
{
- if (coord.is_top_level_transaction())
- {
- /*
- * Transaction is local, but was registered as
- * a Control. If this is a performance hit then
- * add explicit add/removes for local instances.
- */
-
- if (jtsLogger.logger.isDebugEnabled())
- {
- jtsLogger.logger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
- com.arjuna.ats.jts.logging.FacilityCode.FAC_OTS, "OTS::destroyControl - removing control from reaper.");
- }
-
- // wrap the control so it gets compared against reaper list entries using the correct test
- PseudoControlWrapper wrapper = new PseudoControlWrapper(control);
- TransactionReaper.transactionReaper().remove(wrapper);
- }
+ jtsLogger.logger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
+ com.arjuna.ats.jts.logging.FacilityCode.FAC_OTS, "OTS::destroyControl - removing control from reaper.");
}
- catch (Exception e)
- {
- }
- coord = null;
+ // wrap the control so it gets compared against reaper list entries using the correct test
+ PseudoControlWrapper wrapper = new PseudoControlWrapper(control);
+ TransactionReaper.transactionReaper().remove(wrapper);
}
}
+ catch (Exception e)
+ {
+ }
+
+ coord = null;
+ }
/*
* Watch out for conflicts with multiple threads deleting
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/atsintegration/classes/com/arjuna/ats/jbossatx/jta/TransactionManagerService.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/atsintegration/classes/com/arjuna/ats/jbossatx/jta/TransactionManagerService.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/atsintegration/classes/com/arjuna/ats/jbossatx/jta/TransactionManagerService.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -193,7 +193,7 @@
PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "arjuna").addManagementPlugin(new PropertyServiceJMXPlugin());
// Associate transaction reaper with our context classloader.
- TransactionReaper.create() ;
+ TransactionReaper.transactionReaper() ;
/** Register propagation context manager **/
try
Modified: labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/atsintegration/classes/com/arjuna/ats/jbossatx/jts/TransactionManagerService.java
===================================================================
--- labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/atsintegration/classes/com/arjuna/ats/jbossatx/jts/TransactionManagerService.java 2010-05-13 11:36:42 UTC (rev 32860)
+++ labs/jbosstm/branches/JBOSSTS_4_6_1_GA_CP/atsintegration/classes/com/arjuna/ats/jbossatx/jts/TransactionManagerService.java 2010-05-13 11:44:31 UTC (rev 32861)
@@ -200,7 +200,7 @@
PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "arjuna").addManagementPlugin(new PropertyServiceJMXPlugin());
// Associate transaction reaper with our context classloader.
- TransactionReaper.create() ;
+ TransactionReaper.transactionReaper() ;
/** Register propagation context manager **/
try
More information about the jboss-svn-commits
mailing list