/** * 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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * 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.performance.storage; import javax.jms.BytesMessage; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.Destination; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import java.io.File; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.JournalType; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory; import org.apache.activemq.artemis.jms.client.DefaultConnectionProperties; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.junit.Assert; import org.junit.Test; public class SendReceiveMultiThreadTest extends ActiveMQTestBase { final String DIRECTORY = "./target/journaltmp"; ConnectionFactory cf; Destination destination; AtomicInteger received = new AtomicInteger(0); AtomicInteger sent = new AtomicInteger(0); int NUMBER_OF_THREADS = 400; int NUMBER_OF_MESSAGES = 5000; CountDownLatch receivedLatch = new CountDownLatch(NUMBER_OF_MESSAGES * NUMBER_OF_THREADS); @Test public void testMultipleWrites() throws Exception { deleteDirectory(new File(DIRECTORY)); ActiveMQServer server = createServer(true); server.getConfiguration().setJournalFileSize(10 * 1024 * 1024); server.getConfiguration().setJournalMinFiles(2); server.getConfiguration().setJournalCompactMinFiles(ActiveMQDefaultConfiguration.getDefaultJournalCompactMinFiles()); server.getConfiguration().setJournalCompactPercentage(ActiveMQDefaultConfiguration.getDefaultJournalCompactPercentage()); server.getConfiguration().setJournalType(JournalType.ASYNCIO); server.getConfiguration().addAcceptorConfiguration("core", DefaultConnectionProperties.DEFAULT_BROKER_BIND_URL); server.getConfiguration().setJournalDirectory(DIRECTORY + "/journal"); server.getConfiguration().setBindingsDirectory(DIRECTORY + "/bindings"); server.getConfiguration().setPagingDirectory(DIRECTORY + "/paging"); server.getConfiguration().setLargeMessagesDirectory(DIRECTORY + "/largemessage"); server.getConfiguration().setJournalMaxIO_AIO(200); // TODO Setup Acceptors server.start(); Queue queue = server.createQueue(SimpleString.toSimpleString("performanceQueue"), RoutingType.ANYCAST, SimpleString.toSimpleString("performanceQueue"), null, true, false); Queue queue2 = server.createQueue(SimpleString.toSimpleString("stationaryQueue"), RoutingType.ANYCAST, SimpleString.toSimpleString("stationaryQueue"), null, true, false); MyThread[] threads = new MyThread[NUMBER_OF_THREADS]; ConsumerThread[] cthreads = new ConsumerThread[NUMBER_OF_THREADS]; final CountDownLatch alignFlag = new CountDownLatch(NUMBER_OF_THREADS); final CountDownLatch startFlag = new CountDownLatch(1); final CountDownLatch finishFlag = new CountDownLatch(NUMBER_OF_THREADS); cf = new ActiveMQConnectionFactory(); Thread slowSending = new Thread() { @Override public void run() { Connection conn = null; try { conn = cf.createConnection(); Session session = conn.createSession(true, Session.SESSION_TRANSACTED); MessageProducer producer = session.createProducer(ActiveMQJMSClient.createQueue("stationaryQueue")); conn.start(); MessageConsumer consumer = session.createConsumer(ActiveMQJMSClient.createQueue("stationaryQueue")); while (true) { for (int i = 0; i < 10; i++) { System.out.println("stationed message"); producer.send(session.createTextMessage("stationed")); session.commit(); Thread.sleep(1000); } for (int i = 0; i < 10; i++) { consumer.receive(5000); session.commit(); System.out.println("Receiving stationed"); Thread.sleep(1000); } } } catch (Exception e) { e.printStackTrace(); } finally { try { conn.close(); } catch (Exception ignored) { } } } }; slowSending.start(); destination = ActiveMQJMSClient.createQueue("performanceQueue"); for (int i = 0; i < threads.length; i++) { threads[i] = new MyThread("sender::" + i, NUMBER_OF_MESSAGES, alignFlag, startFlag, finishFlag); cthreads[i] = new ConsumerThread(NUMBER_OF_MESSAGES); } for (ConsumerThread t : cthreads) { t.start(); } for (MyThread t : threads) { t.start(); } Assert.assertEquals(NUMBER_OF_THREADS, queue.getConsumerCount()); alignFlag.await(); long startTime = System.currentTimeMillis(); startFlag.countDown(); // I'm using a countDown to avoid measuring time spent on thread context from join. // i.e. i want to measure as soon as the loops are done finishFlag.await(); long endtime = System.currentTimeMillis(); receivedLatch.await(); long endTimeConsuming = System.currentTimeMillis(); for (ConsumerThread t : cthreads) { t.join(); Assert.assertEquals(0, t.errors); } for (MyThread t : threads) { t.join(); Assert.assertEquals(0, t.errors.get()); } slowSending.interrupt(); slowSending.join(); server.stop(); System.out.println("Time on sending:: " + (endtime - startTime)); System.out.println("Time on consuming:: " + (endTimeConsuming - startTime)); } class ConsumerThread extends Thread { final int numberOfMessages; Connection connection; Session session; MessageConsumer consumer; ConsumerThread(int numberOfMessages) throws Exception { super("consumerthread"); this.numberOfMessages = numberOfMessages; connection = cf.createConnection(); session = connection.createSession(true, Session.SESSION_TRANSACTED); consumer = session.createConsumer(destination); connection.start(); } int errors = 0; @Override public void run() { try { for (int i = 0; i < numberOfMessages; i++) { Message message = consumer.receive(50000); if (message == null) { System.err.println("Could not receive message at i = " + numberOfMessages); errors++; break; } int r = received.incrementAndGet(); if (r % 1000 == 0) { System.out.println("Received " + r + " messages"); } if (i % 50 == 0) { session.commit(); } receivedLatch.countDown(); } session.commit(); connection.close(); } catch (Exception e) { e.printStackTrace(); errors++; } } } class MyThread extends Thread { final int numberOfMessages; final AtomicInteger errors = new AtomicInteger(0); final CountDownLatch align; final CountDownLatch start; final CountDownLatch finish; MyThread(String name, int numberOfMessages, CountDownLatch align, CountDownLatch start, CountDownLatch finish) { super(name); this.numberOfMessages = numberOfMessages; this.align = align; this.start = start; this.finish = finish; } @Override public void run() { try { Connection connection = cf.createConnection(); Session session = connection.createSession(true, Session.SESSION_TRANSACTED); MessageProducer producer = session.createProducer(destination); align.countDown(); start.await(); for (int i = 0; i < numberOfMessages; i++) { BytesMessage msg = session.createBytesMessage(); msg.writeBytes(new byte[1024]); producer.send(msg); session.commit(); int s = sent.incrementAndGet(); if (s % 1000 == 0) { System.out.println("Sent " + s); } } connection.close(); System.out.println("Send " + numberOfMessages + " messages on thread " + Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); errors.incrementAndGet(); } finally { finish.countDown(); } } } }