/*
* JBoss, Home of Professional Open Source
* Copyright 2007, 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) 2007,
* @author JBoss Inc.
*/
package com.hp.mwtests.ts.arjuna.reaper;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.jboss.byteman.contrib.bmunit.BMScript;
import org.jboss.byteman.contrib.bmunit.BMUnitRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.coordinator.TransactionReaper;
/**
* 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 Andrew Dinn (adinn@redhat.com), 2007-07-09
*/
@RunWith(BMUnitRunner.class)
@BMScript("reaper")
public class ReaperTestCase2 extends ReaperTestCaseControl
{
@Test
public void testReaper() throws Exception
{
TransactionReaper reaper = TransactionReaper.transactionReaper();
// 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
// the rendezvous for the reapables are keyed by the reapable's uid
Uid uid0 = new Uid();
Uid uid1 = new Uid();
Uid uid2 = new Uid();
Uid uid3 = new Uid();
// 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);
// 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);
// enable a repeatable rendezvous for each of the test reapables which we have marked to
// perform a rendezvous
enableRendezvous(uid0, true);
enableRendezvous(uid2, true);
// 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
reaper.insert(reapable0, 1);
reaper.insert(reapable1, 1);
//assertTrue(reaper.insert(reapable2, 1));
//assertTrue(reaper.insert(reapable3, 1));
// latch the reaper before it tries to process the queue
triggerRendezvous("reaper1");
// make sure they were all registered
// the transactions queue should be
// UID0 RUNNING
// UID1 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 reapable0
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");
// latch the reaperworker before it tries to dequeue from the worker queue
triggerRendezvous("reaperworker1");
// the transactions queue should be
// UID1 RUNNING
// UID0 CANCEL
assertEquals(2, reaper.numberOfTransactions());
assertEquals(2, reaper.numberOfTimeouts());
// now let the worker dequeue a reapable and proceed to call cancel
triggerRendezvous("reaperworker1");
// latch the first reapable inside cancel
triggerRendezvous(uid0);
// now let the reaper check the queue for the second 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 reapable1
assertTrue(checkAndClearFlag(reapable1));
// 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
// UID0 CANCEL
// UID1 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 UID1
triggerRendezvous("reaper1");
// latch the reaper at the reaper element check
triggerRendezvous("reaper element");
// check that we have dequeued reapable0
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());
}
}