/*
* 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.extras.byteman;
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.ServerLocator;
import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryImpl;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.jboss.byteman.contrib.bmunit.BMRule;
import org.jboss.byteman.contrib.bmunit.BMRules;
import org.jboss.byteman.contrib.bmunit.BMUnitRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(BMUnitRunner.class)
public class OrphanedConsumerTest extends ActiveMQTestBase {
private static boolean conditionActive = true;
public static final boolean isConditionActive() {
return conditionActive;
}
public static final void setConditionActive(boolean _conditionActive) {
conditionActive = _conditionActive;
}
public static void throwException() throws Exception {
throw new InterruptedException("nice.. I interrupted this!");
}
private ActiveMQServer server;
private ServerLocator locator;
static ActiveMQServer staticServer;
/**
* {@link #leavingCloseOnTestCountersWhileClosing()} will set this in case of any issues.
* the test must then validate for this being null
*/
static AssertionError verification;
/**
* This static method is an entry point for the byteman rules on {@link #testOrphanedConsumers()}
*/
public static void leavingCloseOnTestCountersWhileClosing() {
if (staticServer.getSessions().size() == 0) {
verification = new AssertionError("The session was closed before the consumers, this may cause issues on management leaving Orphaned Consumers!");
}
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
setConditionActive(true);
/** I'm using the internal method here because closing
* this locator on tear down would hang.
* as we are tweaking with the internal state and making it fail */
locator = internalCreateNonHALocator(true);
}
@Override
@After
public void tearDown() throws Exception {
super.tearDown();
setConditionActive(false);
staticServer = null;
}
/**
* This is like being two tests in one:
* I - validating that any exception during the close wouldn't stop connection from being closed
* II - validating that the connection is only removed at the end of the process and you wouldn't see
* inconsistencies on management
*
* @throws Exception
*/
@Test
@BMRules(
rules = {@BMRule(
name = "closeExit",
targetClass = "org.apache.activemq.artemis.core.server.impl.ServerConsumerImpl",
targetMethod = "close",
targetLocation = "AT EXIT",
condition = "org.apache.activemq.artemis.tests.extras.byteman.OrphanedConsumerTest.isConditionActive()",
action = "System.out.println(\"throwing stuff\");throw new InterruptedException()"), @BMRule(
name = "closeEnter",
targetClass = "org.apache.activemq.artemis.core.server.impl.ServerConsumerImpl",
targetMethod = "close",
targetLocation = "ENTRY",
condition = "org.apache.activemq.artemis.tests.extras.byteman.OrphanedConsumerTest.isConditionActive()",
action = "org.apache.activemq.artemis.tests.extras.byteman.OrphanedConsumerTest.leavingCloseOnTestCountersWhileClosing()")})
public void testOrphanedConsumers() throws Exception {
internalTestOrphanedConsumers(false);
}
/**
* This is like being two tests in one:
* I - validating that any exception during the close wouldn't stop connection from being closed
* II - validating that the connection is only removed at the end of the process and you wouldn't see
* inconsistencies on management
*
* @throws Exception
*/
@Test
@BMRules(
rules = {@BMRule(
name = "closeExit",
targetClass = "org.apache.activemq.artemis.core.server.impl.ServerConsumerImpl",
targetMethod = "close",
targetLocation = "AT EXIT",
condition = "org.apache.activemq.artemis.tests.extras.byteman.OrphanedConsumerTest.isConditionActive()",
action = "System.out.println(\"throwing stuff\");throw new InterruptedException()"), @BMRule(
name = "closeEnter",
targetClass = "org.apache.activemq.artemis.core.server.impl.ServerConsumerImpl",
targetMethod = "close",
targetLocation = "ENTRY",
condition = "org.apache.activemq.artemis.tests.extras.byteman.OrphanedConsumerTest.isConditionActive()",
action = "org.apache.activemq.artemis.tests.extras.byteman.OrphanedConsumerTest.leavingCloseOnTestCountersWhileClosing()")})
public void testOrphanedConsumersByManagement() throws Exception {
internalTestOrphanedConsumers(true);
}
/**
* @param useManagement true = it will use a management operation to make the connection failure, false through ping
* @throws Exception
*/
private void internalTestOrphanedConsumers(boolean useManagement) throws Exception {
final int NUMBER_OF_MESSAGES = 2;
server = createServer(true, true);
server.start();
staticServer = server;
// We are not interested on consumer-window-size on this test
// We want that every message is delivered
// as we asserting for number of consumers available and round-robin on delivery
locator.setConsumerWindowSize(-1).setBlockOnNonDurableSend(false).setBlockOnDurableSend(false).setBlockOnAcknowledge(true).setConnectionTTL(1000).setClientFailureCheckPeriod(100).setReconnectAttempts(0);
ClientSessionFactoryImpl sf = (ClientSessionFactoryImpl) createSessionFactory(locator);
ClientSession session = sf.createSession(true, true, 0);
session.createQueue("queue", "queue1", true);
session.createQueue("queue", "queue2", true);
ClientProducer prod = session.createProducer("queue");
ClientConsumer consumer = session.createConsumer("queue1");
ClientConsumer consumer2 = session.createConsumer("queue2");
Queue queue1 = server.locateQueue(new SimpleString("queue1"));
Queue queue2 = server.locateQueue(new SimpleString("queue2"));
session.start();
if (!useManagement) {
sf.stopPingingAfterOne();
for (long timeout = System.currentTimeMillis() + 6000; timeout > System.currentTimeMillis() && server.getConnectionCount() != 0; ) {
Thread.sleep(100);
}
// an extra second to avoid races of something closing the session while we are asserting it
Thread.sleep(1000);
} else {
server.getActiveMQServerControl().closeConnectionsForAddress("127.0.0.1");
}
if (verification != null) {
throw verification;
}
assertEquals(0, queue1.getConsumerCount());
assertEquals(0, queue2.getConsumerCount());
setConditionActive(false);
locator = internalCreateNonHALocator(true).setBlockOnNonDurableSend(false).setBlockOnDurableSend(false).setBlockOnAcknowledge(true).setReconnectAttempts(0).setConsumerWindowSize(-1);
sf = (ClientSessionFactoryImpl) locator.createSessionFactory();
session = sf.createSession(true, true, 0);
session.start();
prod = session.createProducer("queue");
for (int i = 0; i < NUMBER_OF_MESSAGES; i++) {
ClientMessage message = session.createMessage(true);
message.putIntProperty("i", i);
prod.send(message);
}
consumer = session.createConsumer("queue1");
consumer2 = session.createConsumer("queue2");
for (int i = 0; i < NUMBER_OF_MESSAGES; i++) {
assertNotNull(consumer.receive(5000));
assertNotNull(consumer2.receive(5000));
}
session.close();
}
}