/**
* 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.bugs;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.atomic.AtomicInteger;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
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 org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.util.Wait;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Test for AMQ-3965.
* A consumer may be stalled in case it uses optimizeAcknowledge and receives
* a number of messages that expire before being dispatched to application code.
* See for more details.
*
*/
public class OptimizeAcknowledgeWithExpiredMsgsTest {
private final static Logger LOG = LoggerFactory.getLogger(OptimizeAcknowledgeWithExpiredMsgsTest.class);
private BrokerService broker = null;
private String connectionUri;
/**
* Creates a broker instance but does not start it.
*
* @param brokerUri - transport uri of broker
* @param brokerName - name for the broker
* @return a BrokerService instance with transport uri and broker name set
* @throws Exception
*/
protected BrokerService createBroker() throws Exception {
BrokerService broker = new BrokerService();
broker.setPersistent(false);
broker.setDeleteAllMessagesOnStartup(true);
broker.setUseJmx(false);
connectionUri = broker.addConnector("tcp://localhost:0").getPublishableConnectString();
return broker;
}
@Before
public void setUp() throws Exception {
broker = createBroker();
broker.start();
broker.waitUntilStarted();
}
@After
public void tearDown() throws Exception {
if (broker != null) {
broker.stop();
broker.waitUntilStopped();
broker = null;
}
}
/**
* Tests for AMQ-3965
* Creates connection into broker using optimzeAcknowledge and prefetch=100
* Creates producer and consumer. Producer sends 45 msgs that will expire
* at consumer (but before being dispatched to app code).
* Producer then sends 60 msgs without expiry.
*
* Consumer receives msgs using a MessageListener and increments a counter.
* Main thread sleeps for 5 seconds and checks the counter value.
* If counter != 60 msgs (the number of msgs that should get dispatched
* to consumer) the test fails.
*/
@Test
public void testOptimizedAckWithExpiredMsgs() throws Exception
{
ActiveMQConnectionFactory connectionFactory =
new ActiveMQConnectionFactory(connectionUri + "?jms.optimizeAcknowledge=true&jms.prefetchPolicy.all=100");
// Create JMS resources
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("TEST.FOO");
// ***** Consumer code *****
MessageConsumer consumer = session.createConsumer(destination);
final MyMessageListener listener = new MyMessageListener();
connection.setExceptionListener((ExceptionListener) listener);
// ***** Producer Code *****
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
String text = "Hello world! From: " + Thread.currentThread().getName() + " : " + this.hashCode();
TextMessage message;
// Produce msgs that will expire quickly
for (int i=0; i<45; i++) {
message = session.createTextMessage(text);
producer.send(message,1,1,100);
LOG.trace("Sent message: "+ message.getJMSMessageID() +
" with expiry 10 msec");
}
// Produce msgs that don't expire
for (int i=0; i<60; i++) {
message = session.createTextMessage(text);
producer.send(message,1,1,60000);
// producer.send(message);
LOG.trace("Sent message: "+ message.getJMSMessageID() +
" with expiry 30 sec");
}
consumer.setMessageListener(listener);
sleep(1000); // let the batch of 45 expire.
connection.start();
assertTrue("Should receive all expected messages, counter at " + listener.getCounter(), Wait.waitFor(new Wait.Condition() {
@Override
public boolean isSatisified() throws Exception {
return listener.getCounter() == 60;
}
}));
LOG.info("Received all expected messages with counter at: " + listener.getCounter());
// Cleanup
producer.close();
consumer.close();
session.close();
connection.close();
}
@Test
public void testOptimizedAckWithExpiredMsgsSync() throws Exception
{
ActiveMQConnectionFactory connectionFactory =
new ActiveMQConnectionFactory(connectionUri + "?jms.optimizeAcknowledge=true&jms.prefetchPolicy.all=100");
// Create JMS resources
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("TEST.FOO");
// ***** Consumer code *****
MessageConsumer consumer = session.createConsumer(destination);
// ***** Producer Code *****
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
String text = "Hello world! From: " + Thread.currentThread().getName() + " : " + this.hashCode();
TextMessage message;
// Produce msgs that will expire quickly
for (int i=0; i<45; i++) {
message = session.createTextMessage(text);
producer.send(message,1,1,10);
LOG.trace("Sent message: "+ message.getJMSMessageID() +
" with expiry 10 msec");
}
// Produce msgs that don't expire
for (int i=0; i<60; i++) {
message = session.createTextMessage(text);
producer.send(message,1,1,30000);
// producer.send(message);
LOG.trace("Sent message: "+ message.getJMSMessageID() +
" with expiry 30 sec");
}
sleep(200);
int counter = 1;
for (; counter <= 60; ++counter) {
assertNotNull(consumer.receive(2000));
LOG.info("counter at " + counter);
}
LOG.info("Received all expected messages with counter at: " + counter);
// Cleanup
producer.close();
consumer.close();
session.close();
connection.close();
}
@Test
public void testOptimizedAckWithExpiredMsgsSync2() throws Exception
{
ActiveMQConnectionFactory connectionFactory =
new ActiveMQConnectionFactory(connectionUri + "?jms.optimizeAcknowledge=true&jms.prefetchPolicy.all=100");
// Create JMS resources
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("TEST.FOO");
// ***** Consumer code *****
MessageConsumer consumer = session.createConsumer(destination);
// ***** Producer Code *****
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
String text = "Hello world! From: " + Thread.currentThread().getName() + " : " + this.hashCode();
TextMessage message;
// Produce msgs that don't expire
for (int i=0; i<56; i++) {
message = session.createTextMessage(text);
producer.send(message,1,1,30000);
// producer.send(message);
LOG.trace("Sent message: "+ message.getJMSMessageID() +
" with expiry 30 sec");
}
// Produce msgs that will expire quickly
for (int i=0; i<44; i++) {
message = session.createTextMessage(text);
producer.send(message,1,1,10);
LOG.trace("Sent message: "+ message.getJMSMessageID() +
" with expiry 10 msec");
}
// Produce some moremsgs that don't expire
for (int i=0; i<4; i++) {
message = session.createTextMessage(text);
producer.send(message,1,1,30000);
// producer.send(message);
LOG.trace("Sent message: "+ message.getJMSMessageID() +
" with expiry 30 sec");
}
sleep(200);
int counter = 1;
for (; counter <= 60; ++counter) {
assertNotNull(consumer.receive(2000));
LOG.info("counter at " + counter);
}
LOG.info("Received all expected messages with counter at: " + counter);
// Cleanup
producer.close();
consumer.close();
session.close();
connection.close();
}
private void sleep(int milliSecondTime) {
try {
Thread.sleep(milliSecondTime);
} catch (InterruptedException igonred) {
}
}
/**
* Standard JMS MessageListener
*/
private class MyMessageListener implements MessageListener, ExceptionListener {
private AtomicInteger counter = new AtomicInteger(0);
public void onMessage(final Message message) {
try {
LOG.trace("Got Message " + message.getJMSMessageID());
LOG.info("counter at " + counter.incrementAndGet());
} catch (final Exception e) {
}
}
public int getCounter() {
return counter.get();
}
public synchronized void onException(JMSException ex) {
LOG.error("JMS Exception occured. Shutting down client.");
}
}
}