/* * JBoss, Home of Professional Open Source. * Copyright 2014, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY 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 along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * * @author Red Hat, Inc */ package com.hp.mwtests.ts.jta.recovery; import com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule; import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper; import org.jboss.byteman.contrib.bmunit.BMScript; import org.jboss.byteman.contrib.bmunit.BMUnitRunner; import org.junit.Test; import org.junit.runner.RunWith; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import java.lang.reflect.Field; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.*; @RunWith(BMUnitRunner.class) public class XARecoveryModuleHelpersUnitTest { /** * Test that recovery helpers that are not in use can be removed after pass 1 * @throws Exception */ @Test public void testTimelyXAResourceRecoveryHelperRemoval1() throws Exception { testTimelyXAResourceRecoveryHelperRemoval(false); } /** * Test that recovery helpers that are in use can only be removed after pass 2 completes * @throws Exception */ @Test public void testTimelyXAResourceRecoveryHelperRemoval2() throws Exception { testTimelyXAResourceRecoveryHelperRemoval(true); } /** * Test that recovery helpers can be added and removed during recovery pass 2 * (and not have to wait until pass 2 is complete) * @throws Exception */ @BMScript("recovery-helper") @Test public void testTimelyXAResourceRecoveryHelperRemoval3() throws Exception { final XARecoveryModule xaRecoveryModule = new XARecoveryModule(); ExecutorService pool = Executors.newFixedThreadPool(1); new Thread() { public void run() { xaRecoveryModule.periodicWorkFirstPass(); try { Thread.sleep(100); } catch (InterruptedException e) { } xaRecoveryModule.periodicWorkSecondPass(); } }.start(); Future<String> future = pool.submit(new Callable<String>() { @Override public String call() throws Exception { final XAResourceRecoveryHelper xaResourceRecoveryHelper = new XAResourceRecoveryHelper() { @Override public boolean initialise(String p) throws Exception { return true; } @Override public XAResource[] getXAResources() throws Exception { return new XAResource[0]; } }; return addHelper(xaRecoveryModule, xaResourceRecoveryHelper, 3); } }); String errMsg = future.get(); assertNull(errMsg, errMsg); } private String addHelper(XARecoveryModule xaRecoveryModule, XAResourceRecoveryHelper xaResourceRecoveryHelper, int expectedState) { if (getScanState(xaRecoveryModule) != expectedState) return "Wrong state for addHelper in pass 2a"; xaRecoveryModule.addXAResourceRecoveryHelper(xaResourceRecoveryHelper); if (getScanState(xaRecoveryModule) != expectedState) return "Wrong state for addHelper in pass 2b"; xaRecoveryModule.removeXAResourceRecoveryHelper(xaResourceRecoveryHelper); if (getScanState(xaRecoveryModule) != expectedState) return "Wrong state for addHelper in pass 2c"; return null; } private void testTimelyXAResourceRecoveryHelperRemoval(final boolean somethingToRecover) throws Exception { final XARecoveryModule xaRecoveryModule = new XARecoveryModule(); final long xAResourcesSleepMillis = 2000; final long betweenPassesSleepMillis = 100; final long millis = System.currentTimeMillis(); final SimpleResource testXAResource = new SimpleResource() { @Override public Xid[] recover(int i) throws XAException { if (!somethingToRecover) return new Xid[0]; return new Xid[] {new Xid() { @Override public int getFormatId() { return 0; } @Override public byte[] getGlobalTransactionId() { return new byte[0]; } @Override public byte[] getBranchQualifier() { return new byte[0]; } }}; } }; final XAResourceRecoveryHelper xaResourceRecoveryHelper = new XAResourceRecoveryHelper() { @Override public boolean initialise(String p) throws Exception { return true; } @Override public XAResource[] getXAResources() throws Exception { System.out.printf("getXAResources sleep (%d)%n",System.currentTimeMillis() - millis); Thread.sleep(xAResourcesSleepMillis); System.out.printf("getXAResources return (%d)%n",System.currentTimeMillis() - millis); return new XAResource[] {testXAResource}; } }; final XAResourceRecoveryHelper xaResourceRecoveryHelper2 = new XAResourceRecoveryHelper() { @Override public boolean initialise(String p) throws Exception { return true; } @Override public XAResource[] getXAResources() throws Exception { return new XAResource[0]; } }; /* * Remove helpers in the background whilst recovery is running to check that they are removed when * the scanner is in the correct state. * The sleep time param is tuned to ensure that the add/remove recovery helper operation occurs * during pass 1. */ XAHelperRemover remover = new XAHelperRemover( xaResourceRecoveryHelper, xaRecoveryModule, xAResourcesSleepMillis / 2, false); XAHelperRemover remover2 = new XAHelperRemover( xaResourceRecoveryHelper2, xaRecoveryModule, xAResourcesSleepMillis / 2, true); xaRecoveryModule.addXAResourceRecoveryHelper(xaResourceRecoveryHelper); remover.start(); remover2.start(); System.out.printf("Before pass 1 (%d)%n", System.currentTimeMillis() - millis); xaRecoveryModule.periodicWorkFirstPass(); System.out.printf("Finished pass 1 (%d)%n", System.currentTimeMillis() - millis); Thread.sleep(betweenPassesSleepMillis); System.out.printf("Starting pass 2 (%d)%n", System.currentTimeMillis() - millis); xaRecoveryModule.periodicWorkSecondPass(); System.out.printf("Finished pass 2 (%d)%n", System.currentTimeMillis() - millis); // wait for helper removal threads to finish try { remover.join(); remover2.join(); } catch (InterruptedException e) { fail("Test was interrupted whilst waiting for xa resource helper threads: " + e.getMessage()); } if (somethingToRecover) { assertEquals("helper removed in wrong state", 0, remover.getRemoveState()); } else { // See https://issues.jboss.org/browse/JBTM-2717 assertEquals("helper removed in wrong state", 2, remover.getRemoveState()); } // the unused helper should have been removed after pass 1 assertEquals("helper2 removed in wrong state", 2, remover2.getRemoveState()); } private int getScanState(Object instance) { try { Field scanState = XARecoveryModule.class.getDeclaredField("scanState"); scanState.setAccessible(true); AtomicInteger status = (AtomicInteger) scanState.get(instance); return status.get(); } catch (Exception e) { System.out.printf("getScanState error %s%n", e.getMessage()); return -1; } } private class XAHelperRemover extends Thread { private XAResourceRecoveryHelper helper; private XARecoveryModule xaRecoveryModule; private long xAResourcesSleepMillis; private int removeState = -1; private boolean add; private int getRemoveState() { return removeState; } private XAHelperRemover(XAResourceRecoveryHelper helper, XARecoveryModule xaRecoveryModule, long xAResourcesSleepMillis, boolean add) { this.helper = helper; this.xaRecoveryModule = xaRecoveryModule; this.xAResourcesSleepMillis = xAResourcesSleepMillis; this.add = add; } public void run() { long millis = System.currentTimeMillis(); try { Thread.sleep(xAResourcesSleepMillis); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("removing helper (%d)%n", System.currentTimeMillis() - millis); if (add) xaRecoveryModule.addXAResourceRecoveryHelper(helper); xaRecoveryModule.removeXAResourceRecoveryHelper(helper); removeState = getScanState(xaRecoveryModule); System.out.printf("remove helper took %d millis%n", System.currentTimeMillis() - millis); } } }