use of com.arjuna.ats.internal.arjuna.coordinator.ReaperElement in project narayana by jbosstm.
the class TransactionReaper method 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.
*/
public final void check() {
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("TransactionReaper::check ()");
}
do {
final ReaperElement reaperElement;
synchronized (this) {
final long now = System.currentTimeMillis();
final long next = nextDynamicCheckTime.get();
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("TransactionReaper::check - comparing " + Long.toString(next));
}
if (now < next) {
break;
}
reaperElement = _reaperElements.getFirst();
// then use compareAndSet? Although something will need to check before sleeping anyhow...
if (reaperElement == null) {
nextDynamicCheckTime.set(Long.MAX_VALUE);
return;
} else {
final long nextTimeout = reaperElement.getAbsoluteTimeout();
if (nextTimeout > now) {
nextDynamicCheckTime.set(nextTimeout);
// nothing to do yet.
return;
}
}
}
tsLogger.i18NLogger.warn_coordinator_TransactionReaper_18(reaperElement._control.get_uid(), reaperElement.statusName());
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
reaperElement._status = ReaperElement.SCHEDULE_CANCEL;
reinsertElement(reaperElement, _cancelWaitPeriod);
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("Reaper scheduling TX for cancellation " + reaperElement._control.get_uid());
}
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.
reinsertElement(reaperElement, _cancelWaitPeriod);
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("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
StringBuilder sb = new StringBuilder();
for (StackTraceElement element : reaperElement._worker.getStackTrace()) {
sb.append(element.toString());
sb.append("\n");
}
tsLogger.i18NLogger.wedged_reaperelement(sb.toString());
reaperElement._status = ReaperElement.CANCEL_INTERRUPTED;
reaperElement._worker.interrupt();
reinsertElement(reaperElement, _cancelFailWaitPeriod);
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("TransactionReaper::check interrupting cancel in progress for " + 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.
reaperElement._status = ReaperElement.ZOMBIE;
synchronized (this) {
_zombieCount++;
if (tsLogger.logger.isTraceEnabled()) {
tsLogger.logger.trace("Reaper " + Thread.currentThread() + " got a zombie " + reaperElement._worker + " (zombie count now " + _zombieCount + ") cancelling " + reaperElement._control.get_uid());
}
if (_zombieCount == _zombieMax) {
// log zombie overflow error call()
tsLogger.i18NLogger.error_coordinator_TransactionReaper_5(Integer.toString(_zombieCount));
}
}
_reaperWorkerThread = new ReaperWorkerThread(TransactionReaper._theReaper);
_reaperWorkerThread.setDaemon(true);
_reaperWorkerThread.start();
// log a failed cancel()
tsLogger.i18NLogger.warn_coordinator_TransactionReaper_6(reaperElement._worker.toString(), reaperElement._control.get_uid());
// 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
removeElementReaper(reaperElement);
try {
if (reaperElement._control.preventCommit()) {
// log a successful preventCommit()
tsLogger.i18NLogger.warn_coordinator_TransactionReaper_10(reaperElement._control.get_uid());
notifyListeners(reaperElement._control, false);
} else {
// log a failed preventCommit()
tsLogger.i18NLogger.warn_coordinator_TransactionReaper_11(reaperElement._control.get_uid());
}
} catch (Exception e1) {
// log an exception under preventCommit()
tsLogger.i18NLogger.warn_coordinator_TransactionReaper_12(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
removeElementReaper(reaperElement);
}
break;
}
}
} while (true);
}
use of com.arjuna.ats.internal.arjuna.coordinator.ReaperElement in project narayana by jbosstm.
the class ReaperTestCase method testReaper.
@Test
public void testReaper() throws Exception {
TransactionReaper reaper = TransactionReaper.transactionReaper();
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);
// 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);
// test insertion of timeout=0 is a nullop
reaper.insert(reapable, 0);
assertEquals(0, reaper.numberOfTransactions());
assertEquals(0, reaper.numberOfTimeouts());
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 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());
// 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
// seconds
reaper.insert(reapable, 1);
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());
}
Aggregations