ARTEMIS-4108 AMQP Drain fails under load with Large Messages
This commit is contained in:
parent
0004e52355
commit
eb11b044af
|
@ -129,6 +129,7 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
|
||||||
// as large message could be interrupted due to flow control and resumed at the same message
|
// as large message could be interrupted due to flow control and resumed at the same message
|
||||||
volatile boolean hasLarge = false;
|
volatile boolean hasLarge = false;
|
||||||
volatile LargeMessageDeliveryContext pendingLargeMessage = null;
|
volatile LargeMessageDeliveryContext pendingLargeMessage = null;
|
||||||
|
volatile Runnable afterLargeMessage;
|
||||||
|
|
||||||
|
|
||||||
private int credits = 0;
|
private int credits = 0;
|
||||||
|
@ -177,6 +178,10 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFlow(int currentCredits, boolean drain) {
|
public void onFlow(int currentCredits, boolean drain) {
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("flow {}, draing={}", currentCredits, drain);
|
||||||
|
}
|
||||||
connection.requireInHandler();
|
connection.requireInHandler();
|
||||||
|
|
||||||
setupCredit();
|
setupCredit();
|
||||||
|
@ -191,8 +196,11 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
connection.runNow(() -> {
|
connection.runNow(() -> {
|
||||||
plugSender.reportDrained();
|
if (pendingLargeMessage != null) {
|
||||||
setupCredit();
|
afterLargeMessage = () -> drained(plugSender);
|
||||||
|
} else {
|
||||||
|
drained(plugSender);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
draining.set(false);
|
draining.set(false);
|
||||||
|
@ -205,6 +213,11 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void drained(ProtonServerSenderContext sender) {
|
||||||
|
sender.reportDrained();
|
||||||
|
setupCredit();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasCredits() {
|
public boolean hasCredits() {
|
||||||
if (hasLarge) {
|
if (hasLarge) {
|
||||||
// we will resume accepting once the large message is finished
|
// we will resume accepting once the large message is finished
|
||||||
|
@ -786,6 +799,11 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
|
||||||
|
|
||||||
private void finishLargeMessage() {
|
private void finishLargeMessage() {
|
||||||
lmUsageDown();
|
lmUsageDown();
|
||||||
|
Runnable localRunnable = afterLargeMessage;
|
||||||
|
afterLargeMessage = null;
|
||||||
|
if (localRunnable != null) {
|
||||||
|
localRunnable.run();
|
||||||
|
}
|
||||||
pendingLargeMessage = null;
|
pendingLargeMessage = null;
|
||||||
hasLarge = false;
|
hasLarge = false;
|
||||||
brokerConsumer.promptDelivery();
|
brokerConsumer.promptDelivery();
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
/*
|
||||||
|
* 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.soak.client;
|
||||||
|
|
||||||
|
import javax.jms.Connection;
|
||||||
|
import javax.jms.ConnectionFactory;
|
||||||
|
import javax.jms.MessageConsumer;
|
||||||
|
import javax.jms.MessageProducer;
|
||||||
|
import javax.jms.Session;
|
||||||
|
import javax.jms.TextMessage;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||||
|
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
||||||
|
import org.apache.activemq.artemis.tests.util.CFUtil;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class LargeMessageSoakTest extends ActiveMQTestBase {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||||
|
|
||||||
|
ActiveMQServer server;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
this.server = this.createServer(true, true);
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAMQP() throws Exception {
|
||||||
|
testSendReceive("AMQP");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCORE() throws Exception {
|
||||||
|
testSendReceive("CORE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenWire() throws Exception {
|
||||||
|
testSendReceive("OPENWIRE");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSendReceive(String protocol) throws Exception {
|
||||||
|
AtomicInteger errors = new AtomicInteger(0);
|
||||||
|
|
||||||
|
final int THREADS = 5;
|
||||||
|
final int MESSAGE_COUNT = 5;
|
||||||
|
final int MESSAGE_SIZE = 10000000;
|
||||||
|
|
||||||
|
ExecutorService executorService = Executors.newFixedThreadPool(THREADS * 2);
|
||||||
|
runAfter(executorService::shutdownNow);
|
||||||
|
ConnectionFactory factory = CFUtil.createConnectionFactory(protocol, "tcp://localhost:61616");
|
||||||
|
|
||||||
|
final Connection connectionConsumer = factory.createConnection();
|
||||||
|
connectionConsumer.start();
|
||||||
|
final Connection connectionProducer = factory.createConnection();
|
||||||
|
|
||||||
|
runAfter(connectionProducer::close);
|
||||||
|
runAfter(connectionConsumer::close);
|
||||||
|
|
||||||
|
final String largetext;
|
||||||
|
|
||||||
|
{
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
while (buffer.length() < MESSAGE_SIZE) {
|
||||||
|
buffer.append("Lorem Ypsum blablabla blabalbala I don't care whatever it is in that thing...");
|
||||||
|
}
|
||||||
|
largetext = buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch done = new CountDownLatch(THREADS * 2);
|
||||||
|
|
||||||
|
|
||||||
|
for (int t = 0; t < THREADS; t++) {
|
||||||
|
final int localT = t;
|
||||||
|
executorService.execute(() -> {
|
||||||
|
try {
|
||||||
|
try (Session session = connectionConsumer.createSession(false, Session.AUTO_ACKNOWLEDGE)) {
|
||||||
|
MessageConsumer consumer = session.createConsumer(session.createQueue("TEST"));
|
||||||
|
for (int i = 0; i < MESSAGE_COUNT && errors.get() == 0; i++) {
|
||||||
|
TextMessage textMessage;
|
||||||
|
do {
|
||||||
|
textMessage = (TextMessage) consumer.receive(300);
|
||||||
|
if (textMessage == null) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace("Retrying on thread consumer {}", localT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (textMessage == null);
|
||||||
|
|
||||||
|
|
||||||
|
Assert.assertNotNull(textMessage);
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Consumer Thread {} received {} messages, protocol={}", localT, i, protocol);
|
||||||
|
}
|
||||||
|
// Since all messages come from the same queue on all consumers, this is the only assertion possible for the message
|
||||||
|
Assert.assertEquals(largetext, textMessage.getText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn(e.getMessage(), e);
|
||||||
|
errors.incrementAndGet();
|
||||||
|
} finally {
|
||||||
|
done.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int t = 0; t < THREADS; t++) {
|
||||||
|
final int localT = t;
|
||||||
|
executorService.execute(() -> {
|
||||||
|
try {
|
||||||
|
try (Session session = connectionProducer.createSession(false, Session.AUTO_ACKNOWLEDGE)) {
|
||||||
|
MessageProducer producer = session.createProducer(session.createQueue("TEST"));
|
||||||
|
for (int i = 0; i < MESSAGE_COUNT && errors.get() == 0; i++) {
|
||||||
|
TextMessage textMessage = session.createTextMessage(largetext);
|
||||||
|
producer.send(textMessage);
|
||||||
|
if (logger.isDebugEnabled() && i % 10 == 0) {
|
||||||
|
logger.debug("Producing thread {} sent {} messages, protocol={}", localT, i, protocol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn(e.getMessage(), e);
|
||||||
|
errors.incrementAndGet();
|
||||||
|
} finally {
|
||||||
|
done.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertTrue(done.await(5, TimeUnit.MINUTES));
|
||||||
|
Assert.assertEquals(0, errors.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue