/*
* 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.artemis.tests.stress.journal;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.client.ClientConsumer;
import org.apache.activemq.artemis.api.core.client.ClientMessage;
import org.apache.activemq.artemis.api.core.client.ClientProducer;
import org.apache.activemq.artemis.api.core.client.ClientSession;
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.api.core.client.ServerLocator;
import org.apache.activemq.artemis.core.config.Configuration;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.JournalType;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* A MultiThreadConsumerStressTest
* <br>
* This test validates consuming / sending messages while compacting is working
*/
public class MultiThreadConsumerStressTest extends ActiveMQTestBase {
// Constants -----------------------------------------------------
// Attributes ----------------------------------------------------
final SimpleString ADDRESS = new SimpleString("SomeAddress");
final SimpleString QUEUE = new SimpleString("SomeQueue");
private ActiveMQServer server;
private ClientSessionFactory sf;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
setupServer(JournalType.NIO);
}
@Test
public void testProduceAndConsume() throws Throwable {
int numberOfConsumers = 5;
// this test assumes numberOfConsumers == numberOfProducers
int numberOfProducers = numberOfConsumers;
int produceMessage = 10000;
int commitIntervalProduce = 100;
int consumeMessage = (int) (produceMessage * 0.9);
int commitIntervalConsume = 100;
ClientSession session = sf.createSession(false, false);
session.createQueue("compact", "compact-queue", true);
ClientProducer producer = session.createProducer("compact");
for (int i = 0; i < 100; i++) {
producer.send(session.createMessage(true));
}
session.commit();
// Number of messages expected to be received after restart
int numberOfMessagesExpected = (produceMessage - consumeMessage) * numberOfConsumers;
CountDownLatch latchReady = new CountDownLatch(numberOfConsumers + numberOfProducers);
CountDownLatch latchStart = new CountDownLatch(1);
ArrayList<BaseThread> threads = new ArrayList<>();
ProducerThread[] prod = new ProducerThread[numberOfProducers];
for (int i = 0; i < numberOfProducers; i++) {
prod[i] = new ProducerThread(i, latchReady, latchStart, produceMessage, commitIntervalProduce);
prod[i].start();
threads.add(prod[i]);
}
ConsumerThread[] cons = new ConsumerThread[numberOfConsumers];
for (int i = 0; i < numberOfConsumers; i++) {
cons[i] = new ConsumerThread(i, latchReady, latchStart, consumeMessage, commitIntervalConsume);
cons[i].start();
threads.add(cons[i]);
}
ActiveMQTestBase.waitForLatch(latchReady);
latchStart.countDown();
for (BaseThread t : threads) {
t.join();
if (t.e != null) {
throw t.e;
}
}
server.stop();
setupServer(JournalType.NIO);
ClientSession sess = sf.createSession(true, true);
ClientConsumer consumer = sess.createConsumer(QUEUE);
sess.start();
for (int i = 0; i < numberOfMessagesExpected; i++) {
ClientMessage msg = consumer.receive(5000);
Assert.assertNotNull(msg);
if (i % 1000 == 0) {
System.out.println("Received #" + i + " on thread before end");
}
msg.acknowledge();
}
Assert.assertNull(consumer.receiveImmediate());
sess.close();
}
private void setupServer(final JournalType journalType) throws Exception {
Configuration config = createDefaultNettyConfig().setJournalType(journalType).setJournalFileSize(ActiveMQDefaultConfiguration.getDefaultJournalFileSize()).setJournalMinFiles(ActiveMQDefaultConfiguration.getDefaultJournalMinFiles()).setJournalCompactMinFiles(2).setJournalCompactPercentage(50);
server = createServer(true, config);
server.start();
ServerLocator locator = createNettyNonHALocator().setBlockOnDurableSend(false).setBlockOnNonDurableSend(false).setBlockOnAcknowledge(false);
sf = createSessionFactory(locator);
ClientSession sess = sf.createSession();
try {
sess.createQueue(ADDRESS, QUEUE, true);
} catch (Exception ignored) {
}
sess.close();
locator.close();
locator = createInVMNonHALocator();
sf = createSessionFactory(locator);
}
// Static --------------------------------------------------------
// Constructors --------------------------------------------------
// Public --------------------------------------------------------
class BaseThread extends Thread {
Throwable e;
final CountDownLatch latchReady;
final CountDownLatch latchStart;
final int numberOfMessages;
final int commitInterval;
BaseThread(final String name,
final CountDownLatch latchReady,
final CountDownLatch latchStart,
final int numberOfMessages,
final int commitInterval) {
super(name);
this.latchReady = latchReady;
this.latchStart = latchStart;
this.commitInterval = commitInterval;
this.numberOfMessages = numberOfMessages;
}
}
class ProducerThread extends BaseThread {
ProducerThread(final int id,
final CountDownLatch latchReady,
final CountDownLatch latchStart,
final int numberOfMessages,
final int commitInterval) {
super("ClientProducer:" + id, latchReady, latchStart, numberOfMessages, commitInterval);
}
@Override
public void run() {
ClientSession session = null;
latchReady.countDown();
try {
ActiveMQTestBase.waitForLatch(latchStart);
session = sf.createSession(false, false);
ClientProducer prod = session.createProducer(ADDRESS);
for (int i = 0; i < numberOfMessages; i++) {
if (i % commitInterval == 0) {
session.commit();
}
if (i % 1000 == 0) {
// System.out.println(Thread.currentThread().getName() + "::received #" + i);
}
ClientMessage msg = session.createMessage(true);
prod.send(msg);
}
session.commit();
System.out.println("Thread " + Thread.currentThread().getName() +
" sent " +
numberOfMessages +
" messages");
} catch (Throwable e) {
e.printStackTrace();
this.e = e;
} finally {
try {
session.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
}
class ConsumerThread extends BaseThread {
ConsumerThread(final int id,
final CountDownLatch latchReady,
final CountDownLatch latchStart,
final int numberOfMessages,
final int commitInterval) {
super("ClientConsumer:" + id, latchReady, latchStart, numberOfMessages, commitInterval);
}
@Override
public void run() {
ClientSession session = null;
latchReady.countDown();
try {
ActiveMQTestBase.waitForLatch(latchStart);
session = sf.createSession(false, false);
session.start();
ClientConsumer cons = session.createConsumer(QUEUE);
for (int i = 0; i < numberOfMessages; i++) {
ClientMessage msg = cons.receive(60 * 1000);
msg.acknowledge();
if (i % commitInterval == 0) {
session.commit();
}
if (i % 1000 == 0) {
// System.out.println(Thread.currentThread().getName() + "::sent #" + i);
}
}
System.out.println("Thread " + Thread.currentThread().getName() +
" received " +
numberOfMessages +
" messages");
session.commit();
} catch (Throwable e) {
this.e = e;
} finally {
try {
session.close();
} catch (Throwable e) {
this.e = e;
}
}
}
}
// Package protected ---------------------------------------------
// Protected -----------------------------------------------------
// Private -------------------------------------------------------
// Inner classes -------------------------------------------------
}