/** * 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.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; import javax.jms.Connection; import javax.jms.Destination; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import org.apache.activemq.ActiveMQConnectionFactory; 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.ActiveMQMessage; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * Test the loss of messages detected during testing with ActiveMQ 5.4.1 and 5.4.2. * <p/> * Symptoms: - 1 record is lost "early" in the stream. - no more records lost. * <p/> * Test Configuration: - Broker Settings: - Destination Policy - Occurs with "Destination Policy" using Store Cursor and * a memory limit - Not reproduced without "Destination Policy" defined - Persistence Adapter - Memory: Does not occur. * - KahaDB: Occurs. - Messages - Occurs with TextMessage and BinaryMessage - Persistent messages. * <p/> * Notes: - Lower memory limits increase the rate of occurrence. - Higher memory limits may prevent the problem * (probably because memory limits not reached). - Producers sending a number of messages before consumers come online * increases rate of occurrence. */ public class AMQ3167Test { protected BrokerService embeddedBroker; protected static final int MEMORY_LIMIT = 16 * 1024; protected static boolean Debug_f = false; protected long Producer_stop_time = 0; protected long Consumer_stop_time = 0; protected long Consumer_startup_delay_ms = 2000; protected boolean Stop_after_error = true; protected Connection JMS_conn; protected long Num_error = 0; // // //// // // UTILITIES //// // // //// /** * Create a new, unsecured, client connection to the test broker using the given username and password. This * connection bypasses all security. * <p/> * Don't forget to start the connection or no messages will be received by consumers even though producers will work * fine. * * @username name of the JMS user for the connection; may be null. * @password Password for the JMS user; may be null. */ protected Connection createUnsecuredConnection(String username, String password) throws javax.jms.JMSException { ActiveMQConnectionFactory conn_fact; conn_fact = new ActiveMQConnectionFactory(embeddedBroker.getVmConnectorURI()); return conn_fact.createConnection(username, password); } // // //// // // TEST FUNCTIONALITY //// // // //// @Before public void testPrep() throws Exception { embeddedBroker = new BrokerService(); configureBroker(embeddedBroker); embeddedBroker.start(); embeddedBroker.waitUntilStarted(); // Prepare the connection JMS_conn = createUnsecuredConnection(null, null); JMS_conn.start(); } @After public void testCleanup() throws java.lang.Exception { JMS_conn.stop(); embeddedBroker.stop(); } protected void configureBroker(BrokerService broker_svc) throws Exception { broker_svc.setBrokerName("testbroker1"); broker_svc.setUseJmx(false); broker_svc.setPersistent(true); broker_svc.setDataDirectory("target/AMQ3167Test"); configureDestinationPolicy(broker_svc); } /** * NOTE: overrides any prior policy map defined for the broker service. */ protected void configureDestinationPolicy(BrokerService broker_svc) { PolicyMap pol_map; PolicyEntry pol_ent; ArrayList<PolicyEntry> ent_list; ent_list = new ArrayList<PolicyEntry>(); // // QUEUES // pol_ent = new PolicyEntry(); pol_ent.setQueue(">"); pol_ent.setMemoryLimit(MEMORY_LIMIT); pol_ent.setProducerFlowControl(false); ent_list.add(pol_ent); // // COMPLETE POLICY MAP // pol_map = new PolicyMap(); pol_map.setPolicyEntries(ent_list); broker_svc.setDestinationPolicy(pol_map); } // // //// // // TEST //// // // //// @Test public void testQueueLostMessage() throws Exception { Destination dest; dest = ActiveMQDestination.createDestination("lostmsgtest.queue", ActiveMQDestination.QUEUE_TYPE); // 10 seconds from now Producer_stop_time = java.lang.System.nanoTime() + (10L * 1000000000L); // 15 seconds from now Consumer_stop_time = Producer_stop_time + (5L * 1000000000L); runLostMsgTest(dest, 1000000, 1, 1, false); // Make sure failures in the threads are thoroughly reported in the JUnit framework. assertTrue(Num_error == 0); } /** * */ protected static void log(String msg) { if (Debug_f) java.lang.System.err.println(msg); } /** * Main body of the lost-message test. */ protected void runLostMsgTest(Destination dest, int num_msg, int num_send_per_sess, int num_recv_per_sess, boolean topic_f) throws Exception { Thread prod_thread; Thread cons_thread; String tag; Session sess; MessageProducer prod; MessageConsumer cons; int ack_mode; // // Start the producer // tag = "prod"; log(">> Starting producer " + tag); sess = JMS_conn.createSession((num_send_per_sess > 1), Session.AUTO_ACKNOWLEDGE); prod = sess.createProducer(dest); prod_thread = new producerThread(sess, prod, tag, num_msg, num_send_per_sess); prod_thread.start(); log("Started producer " + tag); // // Delay before starting consumers // log("Waiting before starting consumers"); java.lang.Thread.sleep(Consumer_startup_delay_ms); // // Now create and start the consumer // tag = "cons"; log(">> Starting consumer"); if (num_recv_per_sess > 1) ack_mode = Session.CLIENT_ACKNOWLEDGE; else ack_mode = Session.AUTO_ACKNOWLEDGE; sess = JMS_conn.createSession(false, ack_mode); cons = sess.createConsumer(dest); cons_thread = new consumerThread(sess, cons, tag, num_msg, num_recv_per_sess); cons_thread.start(); log("Started consumer " + tag); // // Wait for the producer and consumer to finish. // log("< waiting for producer."); prod_thread.join(); log("< waiting for consumer."); cons_thread.join(); log("Shutting down"); } // // //// // // INTERNAL CLASSES //// // // //// /** * Producer thread - runs a single producer until the maximum number of messages is sent, the producer stop time is * reached, or a test error is detected. */ protected class producerThread extends Thread { protected Session msgSess; protected MessageProducer msgProd; protected String producerTag; protected int numMsg; protected int numPerSess; protected long producer_stop_time; producerThread(Session sess, MessageProducer prod, String tag, int num_msg, int sess_size) { super(); producer_stop_time = 0; msgSess = sess; msgProd = prod; producerTag = tag; numMsg = num_msg; numPerSess = sess_size; } public void execTest() throws Exception { Message msg; int sess_start; int cur; sess_start = 0; cur = 0; while ((cur < numMsg) && (!didTimeOut()) && ((!Stop_after_error) || (Num_error == 0))) { msg = msgSess.createTextMessage("test message from " + producerTag); msg.setStringProperty("testprodtag", producerTag); msg.setIntProperty("seq", cur); if (msg instanceof ActiveMQMessage) { ((ActiveMQMessage) msg).setResponseRequired(true); } // // Send the message. // msgProd.send(msg); cur++; // // Commit if the number of messages per session has been reached, and // transactions are being used (only when > 1 msg per sess). // if ((numPerSess > 1) && ((cur - sess_start) >= numPerSess)) { msgSess.commit(); sess_start = cur; } } // Make sure to send the final commit, if there were sends since the last commit. if ((numPerSess > 1) && ((cur - sess_start) > 0)) msgSess.commit(); if (cur < numMsg) log("* Producer " + producerTag + " timed out at " + java.lang.System.nanoTime() + " (stop time " + producer_stop_time + ")"); } /** * Check whether it is time for the producer to terminate. */ protected boolean didTimeOut() { if ((Producer_stop_time > 0) && (java.lang.System.nanoTime() >= Producer_stop_time)) return true; return false; } /** * Run the producer. */ @Override public void run() { try { log("- running producer " + producerTag); execTest(); log("- finished running producer " + producerTag); } catch (Throwable thrown) { Num_error++; fail("producer " + producerTag + " failed: " + thrown.getMessage()); throw new Error("producer " + producerTag + " failed", thrown); } } @Override public String toString() { return producerTag; } } /** * Producer thread - runs a single consumer until the maximum number of messages is received, the consumer stop time * is reached, or a test error is detected. */ protected class consumerThread extends Thread { protected Session msgSess; protected MessageConsumer msgCons; protected String consumerTag; protected int numMsg; protected int numPerSess; consumerThread(Session sess, MessageConsumer cons, String tag, int num_msg, int sess_size) { super(); msgSess = sess; msgCons = cons; consumerTag = tag; numMsg = num_msg; numPerSess = sess_size; } public void execTest() throws Exception { Message msg; int sess_start; int cur; msg = null; sess_start = 0; cur = 0; while ((cur < numMsg) && (!didTimeOut()) && ((!Stop_after_error) || (Num_error == 0))) { // // Use a timeout of 1 second to periodically check the consumer timeout. // msg = msgCons.receive(1000); if (msg != null) { checkMessage(msg, cur); cur++; if ((numPerSess > 1) && ((cur - sess_start) >= numPerSess)) { msg.acknowledge(); sess_start = cur; } } } // Acknowledge the last messages, if they were not yet acknowledged. if ((numPerSess > 1) && ((cur - sess_start) > 0)) msg.acknowledge(); if (cur < numMsg) log("* Consumer " + consumerTag + " timed out"); } /** * Check whether it is time for the consumer to terminate. */ protected boolean didTimeOut() { if ((Consumer_stop_time > 0) && (java.lang.System.nanoTime() >= Consumer_stop_time)) return true; return false; } /** * Verify the message received. Sequence numbers are checked and are expected to exactly match the message * number (starting at 0). */ protected void checkMessage(Message msg, int exp_seq) throws javax.jms.JMSException { int seq; seq = msg.getIntProperty("seq"); if (exp_seq != seq) { Num_error++; fail("*** Consumer " + consumerTag + " expected seq " + exp_seq + "; received " + seq); } } /** * Run the consumer. */ @Override public void run() { try { log("- running consumer " + consumerTag); execTest(); log("- running consumer " + consumerTag); } catch (Throwable thrown) { Num_error++; fail("consumer " + consumerTag + " failed: " + thrown.getMessage()); throw new Error("consumer " + consumerTag + " failed", thrown); } } @Override public String toString() { return consumerTag; } } }