/* * Copyright 2005-2014 Red Hat, Inc. * Red Hat licenses this file to you under the Apache License, version * 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.apache.activemq.artemis.tests.extras.jms.ra; import javax.jms.Message; import javax.resource.ResourceException; import javax.resource.spi.LocalTransactionException; import javax.resource.spi.UnavailableException; import javax.resource.spi.endpoint.MessageEndpoint; import javax.resource.spi.endpoint.MessageEndpointFactory; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import javax.transaction.xa.XAResource; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Method; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import com.arjuna.ats.arjuna.coordinator.TransactionReaper; import com.arjuna.ats.arjuna.coordinator.TxControl; import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.client.ClientConsumer; import org.apache.activemq.artemis.api.core.client.ClientMessage; import org.apache.activemq.artemis.api.core.client.ClientProducer; import org.apache.activemq.artemis.api.core.client.ClientSession; import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; import org.apache.activemq.artemis.api.core.client.ServerLocator; import org.apache.activemq.artemis.core.server.ServerConsumer; import org.apache.activemq.artemis.core.server.ServerSession; import org.apache.activemq.artemis.core.settings.impl.AddressSettings; import org.apache.activemq.artemis.ra.ActiveMQResourceAdapter; import org.apache.activemq.artemis.ra.inflow.ActiveMQActivation; import org.apache.activemq.artemis.ra.inflow.ActiveMQActivationSpec; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.tests.integration.ra.ActiveMQRATestBase; import org.apache.activemq.artemis.utils.RandomUtil; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; /** * Simulates several messages being received over multiple instances with reconnects during the process. */ public class MDBMultipleHandlersServerDisconnectTest extends ActiveMQRATestBase { final ConcurrentHashMap<Integer, AtomicInteger> mapCounter = new ConcurrentHashMap<>(); volatile ActiveMQResourceAdapter resourceAdapter; ServerLocator nettyLocator; // This thread will keep bugging the handlers. // if they behave well with XA, the test pass! final AtomicBoolean running = new AtomicBoolean(true); private volatile boolean playTXTimeouts = true; private volatile boolean playServerClosingSession = true; private volatile boolean playServerClosingConsumer = true; @Override @Before public void setUp() throws Exception { nettyLocator = createNettyNonHALocator(); nettyLocator.setRetryInterval(10); nettyLocator.setReconnectAttempts(-1); mapCounter.clear(); resourceAdapter = null; super.setUp(); createQueue(true, "outQueue"); DummyTMLocator.startTM(); running.set(true); } @Override @After public void tearDown() throws Exception { DummyTMLocator.stopTM(); super.tearDown(); } @Override protected boolean usePersistence() { return true; } @Override public boolean useSecurity() { return false; } @Test public void testReconnectMDBNoMessageLoss() throws Exception { AddressSettings settings = new AddressSettings(); settings.setRedeliveryDelay(100); settings.setMaxDeliveryAttempts(-1); server.getAddressSettingsRepository().clear(); server.getAddressSettingsRepository().addMatch("#", settings); ActiveMQResourceAdapter qResourceAdapter = newResourceAdapter(); resourceAdapter = qResourceAdapter; resourceAdapter.setConfirmationWindowSize(-1); resourceAdapter.setCallTimeout(1000L); resourceAdapter.setConsumerWindowSize(1024 * 1024); resourceAdapter.setReconnectAttempts(-1); resourceAdapter.setRetryInterval(100L); // qResourceAdapter.setTransactionManagerLocatorClass(DummyTMLocator.class.getName()); // qResourceAdapter.setTransactionManagerLocatorMethod("getTM"); MyBootstrapContext ctx = new MyBootstrapContext(); qResourceAdapter.setConnectorClassName(NETTY_CONNECTOR_FACTORY); qResourceAdapter.start(ctx); final int NUMBER_OF_SESSIONS = 10; ActiveMQActivationSpec spec = new ActiveMQActivationSpec(); spec.setTransactionTimeout(1); spec.setMaxSession(NUMBER_OF_SESSIONS); spec.setSetupAttempts(-1); spec.setSetupInterval(100); spec.setResourceAdapter(qResourceAdapter); spec.setUseJNDI(false); spec.setDestinationType("javax.jms.Queue"); spec.setDestination(MDBQUEUE); // Some the routines would be screwed up if using the default one Assert.assertFalse(spec.isHasBeenUpdated()); TestEndpointFactory endpointFactory = new TestEndpointFactory(true); qResourceAdapter.endpointActivation(endpointFactory, spec); Assert.assertEquals(1, resourceAdapter.getActivations().values().size()); ActiveMQActivation activation = resourceAdapter.getActivations().values().toArray(new ActiveMQActivation[1])[0]; final int NUMBER_OF_MESSAGES = 1000; Thread producer = new Thread() { @Override public void run() { try { ServerLocator locator = createInVMLocator(0); ClientSessionFactory factory = locator.createSessionFactory(); ClientSession session = factory.createSession(false, false); ClientProducer clientProducer = session.createProducer(MDBQUEUEPREFIXED); StringBuffer buffer = new StringBuffer(); for (int b = 0; b < 500; b++) { buffer.append("ab"); } for (int i = 0; i < NUMBER_OF_MESSAGES; i++) { ClientMessage message = session.createMessage(true); message.getBodyBuffer().writeString(buffer.toString() + i); message.putIntProperty("i", i); clientProducer.send(message); if (i % 100 == 0) { session.commit(); } } session.commit(); } catch (Exception e) { e.printStackTrace(); } } }; producer.start(); final AtomicBoolean metaDataFailed = new AtomicBoolean(false); Thread buggerThread = new Thread() { @Override public void run() { while (running.get()) { try { Thread.sleep(RandomUtil.randomInterval(100, 200)); } catch (InterruptedException intex) { intex.printStackTrace(); return; } List<ServerSession> serverSessions = lookupServerSessions("resource-adapter", NUMBER_OF_SESSIONS); System.err.println("Contains " + serverSessions.size() + " RA sessions"); if (serverSessions.size() != NUMBER_OF_SESSIONS) { System.err.println("the server was supposed to have " + NUMBER_OF_MESSAGES + " RA Sessions but it only contained accordingly to the meta-data"); metaDataFailed.set(true); } else if (serverSessions.size() == NUMBER_OF_SESSIONS) { // it became the same after some reconnect? which would be acceptable metaDataFailed.set(false); } if (playServerClosingSession && serverSessions.size() > 0) { int randomBother = RandomUtil.randomInterval(0, serverSessions.size() - 1); System.out.println("bugging session " + randomBother); ServerSession serverSession = serverSessions.get(randomBother); if (playServerClosingConsumer && RandomUtil.randomBoolean()) { // will play this randomly, only half of the times for (ServerConsumer consumer : serverSession.getServerConsumers()) { try { // Simulating a rare race that could happen in production // where the consumer is closed while things are still happening consumer.close(true); Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } } } RemotingConnection connection = serverSession.getRemotingConnection(); connection.fail(new ActiveMQException("failed at random " + randomBother)); } } } }; buggerThread.start(); ServerLocator locator = createInVMLocator(0); ClientSessionFactory factory = locator.createSessionFactory(); ClientSession session = factory.createSession(false, false); session.start(); ClientConsumer consumer = session.createConsumer("outQueue"); for (int i = 0; i < NUMBER_OF_MESSAGES; i++) { ClientMessage message = consumer.receive(60000); if (message == null) { break; } if (i == NUMBER_OF_MESSAGES * 0.50) { // This is to make sure the MDBs will survive a reboot // and no duplications or message loss will happen because of this System.err.println("Rebooting the MDBs at least once!"); activation.startReconnectThread("I"); } if (i == NUMBER_OF_MESSAGES * 0.90) { System.out.println("Disabled failures at " + i); playTXTimeouts = false; playServerClosingSession = false; playServerClosingConsumer = false; } System.out.println("Received " + i + " messages"); doReceiveMessage(message); if (i % 200 == 0) { System.out.println("received " + i); session.commit(); } } session.commit(); while (true) { ClientMessage message = consumer.receiveImmediate(); if (message == null) { break; } System.out.println("Received extra message " + message); doReceiveMessage(message); } session.commit(); Assert.assertNull(consumer.receiveImmediate()); StringWriter writer = new StringWriter(); PrintWriter out = new PrintWriter(writer); boolean failed = false; for (int i = 0; i < NUMBER_OF_MESSAGES; i++) { AtomicInteger atomicInteger = mapCounter.get(Integer.valueOf(i)); if (atomicInteger == null) { out.println("didn't receive message with i=" + i); failed = true; } else if (atomicInteger.get() > 1) { out.println("message with i=" + i + " received " + atomicInteger.get() + " times"); failed = true; } } running.set(false); buggerThread.join(); producer.join(); qResourceAdapter.stop(); session.close(); if (failed) { for (int i = 0; i < 10; i++) { System.out.println("----------------------------------------------------"); } System.out.println(writer.toString()); } Assert.assertFalse(writer.toString(), failed); System.out.println("Received " + NUMBER_OF_MESSAGES + " messages"); Assert.assertFalse("There was meta-data failures, some sessions didn't reconnect properly", metaDataFailed.get()); } private void doReceiveMessage(ClientMessage message) throws Exception { Assert.assertNotNull(message); message.acknowledge(); Integer value = message.getIntProperty("i"); AtomicInteger mapCount = new AtomicInteger(1); mapCount = mapCounter.putIfAbsent(value, mapCount); if (mapCount != null) { mapCount.incrementAndGet(); } } private List<ServerSession> lookupServerSessions(String parameter, int numberOfSessions) { long timeout = System.currentTimeMillis() + 50000; List<ServerSession> serverSessions = new LinkedList<>(); do { if (!serverSessions.isEmpty()) { System.err.println("Retry on serverSessions!!! currently with " + serverSessions.size()); serverSessions.clear(); try { Thread.sleep(100); } catch (Exception e) { break; } } serverSessions.clear(); for (ServerSession session : server.getSessions()) { if (session.getMetaData(parameter) != null) { serverSessions.add(session); } } } while (running.get() && serverSessions.size() != numberOfSessions && timeout > System.currentTimeMillis()); System.err.println("Returning " + serverSessions.size() + " sessions"); return serverSessions; } protected class TestEndpointFactory implements MessageEndpointFactory { private final boolean isDeliveryTransacted; public TestEndpointFactory(boolean deliveryTransacted) { isDeliveryTransacted = deliveryTransacted; } @Override public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException { TestEndpoint retEnd = new TestEndpoint(); if (xaResource != null) { retEnd.setXAResource(xaResource); } return retEnd; } @Override public boolean isDeliveryTransacted(Method method) throws NoSuchMethodException { return isDeliveryTransacted; } } public class TestEndpoint extends DummyMessageEndpoint { ClientSessionFactory factory; ClientSession endpointSession; ClientProducer producer; Transaction currentTX; public TestEndpoint() { super(null); try { factory = nettyLocator.createSessionFactory(); // buggingList.add(factory); endpointSession = factory.createSession(true, false, false); producer = endpointSession.createProducer("outQueue"); } catch (Throwable e) { throw new RuntimeException(e); } } @Override public void beforeDelivery(Method method) throws NoSuchMethodException, ResourceException { super.beforeDelivery(method); try { DummyTMLocator.tm.begin(); currentTX = DummyTMLocator.tm.getTransaction(); currentTX.enlistResource(xaResource); } catch (Throwable e) { throw new RuntimeException(e.getMessage(), e); } } @Override public void onMessage(Message message) { Integer value = 0; try { value = message.getIntProperty("i"); } catch (Exception e) { } super.onMessage(message); try { currentTX.enlistResource(endpointSession); ClientMessage message1 = endpointSession.createMessage(true); message1.putIntProperty("i", value); producer.send(message1); currentTX.delistResource(endpointSession, XAResource.TMSUCCESS); if (playTXTimeouts) { if (RandomUtil.randomInterval(0, 5) == 3) { Thread.sleep(2000); } } } catch (Exception e) { e.printStackTrace(); try { currentTX.setRollbackOnly(); } catch (Exception ex) { } e.printStackTrace(); // throw new RuntimeException(e); } } @Override public void afterDelivery() throws ResourceException { // This is a copy & paste of what the Application server would do here try { if (currentTX.getStatus() == Status.STATUS_MARKED_ROLLBACK) { DummyTMLocator.tm.rollback(); } else { DummyTMLocator.tm.commit(); } } catch (HeuristicMixedException e) { throw new LocalTransactionException(e); } catch (SystemException e) { throw new LocalTransactionException(e); } catch (HeuristicRollbackException e) { throw new LocalTransactionException(e); } catch (RollbackException e) { throw new LocalTransactionException(e); } super.afterDelivery(); } } public static class DummyTMLocator { public static TransactionManagerImple tm; public static void stopTM() { try { TransactionReaper.terminate(true); TxControl.disable(true); } catch (Exception e) { e.printStackTrace(); } tm = null; } public static void startTM() { tm = new TransactionManagerImple(); TxControl.enable(); } public TransactionManager getTM() { return tm; } } }