/*
* 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.integration.client;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import io.netty.buffer.ByteBuf;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQPropertyConversionException;
import org.apache.activemq.artemis.api.core.ICoreMessage;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.RefCountMessage;
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.MessageHandler;
import org.apache.activemq.artemis.api.core.client.ServerLocator;
import org.apache.activemq.artemis.core.client.impl.ClientSessionInternal;
import org.apache.activemq.artemis.core.persistence.Persister;
import org.apache.activemq.artemis.core.protocol.core.impl.ActiveMQConsumerContext;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.spi.core.remoting.ConsumerContext;
import org.apache.activemq.artemis.tests.integration.IntegrationTestLogger;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.UUID;
import org.junit.Assert;
import org.junit.Test;
public class AcknowledgeTest extends ActiveMQTestBase {
private static final IntegrationTestLogger log = IntegrationTestLogger.LOGGER;
public final SimpleString addressA = new SimpleString("addressA");
public final SimpleString queueA = new SimpleString("queueA");
public final SimpleString queueB = new SimpleString("queueB");
public final SimpleString queueC = new SimpleString("queueC");
@Test
public void testReceiveAckLastMessageOnly() throws Exception {
ActiveMQServer server = createServer(false);
server.start();
ServerLocator locator = createInVMNonHALocator().setAckBatchSize(0).setBlockOnAcknowledge(true);
ClientSessionFactory cf = createSessionFactory(locator);
ClientSession sendSession = cf.createSession(false, true, true);
ClientSession session = cf.createSession(false, true, true);
sendSession.createQueue(addressA, queueA, false);
ClientProducer cp = sendSession.createProducer(addressA);
ClientConsumer cc = session.createConsumer(queueA);
int numMessages = 100;
for (int i = 0; i < numMessages; i++) {
cp.send(sendSession.createMessage(false));
}
session.start();
ClientMessage cm = null;
for (int i = 0; i < numMessages; i++) {
cm = cc.receive(5000);
Assert.assertNotNull(cm);
}
cm.acknowledge();
Queue q = (Queue) server.getPostOffice().getBinding(queueA).getBindable();
Assert.assertEquals(0, q.getDeliveringCount());
session.close();
sendSession.close();
}
@Test
public void testAsyncConsumerNoAck() throws Exception {
ActiveMQServer server = createServer(false);
server.start();
ServerLocator locator = createInVMNonHALocator();
ClientSessionFactory cf = createSessionFactory(locator);
ClientSession sendSession = cf.createSession(false, true, true);
ClientSession session = cf.createSession(false, true, true);
sendSession.createQueue(addressA, queueA, false);
ClientProducer cp = sendSession.createProducer(addressA);
ClientConsumer cc = session.createConsumer(queueA);
int numMessages = 3;
for (int i = 0; i < numMessages; i++) {
cp.send(sendSession.createMessage(false));
}
Thread.sleep(500);
log.info("woke up");
final CountDownLatch latch = new CountDownLatch(numMessages);
session.start();
cc.setMessageHandler(new MessageHandler() {
int c = 0;
@Override
public void onMessage(final ClientMessage message) {
log.info("Got message " + c++);
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Queue q = (Queue) server.getPostOffice().getBinding(queueA).getBindable();
Assert.assertEquals(numMessages, q.getDeliveringCount());
sendSession.close();
session.close();
}
@Test
public void testAsyncConsumerAck() throws Exception {
ActiveMQServer server = createServer(false);
server.start();
ServerLocator locator = createInVMNonHALocator().setBlockOnAcknowledge(true).setAckBatchSize(0);
ClientSessionFactory cf = createSessionFactory(locator);
ClientSession sendSession = cf.createSession(false, true, true);
final ClientSession session = cf.createSession(false, true, true);
sendSession.createQueue(addressA, queueA, false);
ClientProducer cp = sendSession.createProducer(addressA);
ClientConsumer cc = session.createConsumer(queueA);
int numMessages = 100;
for (int i = 0; i < numMessages; i++) {
cp.send(sendSession.createMessage(false));
}
final CountDownLatch latch = new CountDownLatch(numMessages);
session.start();
cc.setMessageHandler(new MessageHandler() {
@Override
public void onMessage(final ClientMessage message) {
try {
message.acknowledge();
} catch (ActiveMQException e) {
try {
session.close();
} catch (ActiveMQException e1) {
e1.printStackTrace();
}
}
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Queue q = (Queue) server.getPostOffice().getBinding(queueA).getBindable();
Assert.assertEquals(0, q.getDeliveringCount());
sendSession.close();
session.close();
}
/**
* This is validating a case where a consumer will try to ack a message right after failover, but the consumer at the target server didn't
* receive the message yet.
* on that case the system should rollback any acks done and redeliver any messages
*/
@Test
public void testInvalidACK() throws Exception {
ActiveMQServer server = createServer(false);
server.start();
ServerLocator locator = createInVMNonHALocator().setAckBatchSize(0).setBlockOnAcknowledge(true);
ClientSessionFactory cf = createSessionFactory(locator);
int numMessages = 100;
ClientSession sessionConsumer = cf.createSession(true, true, 0);
sessionConsumer.start();
sessionConsumer.createQueue(addressA, queueA, true);
ClientConsumer consumer = sessionConsumer.createConsumer(queueA);
// sending message
{
ClientSession sendSession = cf.createSession(false, true, true);
ClientProducer cp = sendSession.createProducer(addressA);
for (int i = 0; i < numMessages; i++) {
ClientMessage msg = sendSession.createMessage(true);
msg.putIntProperty("seq", i);
cp.send(msg);
}
sendSession.close();
}
{
ClientMessage msg = consumer.receive(5000);
// need to way some time before all the possible references are sent to the consumer
// as we need to guarantee the order on cancellation on this test
Thread.sleep(1000);
try {
// pretending to be an unbehaved client doing an invalid ack right after failover
((ClientSessionInternal) sessionConsumer).acknowledge(new FakeConsumerWithID(0), new FakeMessageWithID(12343));
fail("supposed to throw an exception here");
} catch (Exception e) {
}
try {
// pretending to be an unbehaved client doing an invalid ack right after failover
((ClientSessionInternal) sessionConsumer).acknowledge(new FakeConsumerWithID(3), new FakeMessageWithID(12343));
fail("supposed to throw an exception here");
} catch (Exception e) {
e.printStackTrace();
}
consumer.close();
consumer = sessionConsumer.createConsumer(queueA);
for (int i = 0; i < numMessages; i++) {
msg = consumer.receive(5000);
assertNotNull(msg);
assertEquals(i, msg.getIntProperty("seq").intValue());
msg.acknowledge();
}
}
}
@Test
public void testAsyncConsumerAckLastMessageOnly() throws Exception {
ActiveMQServer server = createServer(false);
server.start();
ServerLocator locator = createInVMNonHALocator().setBlockOnAcknowledge(true).setAckBatchSize(0);
ClientSessionFactory cf = createSessionFactory(locator);
ClientSession sendSession = cf.createSession(false, true, true);
final ClientSession session = cf.createSession(false, true, true);
sendSession.createQueue(addressA, queueA, false);
ClientProducer cp = sendSession.createProducer(addressA);
ClientConsumer cc = session.createConsumer(queueA);
int numMessages = 100;
for (int i = 0; i < numMessages; i++) {
cp.send(sendSession.createMessage(false));
}
final CountDownLatch latch = new CountDownLatch(numMessages);
session.start();
cc.setMessageHandler(new MessageHandler() {
@Override
public void onMessage(final ClientMessage message) {
if (latch.getCount() == 1) {
try {
message.acknowledge();
} catch (ActiveMQException e) {
try {
session.close();
} catch (ActiveMQException e1) {
e1.printStackTrace();
}
}
}
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Queue q = (Queue) server.getPostOffice().getBinding(queueA).getBindable();
Assert.assertEquals(0, q.getDeliveringCount());
sendSession.close();
session.close();
}
class FakeConsumerWithID implements ClientConsumer {
final long id;
FakeConsumerWithID(long id) {
this.id = id;
}
@Override
public ConsumerContext getConsumerContext() {
return new ActiveMQConsumerContext(this.id);
}
@Override
public ClientMessage receive() throws ActiveMQException {
return null;
}
@Override
public ClientMessage receive(long timeout) throws ActiveMQException {
return null;
}
@Override
public ClientMessage receiveImmediate() throws ActiveMQException {
return null;
}
@Override
public MessageHandler getMessageHandler() throws ActiveMQException {
return null;
}
@Override
public FakeConsumerWithID setMessageHandler(MessageHandler handler) throws ActiveMQException {
return this;
}
@Override
public void close() throws ActiveMQException {
}
@Override
public boolean isClosed() {
return false;
}
@Override
public Exception getLastException() {
return null;
}
}
class FakeMessageWithID extends RefCountMessage {
final long id;
@Override
public SimpleString getReplyTo() {
return null;
}
@Override
public Message setReplyTo(SimpleString address) {
return null;
}
@Override
public Object removeAnnotation(SimpleString key) {
return null;
}
@Override
public Object getAnnotation(SimpleString key) {
return null;
}
@Override
public int getPersistSize() {
return 0;
}
@Override
public void persist(ActiveMQBuffer targetRecord) {
}
@Override
public Persister<Message> getPersister() {
return null;
}
@Override
public void reloadPersistence(ActiveMQBuffer record) {
}
@Override
public Long getScheduledDeliveryTime() {
return null;
}
@Override
public ICoreMessage toCore() {
return null;
}
@Override
public void receiveBuffer(ByteBuf buffer) {
}
@Override
public void sendBuffer(ByteBuf buffer, int count) {
}
@Override
public Message setUserID(Object userID) {
return null;
}
@Override
public void messageChanged() {
}
@Override
public Message copy() {
return null;
}
@Override
public Message copy(long newID) {
return null;
}
@Override
public Message setMessageID(long id) {
return null;
}
@Override
public int getRefCount() {
return 0;
}
@Override
public int incrementRefCount() throws Exception {
return 0;
}
@Override
public int decrementRefCount() throws Exception {
return 0;
}
@Override
public int incrementDurableRefCount() {
return 0;
}
@Override
public int decrementDurableRefCount() {
return 0;
}
@Override
public int getMemoryEstimate() {
return 0;
}
FakeMessageWithID(final long id) {
this.id = id;
}
@Override
public long getMessageID() {
return id;
}
@Override
public UUID getUserID() {
return null;
}
@Override
public String getAddress() {
return null;
}
@Override
public SimpleString getAddressSimpleString() {
return null;
}
@Override
public Message setBuffer(ByteBuf buffer) {
return null;
}
@Override
public ByteBuf getBuffer() {
return null;
}
@Override
public Message setAddress(String address) {
return null;
}
@Override
public Message setAddress(SimpleString address) {
return null;
}
@Override
public boolean isDurable() {
return false;
}
@Override
public FakeMessageWithID setDurable(boolean durable) {
return this;
}
@Override
public long getExpiration() {
return 0;
}
@Override
public boolean isExpired() {
return false;
}
@Override
public FakeMessageWithID setExpiration(long expiration) {
return this;
}
@Override
public long getTimestamp() {
return 0;
}
@Override
public FakeMessageWithID setTimestamp(long timestamp) {
return this;
}
@Override
public byte getPriority() {
return 0;
}
@Override
public FakeMessageWithID setPriority(byte priority) {
return this;
}
@Override
public int getEncodeSize() {
return 0;
}
@Override
public boolean isLargeMessage() {
return false;
}
@Override
public Message putBooleanProperty(SimpleString key, boolean value) {
return null;
}
@Override
public Message putBooleanProperty(String key, boolean value) {
return null;
}
@Override
public Message putByteProperty(SimpleString key, byte value) {
return null;
}
@Override
public Message putByteProperty(String key, byte value) {
return null;
}
@Override
public Message putBytesProperty(SimpleString key, byte[] value) {
return null;
}
@Override
public Message putBytesProperty(String key, byte[] value) {
return null;
}
@Override
public Message putShortProperty(SimpleString key, short value) {
return null;
}
@Override
public Message putShortProperty(String key, short value) {
return null;
}
@Override
public Message putCharProperty(SimpleString key, char value) {
return null;
}
@Override
public Message putCharProperty(String key, char value) {
return null;
}
@Override
public Message putIntProperty(SimpleString key, int value) {
return null;
}
@Override
public Message putIntProperty(String key, int value) {
return null;
}
@Override
public Message putLongProperty(SimpleString key, long value) {
return null;
}
@Override
public Message putLongProperty(String key, long value) {
return null;
}
@Override
public Message putFloatProperty(SimpleString key, float value) {
return null;
}
@Override
public Message putFloatProperty(String key, float value) {
return null;
}
@Override
public Message putDoubleProperty(SimpleString key, double value) {
return null;
}
@Override
public Message putDoubleProperty(String key, double value) {
return null;
}
@Override
public Message putStringProperty(SimpleString key, SimpleString value) {
return null;
}
@Override
public Message putStringProperty(String key, String value) {
return null;
}
@Override
public Message putObjectProperty(SimpleString key, Object value) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Message putObjectProperty(String key, Object value) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Object removeProperty(SimpleString key) {
return null;
}
@Override
public Object removeProperty(String key) {
return null;
}
@Override
public boolean containsProperty(SimpleString key) {
return false;
}
@Override
public boolean containsProperty(String key) {
return false;
}
@Override
public Boolean getBooleanProperty(SimpleString key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Boolean getBooleanProperty(String key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Byte getByteProperty(SimpleString key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Byte getByteProperty(String key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Double getDoubleProperty(SimpleString key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Double getDoubleProperty(String key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Integer getIntProperty(SimpleString key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Integer getIntProperty(String key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Long getLongProperty(SimpleString key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Long getLongProperty(String key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Object getObjectProperty(SimpleString key) {
return null;
}
@Override
public Object getObjectProperty(String key) {
return null;
}
@Override
public Short getShortProperty(SimpleString key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Short getShortProperty(String key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Float getFloatProperty(SimpleString key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public Float getFloatProperty(String key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public String getStringProperty(SimpleString key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public String getStringProperty(String key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public SimpleString getSimpleStringProperty(SimpleString key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public SimpleString getSimpleStringProperty(String key) throws ActiveMQPropertyConversionException {
return null;
}
@Override
public byte[] getBytesProperty(SimpleString key) throws ActiveMQPropertyConversionException {
return new byte[0];
}
@Override
public byte[] getBytesProperty(String key) throws ActiveMQPropertyConversionException {
return new byte[0];
}
@Override
public Set<SimpleString> getPropertyNames() {
return null;
}
@Override
public Map<String, Object> toMap() {
return null;
}
@Override
public Map<String, Object> toPropertyMap() {
return null;
}
}
}