/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF 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.usecases; import javax.jms.MessageConsumer; import java.net.URI; import java.util.Vector; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import junit.framework.Test; import org.apache.activemq.JmsMultipleBrokersTestSupport; import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.region.policy.PolicyEntry; import org.apache.activemq.broker.region.policy.PolicyMap; import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQQueue; import org.apache.activemq.command.ActiveMQTopic; import org.apache.activemq.network.NetworkConnector; import org.apache.activemq.util.MessageIdList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Assert; /** * This test demonstrates and verifies the behaviour of a network bridge when it * forwards a message to a queue that is full and producer flow control is * enabled. * <br> * The expected behaviour is that the bridge will stop forwarding messages to * the full queue once the associated demand consumer's prefetch is full, but * will continue to forward messages to the other queues that are not full. * <br> * In actuality, a message that is sent <b>asynchronously</b> to a local queue, * but blocked by producer flow control on the remote queue, will stop the * bridge from forwarding all subsequent messages, even those destined for * remote queues that are not full. In the same scenario, but with a message * that is sent <b>synchronously</b> to the local queue, the bridge continues * forwarding messages to remote queues that are not full. * <br> * This test demonstrates the differing behaviour via the following scenario: * <ul> * <li>broker0, designated as the local broker, produces messages to two shared * queues * <li>broker1, designated as the remote broker, has two consumers: the first * consumes from one of the shared queues as fast as possible, the second * consumes from the other shared queue with an artificial processing delay for * each message * <li>broker0 forwards messages to broker1 over a TCP-based network bridge * with a demand consumer prefetch of 1 * <li>broker1's consumers have a prefetch of 1 * <li>broker1's "slow consumer" queue has a memory limit that triggers * producer flow control once the queue contains a small number of messages * </ul> * In this scenario, since broker1's consumers have a prefetch of 1, the "slow * consumer" queue will quickly become full and trigger producer flow control. * The "fast consumer" queue is unlikely to become full. Since producer flow * control on the "slow consumer" queue should not affect the "fast consumer" * queue, the expectation is that the fast consumer in broker1 will finish * processing all its messages well ahead of the slow consumer. * <br> * The difference between expected and actual behaviour is demonstrated by * changing the messages produced by broker0 from persistent to non-persistent. * With persistent messages, broker0 dispatches synchronously and the expected * behaviour is observed (i.e., the fast consumer on broker1 is much faster than * the slow consumer). With non-persistent messages, broker0 dispatches * asynchronously and the expected behaviour is <b>not</b> observed (i.e., the * fast consumer is only marginally faster than the slow consumer). * <br> * Since the expected behaviour may be desirable for both persistent and * non-persistent messages, this test also demonstrates an enhancement to the * network bridge configuration: <tt>isAlwaysSendSync</tt>. When false the * bridge operates as originally observed. When <tt>true</tt>, the bridge * operates with the same behaviour as was originally observed with persistent * messages, for both persistent and non-persistent messages. * <br> * https://issues.apache.org/jira/browse/AMQ-3331 * * @author schow */ public class NetworkBridgeProducerFlowControlTest extends JmsMultipleBrokersTestSupport { // Protect against hanging test. private static final long MAX_TEST_TIME = 120000; private static final Log LOG = LogFactory.getLog(NetworkBridgeProducerFlowControlTest.class); // Combo flag set to true/false by the test framework. public boolean persistentTestMessages; public boolean networkIsAlwaysSendSync; private Vector<Throwable> exceptions = new Vector<>(); public static Test suite() { return suite(NetworkBridgeProducerFlowControlTest.class); } public void initCombosForTestFastAndSlowRemoteConsumers() { addCombinationValues("persistentTestMessages", new Object[]{new Boolean(true), new Boolean(false)}); addCombinationValues("networkIsAlwaysSendSync", new Object[]{new Boolean(true), new Boolean(false)}); } @Override protected void setUp() throws Exception { setAutoFail(true); setMaxTestTime(MAX_TEST_TIME); super.setUp(); } /** * This test is parameterized by {@link #persistentTestMessages}, which * determines whether the producer on broker0 sends persistent or * non-persistent messages, and {@link #networkIsAlwaysSendSync}, which * determines how the bridge will forward both persistent and non-persistent * messages to broker1. * * @see #initCombosForTestFastAndSlowRemoteConsumers() */ public void testFastAndSlowRemoteConsumers() throws Exception { final int NUM_MESSAGES = 100; final long TEST_MESSAGE_SIZE = 1024; final long SLOW_CONSUMER_DELAY_MILLIS = 100; // Consumer prefetch is disabled for broker1's consumers. final ActiveMQQueue SLOW_SHARED_QUEUE = new ActiveMQQueue(NetworkBridgeProducerFlowControlTest.class.getSimpleName() + ".slow.shared?consumer.prefetchSize=1"); final ActiveMQQueue FAST_SHARED_QUEUE = new ActiveMQQueue(NetworkBridgeProducerFlowControlTest.class.getSimpleName() + ".fast.shared?consumer.prefetchSize=1"); // Start a local and a remote broker. createBroker(new URI("broker:(tcp://localhost:0" + ")?brokerName=broker0&persistent=false&useJmx=true")); BrokerService remoteBroker = createBroker(new URI("broker:(tcp://localhost:0" + ")?brokerName=broker1&persistent=false&useJmx=true")); // Set a policy on the remote broker that limits the maximum size of the // slow shared queue. PolicyEntry policyEntry = new PolicyEntry(); policyEntry.setMemoryLimit(5 * TEST_MESSAGE_SIZE); PolicyMap policyMap = new PolicyMap(); policyMap.put(SLOW_SHARED_QUEUE, policyEntry); remoteBroker.setDestinationPolicy(policyMap); // Create an outbound bridge from the local broker to the remote broker. // The bridge is configured with the remoteDispatchType enhancement. NetworkConnector nc = bridgeBrokers("broker0", "broker1"); nc.setAlwaysSyncSend(networkIsAlwaysSendSync); nc.setPrefetchSize(1); startAllBrokers(); waitForBridgeFormation(); // Send the test messages to the local broker's shared queues. The // messages are either persistent or non-persistent to demonstrate the // difference between synchronous and asynchronous dispatch. persistentDelivery = persistentTestMessages; sendMessages("broker0", FAST_SHARED_QUEUE, NUM_MESSAGES); sendMessages("broker0", SLOW_SHARED_QUEUE, NUM_MESSAGES); // Start two asynchronous consumers on the remote broker, one for each // of the two shared queues, and keep track of how long it takes for // each of the consumers to receive all the messages. final CountDownLatch fastConsumerLatch = new CountDownLatch(NUM_MESSAGES); final CountDownLatch slowConsumerLatch = new CountDownLatch(NUM_MESSAGES); final long startTimeMillis = System.currentTimeMillis(); final AtomicLong fastConsumerTime = new AtomicLong(); final AtomicLong slowConsumerTime = new AtomicLong(); Thread fastWaitThread = new Thread() { @Override public void run() { try { fastConsumerLatch.await(); fastConsumerTime.set(System.currentTimeMillis() - startTimeMillis); } catch (InterruptedException ex) { exceptions.add(ex); Assert.fail(ex.getMessage()); } } }; Thread slowWaitThread = new Thread() { @Override public void run() { try { slowConsumerLatch.await(); slowConsumerTime.set(System.currentTimeMillis() - startTimeMillis); } catch (InterruptedException ex) { exceptions.add(ex); Assert.fail(ex.getMessage()); } } }; fastWaitThread.start(); slowWaitThread.start(); createConsumer("broker1", FAST_SHARED_QUEUE, fastConsumerLatch); MessageConsumer slowConsumer = createConsumer("broker1", SLOW_SHARED_QUEUE, slowConsumerLatch); MessageIdList messageIdList = brokers.get("broker1").consumers.get(slowConsumer); messageIdList.setProcessingDelay(SLOW_CONSUMER_DELAY_MILLIS); fastWaitThread.join(); slowWaitThread.join(); assertTrue("no exceptions on the wait threads:" + exceptions, exceptions.isEmpty()); LOG.info("Fast consumer duration (ms): " + fastConsumerTime.get()); LOG.info("Slow consumer duration (ms): " + slowConsumerTime.get()); // Verify the behaviour as described in the description of this class. if (networkIsAlwaysSendSync) { Assert.assertTrue(fastConsumerTime.get() < slowConsumerTime.get() / 10); } else { Assert.assertEquals(persistentTestMessages, fastConsumerTime.get() < slowConsumerTime.get() / 10); } } public void testSendFailIfNoSpaceDoesNotBlockQueueNetwork() throws Exception { // Consumer prefetch is disabled for broker1's consumers. final ActiveMQQueue SLOW_SHARED_QUEUE = new ActiveMQQueue(NetworkBridgeProducerFlowControlTest.class.getSimpleName() + ".slow.shared?consumer.prefetchSize=1"); final ActiveMQQueue FAST_SHARED_QUEUE = new ActiveMQQueue(NetworkBridgeProducerFlowControlTest.class.getSimpleName() + ".fast.shared?consumer.prefetchSize=1"); doTestSendFailIfNoSpaceDoesNotBlockNetwork(SLOW_SHARED_QUEUE, FAST_SHARED_QUEUE); } public void testSendFailIfNoSpaceDoesNotBlockTopicNetwork() throws Exception { // Consumer prefetch is disabled for broker1's consumers. final ActiveMQTopic SLOW_SHARED_TOPIC = new ActiveMQTopic(NetworkBridgeProducerFlowControlTest.class.getSimpleName() + ".slow.shared?consumer.prefetchSize=1"); final ActiveMQTopic FAST_SHARED_TOPIC = new ActiveMQTopic(NetworkBridgeProducerFlowControlTest.class.getSimpleName() + ".fast.shared?consumer.prefetchSize=1"); doTestSendFailIfNoSpaceDoesNotBlockNetwork(SLOW_SHARED_TOPIC, FAST_SHARED_TOPIC); } public void doTestSendFailIfNoSpaceDoesNotBlockNetwork(ActiveMQDestination slowDestination, ActiveMQDestination fastDestination) throws Exception { final int NUM_MESSAGES = 100; final long TEST_MESSAGE_SIZE = 1024; final long SLOW_CONSUMER_DELAY_MILLIS = 100; // Start a local and a remote broker. createBroker(new URI("broker:(tcp://localhost:0" + ")?brokerName=broker0&persistent=false&useJmx=true")); BrokerService remoteBroker = createBroker(new URI("broker:(tcp://localhost:0" + ")?brokerName=broker1&persistent=false&useJmx=true")); remoteBroker.getSystemUsage().setSendFailIfNoSpace(true); // Set a policy on the remote broker that limits the maximum size of the // slow shared queue. PolicyEntry policyEntry = new PolicyEntry(); policyEntry.setMemoryLimit(5 * TEST_MESSAGE_SIZE); PolicyMap policyMap = new PolicyMap(); policyMap.put(slowDestination, policyEntry); remoteBroker.setDestinationPolicy(policyMap); // Create an outbound bridge from the local broker to the remote broker. // The bridge is configured with the remoteDispatchType enhancement. NetworkConnector nc = bridgeBrokers("broker0", "broker1"); nc.setAlwaysSyncSend(true); nc.setPrefetchSize(1); startAllBrokers(); waitForBridgeFormation(); // Start two asynchronous consumers on the remote broker, one for each // of the two shared queues, and keep track of how long it takes for // each of the consumers to receive all the messages. final CountDownLatch fastConsumerLatch = new CountDownLatch(NUM_MESSAGES); final CountDownLatch slowConsumerLatch = new CountDownLatch(NUM_MESSAGES); final long startTimeMillis = System.currentTimeMillis(); final AtomicLong fastConsumerTime = new AtomicLong(); final AtomicLong slowConsumerTime = new AtomicLong(); Thread fastWaitThread = new Thread() { @Override public void run() { try { fastConsumerLatch.await(); fastConsumerTime.set(System.currentTimeMillis() - startTimeMillis); } catch (InterruptedException ex) { exceptions.add(ex); Assert.fail(ex.getMessage()); } } }; Thread slowWaitThread = new Thread() { @Override public void run() { try { slowConsumerLatch.await(); slowConsumerTime.set(System.currentTimeMillis() - startTimeMillis); } catch (InterruptedException ex) { exceptions.add(ex); Assert.fail(ex.getMessage()); } } }; fastWaitThread.start(); slowWaitThread.start(); createConsumer("broker1", fastDestination, fastConsumerLatch); MessageConsumer slowConsumer = createConsumer("broker1", slowDestination, slowConsumerLatch); MessageIdList messageIdList = brokers.get("broker1").consumers.get(slowConsumer); messageIdList.setProcessingDelay(SLOW_CONSUMER_DELAY_MILLIS); // Send the test messages to the local broker's shared queues. The // messages are either persistent or non-persistent to demonstrate the // difference between synchronous and asynchronous dispatch. persistentDelivery = false; sendMessages("broker0", fastDestination, NUM_MESSAGES); sendMessages("broker0", slowDestination, NUM_MESSAGES); fastWaitThread.join(TimeUnit.SECONDS.toMillis(60)); slowWaitThread.join(TimeUnit.SECONDS.toMillis(60)); assertTrue("no exceptions on the wait threads:" + exceptions, exceptions.isEmpty()); LOG.info("Fast consumer duration (ms): " + fastConsumerTime.get()); LOG.info("Slow consumer duration (ms): " + slowConsumerTime.get()); assertTrue("fast time set", fastConsumerTime.get() > 0); assertTrue("slow time set", slowConsumerTime.get() > 0); // Verify the behaviour as described in the description of this class. Assert.assertTrue(fastConsumerTime.get() < slowConsumerTime.get() / 10); } }