Search in sources :

Example 6 with ReaperElement

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);
}
Also used : ReaperWorkerThread(com.arjuna.ats.internal.arjuna.coordinator.ReaperWorkerThread) ReaperElement(com.arjuna.ats.internal.arjuna.coordinator.ReaperElement)

Example 7 with ReaperElement

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());
}
Also used : Uid(com.arjuna.ats.arjuna.common.Uid) ReaperElement(com.arjuna.ats.internal.arjuna.coordinator.ReaperElement) TreeSet(java.util.TreeSet) TransactionReaper(com.arjuna.ats.arjuna.coordinator.TransactionReaper) SortedSet(java.util.SortedSet) Reapable(com.arjuna.ats.arjuna.coordinator.Reapable) Test(org.junit.Test)

Aggregations

ReaperElement (com.arjuna.ats.internal.arjuna.coordinator.ReaperElement)7 ReaperWorkerThread (com.arjuna.ats.internal.arjuna.coordinator.ReaperWorkerThread)2 Uid (com.arjuna.ats.arjuna.common.Uid)1 Reapable (com.arjuna.ats.arjuna.coordinator.Reapable)1 TransactionReaper (com.arjuna.ats.arjuna.coordinator.TransactionReaper)1 SortedSet (java.util.SortedSet)1 TreeSet (java.util.TreeSet)1 Test (org.junit.Test)1