From 6d1f57b13776cd037b3f61c40b6cd86595479f5b Mon Sep 17 00:00:00 2001 From: Gary Tully Date: Mon, 17 Nov 2008 11:49:28 +0000 Subject: [PATCH] test case and logging improvements relating to cyclic/multicast discovery network of size 3 and topic message duplication with nteworkTTL > 1 git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@718224 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/activemq/ActiveMQConnection.java | 2 +- .../broker/region/PrefetchSubscription.java | 5 +- .../DemandForwardingBridgeSupport.java | 12 +- .../network/DiscoveryNetworkConnector.java | 8 +- .../multicast/MulticastDiscoveryAgent.java | 43 ++-- .../NoDuplicateOnTopicNetworkTest.java | 229 ++++++++++++++++++ 6 files changed, 270 insertions(+), 29 deletions(-) create mode 100644 activemq-core/src/test/java/org/apache/activemq/usecases/NoDuplicateOnTopicNetworkTest.java diff --git a/activemq-core/src/main/java/org/apache/activemq/ActiveMQConnection.java b/activemq-core/src/main/java/org/apache/activemq/ActiveMQConnection.java index 24c84428ea..3e50a08665 100755 --- a/activemq-core/src/main/java/org/apache/activemq/ActiveMQConnection.java +++ b/activemq-core/src/main/java/org/apache/activemq/ActiveMQConnection.java @@ -1739,7 +1739,7 @@ public class ActiveMQConnection implements Connection, TopicConnection, QueueCon /** * Handles async client internal exceptions. * A client internal exception is usually one that has been thrown - * by a container runtie component during asynchronous processing of a + * by a container runtime component during asynchronous processing of a * message that does not affect the connection itself. * This method notifies the ClientInternalExceptionListener by invoking * its onException method, if one has been registered with this connection. diff --git a/activemq-core/src/main/java/org/apache/activemq/broker/region/PrefetchSubscription.java b/activemq-core/src/main/java/org/apache/activemq/broker/region/PrefetchSubscription.java index 25764d0f28..7b390ebb0c 100755 --- a/activemq-core/src/main/java/org/apache/activemq/broker/region/PrefetchSubscription.java +++ b/activemq-core/src/main/java/org/apache/activemq/broker/region/PrefetchSubscription.java @@ -199,7 +199,7 @@ public abstract class PrefetchSubscription extends AbstractSubscription { inAckRange = true; } if (inAckRange) { - // Don't remove the nodes until we are committed. + // Don't remove the nodes until we are committed. if (!context.isInTransaction()) { dequeueCounter++; if (!this.getConsumerInfo().isBrowser()) { @@ -282,9 +282,6 @@ public abstract class PrefetchSubscription extends AbstractSubscription { }else if (ack.isDeliveredAck()) { // Message was delivered but not acknowledged: update pre-fetch // counters. - // Acknowledge all dispatched messages up till the message id of - // the - // acknowledgment. int index = 0; for (Iterator iter = dispatched.iterator(); iter.hasNext(); index++) { final MessageReference node = iter.next(); diff --git a/activemq-core/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java b/activemq-core/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java index 90243a77ce..2c39903b89 100644 --- a/activemq-core/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java +++ b/activemq-core/src/main/java/org/apache/activemq/network/DemandForwardingBridgeSupport.java @@ -500,7 +500,7 @@ public abstract class DemandForwardingBridgeSupport implements NetworkBridge { BrokerId[] path = info.getBrokerPath(); if (path != null && path.length >= networkTTL) { if (LOG.isDebugEnabled()) { - LOG.debug(configuration.getBrokerName() + " Ignoring Subscription " + info + " restricted to " + networkTTL + " network hops only"); + LOG.debug(configuration.getBrokerName() + " Ignoring sub from " + remoteBrokerName + ", restricted to " + networkTTL + " network hops only : " + info); } return; } @@ -508,24 +508,24 @@ public abstract class DemandForwardingBridgeSupport implements NetworkBridge { // Ignore this consumer as it's a consumer we locally sent to // the broker. if (LOG.isDebugEnabled()) { - LOG.debug(configuration.getBrokerName() + " Ignoring sub " + info + " already routed through this broker once"); + LOG.debug(configuration.getBrokerName() + " Ignoring sub from " + remoteBrokerName + ", already routed through this broker once : " + info); } return; } if (!isPermissableDestination(info.getDestination())) { - // ignore if not in the permited or in the excluded list + // ignore if not in the permitted or in the excluded list if (LOG.isDebugEnabled()) { - LOG.debug(configuration.getBrokerName() + " Ignoring sub " + info + " destination " + info.getDestination() + " is not permiited"); + LOG.debug(configuration.getBrokerName() + " Ignoring sub from " + remoteBrokerName + ", destination " + info.getDestination() + " is not permiited :" + info); } return; } if (addConsumerInfo(info)) { if (LOG.isDebugEnabled()) { - LOG.debug(configuration.getBrokerName() + " Forwarding sub on " + localBroker + " from " + remoteBrokerName + " : " + info); + LOG.debug(configuration.getBrokerName() + " Forwarding sub on " + localBroker + " from " + remoteBrokerName + " : " + info); } } else { if (LOG.isDebugEnabled()) { - LOG.debug(configuration.getBrokerName() + " Ignoring sub " + info + " already subscribed to matching destination"); + LOG.debug(configuration.getBrokerName() + " Ignoring sub from " + remoteBrokerName + " as already subscribed to matching destination : " + info); } } } else if (data.getClass() == DestinationInfo.class) { diff --git a/activemq-core/src/main/java/org/apache/activemq/network/DiscoveryNetworkConnector.java b/activemq-core/src/main/java/org/apache/activemq/network/DiscoveryNetworkConnector.java index 3f15ac9955..e195d8c760 100644 --- a/activemq-core/src/main/java/org/apache/activemq/network/DiscoveryNetworkConnector.java +++ b/activemq-core/src/main/java/org/apache/activemq/network/DiscoveryNetworkConnector.java @@ -79,10 +79,11 @@ public class DiscoveryNetworkConnector extends NetworkConnector implements Disco return; } if ( localURI.equals(uri) || (connectionFilter != null && !connectionFilter.connectTo(uri))) { + LOG.debug("not connecting loopback: " + uri); return; } URI connectUri = uri; - LOG.info("Establishing network connection between from " + localURIName + " to " + connectUri); + LOG.info("Establishing network connection from " + localURIName + " to " + connectUri); Transport remoteTransport; Transport localTransport; @@ -213,10 +214,13 @@ public class DiscoveryNetworkConnector extends NetworkConnector implements Disco String name = super.getName(); if (name == null) { name = discoveryAgent.toString(); - ; super.setName(name); } return name; } + @Override + public String toString() { + return "DiscoveryNetworkConnector:" + getName() + ":" + getBrokerService(); + } } diff --git a/activemq-core/src/main/java/org/apache/activemq/transport/discovery/multicast/MulticastDiscoveryAgent.java b/activemq-core/src/main/java/org/apache/activemq/transport/discovery/multicast/MulticastDiscoveryAgent.java index b22ccbd2d6..4a3813933d 100755 --- a/activemq-core/src/main/java/org/apache/activemq/transport/discovery/multicast/MulticastDiscoveryAgent.java +++ b/activemq-core/src/main/java/org/apache/activemq/transport/discovery/multicast/MulticastDiscoveryAgent.java @@ -176,14 +176,7 @@ public class MulticastDiscoveryAgent implements DiscoveryAgent, Runnable { private long lastAdvertizeTime; private AtomicBoolean started = new AtomicBoolean(false); private boolean reportAdvertizeFailed = true; - - private final Executor executor = new ThreadPoolExecutor(1, 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue(), new ThreadFactory() { - public Thread newThread(Runnable runable) { - Thread t = new Thread(runable, "Multicast Discovery Agent Notifier"); - t.setDaemon(true); - return t; - } - }); + private Executor executor = null; /** * Set the discovery listener @@ -304,7 +297,7 @@ public class MulticastDiscoveryAgent implements DiscoveryAgent, Runnable { mcast.joinGroup(inetAddress); mcast.setSoTimeout((int)keepAliveInterval); runner = new Thread(this); - runner.setName("MulticastDiscovery: " + selfService); + runner.setName(this.toString() + ":" + runner.getName()); runner.setDaemon(true); runner.start(); doAdvertizeSelf(); @@ -409,11 +402,9 @@ public class MulticastDiscoveryAgent implements DiscoveryAgent, Runnable { RemoteBrokerData data = brokersByService.get(service); if (data == null) { data = new RemoteBrokerData(brokerName, service); - brokersByService.put(service, data); - + brokersByService.put(service, data); fireServiceAddEvent(data); doAdvertizeSelf(); - } else { data.updateHeartBeat(); if (data.doRecovery()) { @@ -433,7 +424,7 @@ public class MulticastDiscoveryAgent implements DiscoveryAgent, Runnable { } private void doExpireOldServices() { - long expireTime = System.currentTimeMillis() - (keepAliveInterval * HEARTBEAT_MISS_BEFORE_DEATH); + long expireTime = System.currentTimeMillis() - (keepAliveInterval * HEARTBEAT_MISS_BEFORE_DEATH); for (Iterator i = brokersByService.values().iterator(); i.hasNext();) { RemoteBrokerData data = i.next(); if (data.getLastHeartBeat() < expireTime) { @@ -467,7 +458,7 @@ public class MulticastDiscoveryAgent implements DiscoveryAgent, Runnable { // Have the listener process the event async so that // he does not block this thread since we are doing time sensitive // processing of events. - executor.execute(new Runnable() { + getExecutor().execute(new Runnable() { public void run() { DiscoveryListener discoveryListener = MulticastDiscoveryAgent.this.discoveryListener; if (discoveryListener != null) { @@ -482,11 +473,11 @@ public class MulticastDiscoveryAgent implements DiscoveryAgent, Runnable { if (discoveryListener != null) { final DiscoveryEvent event = new DiscoveryEvent(data.service); event.setBrokerName(data.brokerName); - + // Have the listener process the event async so that // he does not block this thread since we are doing time sensitive // processing of events. - executor.execute(new Runnable() { + getExecutor().execute(new Runnable() { public void run() { DiscoveryListener discoveryListener = MulticastDiscoveryAgent.this.discoveryListener; if (discoveryListener != null) { @@ -497,6 +488,20 @@ public class MulticastDiscoveryAgent implements DiscoveryAgent, Runnable { } } + private Executor getExecutor() { + if (executor == null) { + final String threadName = "Notifier-" + this.toString(); + executor = new ThreadPoolExecutor(1, 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue(), new ThreadFactory() { + public Thread newThread(Runnable runable) { + Thread t = new Thread(runable, threadName); + t.setDaemon(true); + return t; + } + }); + } + return executor; + } + public long getBackOffMultiplier() { return backOffMultiplier; } @@ -540,4 +545,10 @@ public class MulticastDiscoveryAgent implements DiscoveryAgent, Runnable { public void setGroup(String group) { this.group = group; } + + @Override + public String toString() { + return "MulticastDiscoveryAgent-" + + (selfService != null ? "advertise:" + selfService : "listener:" + this.discoveryListener); + } } diff --git a/activemq-core/src/test/java/org/apache/activemq/usecases/NoDuplicateOnTopicNetworkTest.java b/activemq-core/src/test/java/org/apache/activemq/usecases/NoDuplicateOnTopicNetworkTest.java new file mode 100644 index 0000000000..45ee05bcfb --- /dev/null +++ b/activemq-core/src/test/java/org/apache/activemq/usecases/NoDuplicateOnTopicNetworkTest.java @@ -0,0 +1,229 @@ +package org.apache.activemq.usecases; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.jms.Connection; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.jms.Topic; + +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.activemq.broker.BrokerService; +import org.apache.activemq.network.NetworkConnector; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import junit.framework.TestCase; + +public class NoDuplicateOnTopicNetworkTest extends TestCase { + private static final Log LOG = LogFactory + .getLog(NoDuplicateOnTopicNetworkTest.class); + + private static final String MULTICAST_DEFAULT = "multicast://default"; + private static final String BROKER_1 = "tcp://localhost:61626"; + private static final String BROKER_2 = "tcp://localhost:61636"; + private static final String BROKER_3 = "tcp://localhost:61646"; + private BrokerService broker1; + private BrokerService broker2; + private BrokerService broker3; + + private boolean dynamicOnly = false; + // no duplicates in cyclic network if networkTTL <=1 + // when > 1, subscriptions perculate around resulting in duplicates as there is no + // memory of the original subscription. + // solution for 6.0 using org.apache.activemq.command.ConsumerInfo.getNetworkConsumerIds() + private int ttl = 1; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + broker3 = createAndStartBroker("broker3", BROKER_3); + Thread.sleep(3000); + broker2 = createAndStartBroker("broker2", BROKER_2); + Thread.sleep(3000); + broker1 = createAndStartBroker("broker1", BROKER_1); + Thread.sleep(1000); + } + + private BrokerService createAndStartBroker(String name, String addr) + throws Exception { + BrokerService broker = new BrokerService(); + broker.setBrokerName(name); + broker.addConnector(addr).setDiscoveryUri(new URI(MULTICAST_DEFAULT)); + broker.setUseJmx(false); + + NetworkConnector networkConnector = broker + .addNetworkConnector(MULTICAST_DEFAULT); + networkConnector.setDecreaseNetworkConsumerPriority(true); + networkConnector.setDynamicOnly(dynamicOnly); + networkConnector.setNetworkTTL(ttl); + + broker.start(); + + return broker; + } + + @Override + protected void tearDown() throws Exception { + broker1.stop(); + broker2.stop(); + broker3.stop(); + super.tearDown(); + } + + public void testProducerConsumerTopic() throws Exception { + final String topicName = "broadcast"; + Thread producerThread = new Thread(new Runnable() { + public void run() { + TopicWithDuplicateMessages producer = new TopicWithDuplicateMessages(); + producer.setBrokerURL(BROKER_1); + producer.setTopicName(topicName); + try { + producer.produce(); + } catch (JMSException e) { + fail("Unexpected " + e); + } + } + }); + + final TopicWithDuplicateMessages consumer = new TopicWithDuplicateMessages(); + Thread consumerThread = new Thread(new Runnable() { + public void run() { + consumer.setBrokerURL(BROKER_2); + consumer.setTopicName(topicName); + try { + consumer.consumer(); + consumer.getLatch().await(60, TimeUnit.SECONDS); + } catch (Exception e) { + fail("Unexpected " + e); + } + } + }); + + consumerThread.start(); + Thread.sleep(1000); + producerThread.start(); + producerThread.join(); + consumerThread.join(); + + Map map = new HashMap(); + for (String msg : consumer.getMessageStrings()) { + assertTrue("is not a duplicate: " + msg, !map.containsKey(msg)); + map.put(msg, msg); + } + assertEquals("got all required messages: " + map.size(), consumer + .getNumMessages(), map.size()); + } + + class TopicWithDuplicateMessages { + private String brokerURL; + private String topicName; + private Connection connection; + private Session session; + private Topic topic; + private MessageProducer producer; + private MessageConsumer consumer; + + private List receivedStrings = new ArrayList(); + private int numMessages = 10; + private CountDownLatch recievedLatch = new CountDownLatch(numMessages); + + public CountDownLatch getLatch() { + return recievedLatch; + } + + public List getMessageStrings() { + return receivedStrings; + } + + public String getBrokerURL() { + return brokerURL; + } + + public void setBrokerURL(String brokerURL) { + this.brokerURL = brokerURL; + } + + public String getTopicName() { + return topicName; + } + + public void setTopicName(String topicName) { + this.topicName = topicName; + } + + private void createConnection() throws JMSException { + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory( + brokerURL); + connection = factory.createConnection(); + } + + private void createTopic() throws JMSException { + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + topic = session.createTopic(topicName); + } + + private void createProducer() throws JMSException { + producer = session.createProducer(topic); + } + + private void createConsumer() throws JMSException { + consumer = session.createConsumer(topic); + consumer.setMessageListener(new MessageListener() { + + public void onMessage(Message arg0) { + TextMessage msg = (TextMessage) arg0; + try { + LOG.debug("Received message [" + msg.getText() + "]"); + receivedStrings.add(msg.getText()); + recievedLatch.countDown(); + } catch (JMSException e) { + fail("Unexpected :" + e); + } + } + + }); + } + + private void publish() throws JMSException { + for (int i = 0; i < numMessages; i++) { + TextMessage textMessage = session.createTextMessage(); + String message = "message: " + i; + LOG.debug("Sending message[" + message + "]"); + textMessage.setText(message); + producer.send(textMessage); + } + } + + public void produce() throws JMSException { + createConnection(); + createTopic(); + createProducer(); + connection.start(); + publish(); + } + + public void consumer() throws JMSException { + createConnection(); + createTopic(); + createConsumer(); + connection.start(); + } + + public int getNumMessages() { + return numMessages; + } + } +}