/**
* 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.
*
* 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.
*/
/**
* 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.broker.region;
import javax.jms.InvalidSelectorException;
import javax.management.ObjectName;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.TestCase;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.ProducerBrokerExchange;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ConsumerInfo;
import org.apache.activemq.command.Message;
import org.apache.activemq.command.MessageAck;
import org.apache.activemq.command.MessageDispatchNotification;
import org.apache.activemq.command.MessageId;
import org.apache.activemq.command.MessagePull;
import org.apache.activemq.command.ProducerInfo;
import org.apache.activemq.command.Response;
import org.apache.activemq.filter.MessageEvaluationContext;
import org.apache.activemq.state.ProducerState;
import org.apache.activemq.store.MessageStore;
import org.apache.activemq.thread.TaskRunnerFactory;
public class SubscriptionAddRemoveQueueTest extends TestCase {
Queue queue;
ConsumerInfo info = new ConsumerInfo();
List<SimpleImmediateDispatchSubscription> subs = new ArrayList<>();
ConnectionContext context = new ConnectionContext();
ProducerBrokerExchange producerBrokerExchange = new ProducerBrokerExchange();
ProducerInfo producerInfo = new ProducerInfo();
ProducerState producerState = new ProducerState(producerInfo);
ActiveMQDestination destination = new ActiveMQQueue("TEST");
int numSubscriptions = 1000;
boolean working = true;
int senders = 20;
@Override
public void setUp() throws Exception {
BrokerService brokerService = new BrokerService();
brokerService.start();
DestinationStatistics parentStats = new DestinationStatistics();
parentStats.setEnabled(true);
TaskRunnerFactory taskFactory = new TaskRunnerFactory();
MessageStore store = null;
info.setDestination(destination);
info.setPrefetchSize(100);
producerBrokerExchange.setProducerState(producerState);
producerBrokerExchange.setConnectionContext(context);
queue = new Queue(brokerService, destination, store, parentStats, taskFactory);
queue.initialize();
}
public void testNoDispatchToRemovedConsumers() throws Exception {
final AtomicInteger producerId = new AtomicInteger();
Runnable sender = new Runnable() {
@Override
public void run() {
AtomicInteger id = new AtomicInteger();
int producerIdAndIncrement = producerId.getAndIncrement();
while (working) {
try {
Message msg = new ActiveMQMessage();
msg.setDestination(destination);
msg.setMessageId(new MessageId(producerIdAndIncrement + ":0:" + id.getAndIncrement()));
queue.send(producerBrokerExchange, msg);
} catch (Exception e) {
e.printStackTrace();
fail("unexpected exception in sendMessage, ex:" + e);
}
}
}
};
Runnable subRemover = new Runnable() {
@Override
public void run() {
for (Subscription sub : subs) {
try {
queue.removeSubscription(context, sub, 0);
} catch (Exception e) {
e.printStackTrace();
fail("unexpected exception in removeSubscription, ex:" + e);
}
}
}
};
for (int i = 0; i < numSubscriptions; i++) {
SimpleImmediateDispatchSubscription sub = new SimpleImmediateDispatchSubscription();
subs.add(sub);
queue.addSubscription(context, sub);
}
assertEquals("there are X subscriptions", numSubscriptions, queue.getDestinationStatistics().getConsumers().getCount());
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < senders; i++) {
executor.submit(sender);
}
Thread.sleep(1000);
for (SimpleImmediateDispatchSubscription sub : subs) {
assertTrue("There are some locked messages in the subscription", hasSomeLocks(sub.dispatched));
}
Future<?> result = executor.submit(subRemover);
result.get();
working = false;
assertEquals("there are no subscriptions", 0, queue.getDestinationStatistics().getConsumers().getCount());
for (SimpleImmediateDispatchSubscription sub : subs) {
assertTrue("There are no locked messages in any removed subscriptions", !hasSomeLocks(sub.dispatched));
}
}
private boolean hasSomeLocks(List<MessageReference> dispatched) {
boolean hasLock = false;
for (MessageReference mr : dispatched) {
QueueMessageReference qmr = (QueueMessageReference) mr;
if (qmr.getLockOwner() != null) {
hasLock = true;
break;
}
}
return hasLock;
}
public class SimpleImmediateDispatchSubscription implements Subscription, LockOwner {
private SubscriptionStatistics subscriptionStatistics = new SubscriptionStatistics();
List<MessageReference> dispatched = Collections.synchronizedList(new ArrayList<MessageReference>());
@Override
public long getPendingMessageSize() {
return 0;
}
@Override
public void acknowledge(ConnectionContext context, MessageAck ack) throws Exception {
}
@Override
public void add(MessageReference node) throws Exception {
// immediate dispatch
QueueMessageReference qmr = (QueueMessageReference) node;
qmr.lock(this);
dispatched.add(qmr);
}
@Override
public ConnectionContext getContext() {
return null;
}
@Override
public int getCursorMemoryHighWaterMark() {
return 0;
}
@Override
public void setCursorMemoryHighWaterMark(int cursorMemoryHighWaterMark) {
}
@Override
public boolean isSlowConsumer() {
return false;
}
@Override
public void unmatched(MessageReference node) throws IOException {
}
@Override
public long getTimeOfLastMessageAck() {
return 0;
}
@Override
public long getConsumedCount() {
return 0;
}
@Override
public void incrementConsumedCount() {
}
@Override
public void resetConsumedCount() {
}
@Override
public void add(ConnectionContext context, Destination destination) throws Exception {
}
@Override
public void destroy() {
}
@Override
public void gc() {
}
@Override
public ConsumerInfo getConsumerInfo() {
return info;
}
@Override
public long getDequeueCounter() {
return 0;
}
@Override
public long getDispatchedCounter() {
return 0;
}
@Override
public int getDispatchedQueueSize() {
return 0;
}
@Override
public long getEnqueueCounter() {
return 0;
}
@Override
public int getInFlightSize() {
return 0;
}
@Override
public int getInFlightUsage() {
return 0;
}
@Override
public ObjectName getObjectName() {
return null;
}
@Override
public int getPendingQueueSize() {
return 0;
}
@Override
public int getPrefetchSize() {
return 0;
}
@Override
public String getSelector() {
return null;
}
@Override
public boolean isBrowser() {
return false;
}
@Override
public boolean isFull() {
return false;
}
@Override
public boolean isHighWaterMark() {
return false;
}
@Override
public boolean isLowWaterMark() {
return false;
}
@Override
public boolean isRecoveryRequired() {
return false;
}
public boolean isSlave() {
return false;
}
@Override
public boolean matches(MessageReference node, MessageEvaluationContext context) throws IOException {
return true;
}
@Override
public boolean matches(ActiveMQDestination destination) {
return false;
}
@Override
public void processMessageDispatchNotification(MessageDispatchNotification mdn) throws Exception {
}
@Override
public Response pullMessage(ConnectionContext context, MessagePull pull) throws Exception {
return null;
}
@Override
public boolean isWildcard() {
return false;
}
@Override
public List<MessageReference> remove(ConnectionContext context, Destination destination) throws Exception {
return new ArrayList<>(dispatched);
}
@Override
public void setObjectName(ObjectName objectName) {
}
@Override
public void setSelector(String selector) throws InvalidSelectorException, UnsupportedOperationException {
}
@Override
public void updateConsumerPrefetch(int newPrefetch) {
}
@Override
public boolean addRecoveredMessage(ConnectionContext context, MessageReference message) throws Exception {
return false;
}
@Override
public ActiveMQDestination getActiveMQDestination() {
return null;
}
@Override
public int getLockPriority() {
return 0;
}
@Override
public boolean isLockExclusive() {
return false;
}
public void addDestination(Destination destination) {
}
public void removeDestination(Destination destination) {
}
@Override
public int countBeforeFull() {
return 10;
}
@Override
public SubscriptionStatistics getSubscriptionStatistics() {
return subscriptionStatistics;
}
@Override
public long getInFlightMessageSize() {
return subscriptionStatistics.getInflightMessageSize().getTotalSize();
}
}
}