Makes the locking in RegionBroker a bit more fine grained.  We hold a lock only for a short time and allow destination adds that aren't on the same destination to continue on concurrently.  

git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@1479963 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Timothy A. Bish 2013-05-07 16:13:18 +00:00
parent 866440dfb2
commit d7aaca5034
2 changed files with 236 additions and 66 deletions

View File

@ -43,7 +43,23 @@ import org.apache.activemq.broker.ProducerBrokerExchange;
import org.apache.activemq.broker.TransportConnector;
import org.apache.activemq.broker.region.policy.DeadLetterStrategy;
import org.apache.activemq.broker.region.policy.PolicyMap;
import org.apache.activemq.command.*;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.BrokerId;
import org.apache.activemq.command.BrokerInfo;
import org.apache.activemq.command.ConnectionId;
import org.apache.activemq.command.ConnectionInfo;
import org.apache.activemq.command.ConsumerControl;
import org.apache.activemq.command.ConsumerInfo;
import org.apache.activemq.command.DestinationInfo;
import org.apache.activemq.command.Message;
import org.apache.activemq.command.MessageAck;
import org.apache.activemq.command.MessageDispatch;
import org.apache.activemq.command.MessageDispatchNotification;
import org.apache.activemq.command.MessagePull;
import org.apache.activemq.command.ProducerInfo;
import org.apache.activemq.command.RemoveSubscriptionInfo;
import org.apache.activemq.command.Response;
import org.apache.activemq.command.TransactionId;
import org.apache.activemq.state.ConnectionState;
import org.apache.activemq.store.PListStore;
import org.apache.activemq.thread.Scheduler;
@ -59,8 +75,6 @@ import org.slf4j.LoggerFactory;
/**
* Routes Broker operations to the correct messaging regions for processing.
*
*
*/
public class RegionBroker extends EmptyBroker {
public static final String ORIGINAL_EXPIRATION = "originalExpiration";
@ -80,6 +94,7 @@ public class RegionBroker extends EmptyBroker {
private boolean keepDurableSubsActive;
private final CopyOnWriteArrayList<Connection> connections = new CopyOnWriteArrayList<Connection>();
private final Map<ActiveMQDestination, ActiveMQDestination> destinationGate = new HashMap<ActiveMQDestination, ActiveMQDestination>();
private final Map<ActiveMQDestination, Destination> destinations = new ConcurrentHashMap<ActiveMQDestination, Destination>();
private final Map<BrokerId, BrokerInfo> brokerInfos = new HashMap<BrokerId, BrokerInfo>();
@ -102,9 +117,9 @@ public class RegionBroker extends EmptyBroker {
};
public RegionBroker(BrokerService brokerService, TaskRunnerFactory taskRunnerFactory, SystemUsage memoryManager, DestinationFactory destinationFactory,
DestinationInterceptor destinationInterceptor,Scheduler scheduler,ThreadPoolExecutor executor) throws IOException {
DestinationInterceptor destinationInterceptor, Scheduler scheduler, ThreadPoolExecutor executor) throws IOException {
this.brokerService = brokerService;
this.executor=executor;
this.executor = executor;
this.scheduler = scheduler;
if (destinationFactory == null) {
throw new IllegalArgumentException("null destinationFactory");
@ -126,7 +141,7 @@ public class RegionBroker extends EmptyBroker {
}
@Override
public Set <Destination> getDestinations(ActiveMQDestination destination) {
public Set<Destination> getDestinations(ActiveMQDestination destination) {
try {
return getRegion(destination).getDestinations(destination);
} catch (JMSException jmse) {
@ -216,7 +231,7 @@ public class RegionBroker extends EmptyBroker {
ConnectionContext oldContext = clientIdSet.get(clientId);
if (oldContext != null) {
throw new InvalidClientIDException("Broker: " + getBrokerName() + " - Client: " + clientId + " already connected from "
+ oldContext.getConnection().getRemoteAddress());
+ oldContext.getConnection().getRemoteAddress());
} else {
clientIdSet.put(clientId, context);
}
@ -267,21 +282,42 @@ public class RegionBroker extends EmptyBroker {
return answer;
}
synchronized (destinations) {
answer = destinations.get(destination);
if (answer != null) {
return answer;
synchronized (destinationGate) {
answer = destinations.get(destination);
if (answer != null) {
return answer;
}
if (destinationGate.get(destination) != null) {
// Guard against spurious wakeup.
while (destinationGate.containsKey(destination)) {
destinationGate.wait();
}
answer = destinations.get(destination);
if (answer != null) {
return answer;
} else {
// In case of intermediate remove or add failure
destinationGate.put(destination, destination);
}
}
}
boolean create = true;
if (destination.isTemporary())
create = createIfTemp;
answer = getRegion(destination).addDestination(context, destination, create);
try {
boolean create = true;
if (destination.isTemporary()) {
create = createIfTemp;
}
answer = getRegion(destination).addDestination(context, destination, create);
destinations.put(destination, answer);
} finally {
synchronized (destinationGate) {
destinationGate.remove(destination);
destinationGate.notifyAll();
}
}
destinations.put(destination, answer);
return answer;
}
}
@Override
@ -294,14 +330,13 @@ public class RegionBroker extends EmptyBroker {
@Override
public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
addDestination(context, info.getDestination(),true);
addDestination(context, info.getDestination(), true);
}
@Override
public void removeDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
removeDestination(context, info.getDestination(), info.getTimeout());
}
@Override
@ -384,9 +419,10 @@ public class RegionBroker extends EmptyBroker {
ActiveMQDestination destination = message.getDestination();
message.setBrokerInTime(System.currentTimeMillis());
if (producerExchange.isMutable() || producerExchange.getRegion() == null
|| (producerExchange.getRegionDestination() != null && producerExchange.getRegionDestination().isDisposed())) {
|| (producerExchange.getRegionDestination() != null && producerExchange.getRegionDestination().isDisposed())) {
// ensure the destination is registered with the RegionBroker
producerExchange.getConnectionContext().getBroker().addDestination(producerExchange.getConnectionContext(), destination, isAllowTempAutoCreationOnSend());
producerExchange.getConnectionContext().getBroker()
.addDestination(producerExchange.getConnectionContext(), destination, isAllowTempAutoCreationOnSend());
producerExchange.setRegion(getRegion(destination));
producerExchange.setRegionDestination(null);
}
@ -412,16 +448,16 @@ public class RegionBroker extends EmptyBroker {
protected Region getRegion(ActiveMQDestination destination) throws JMSException {
switch (destination.getDestinationType()) {
case ActiveMQDestination.QUEUE_TYPE:
return queueRegion;
case ActiveMQDestination.TOPIC_TYPE:
return topicRegion;
case ActiveMQDestination.TEMP_QUEUE_TYPE:
return tempQueueRegion;
case ActiveMQDestination.TEMP_TOPIC_TYPE:
return tempTopicRegion;
default:
throw createUnknownDestinationTypeException(destination);
case ActiveMQDestination.QUEUE_TYPE:
return queueRegion;
case ActiveMQDestination.TOPIC_TYPE:
return topicRegion;
case ActiveMQDestination.TEMP_QUEUE_TYPE:
return tempQueueRegion;
case ActiveMQDestination.TEMP_TOPIC_TYPE:
return tempTopicRegion;
default:
throw createUnknownDestinationTypeException(destination);
}
}
@ -523,7 +559,7 @@ public class RegionBroker extends EmptyBroker {
if (info != null) {
BrokerInfo existing = brokerInfos.get(info.getBrokerId());
if (existing != null && existing.decrementRefCount() == 0) {
brokerInfos.remove(info.getBrokerId());
brokerInfos.remove(info.getBrokerId());
}
if (LOG.isDebugEnabled()) {
LOG.debug(getBrokerName() + " removeBroker:" + info.getBrokerName() + " brokerInfo size : " + brokerInfos.size());
@ -551,7 +587,7 @@ public class RegionBroker extends EmptyBroker {
message.setBrokerOutTime(endTime);
if (getBrokerService().isEnableStatistics()) {
long totalTime = endTime - message.getBrokerInTime();
((Destination)message.getRegionDestination()).getDestinationStatistics().getProcessTime().addTime(totalTime);
((Destination) message.getRegionDestination()).getDestinationStatistics().getProcessTime().addTime(totalTime);
}
}
}
@ -589,7 +625,7 @@ public class RegionBroker extends EmptyBroker {
public void setKeepDurableSubsActive(boolean keepDurableSubsActive) {
this.keepDurableSubsActive = keepDurableSubsActive;
((TopicRegion)topicRegion).setKeepDurableSubsActive(keepDurableSubsActive);
((TopicRegion) topicRegion).setKeepDurableSubsActive(keepDurableSubsActive);
}
public DestinationInterceptor getDestinationInterceptor() {
@ -647,16 +683,15 @@ public class RegionBroker extends EmptyBroker {
}
private boolean stampAsExpired(Message message) throws IOException {
boolean stamped=false;
boolean stamped = false;
if (message.getProperty(ORIGINAL_EXPIRATION) == null) {
long expiration=message.getExpiration();
message.setProperty(ORIGINAL_EXPIRATION,new Long(expiration));
long expiration = message.getExpiration();
message.setProperty(ORIGINAL_EXPIRATION, new Long(expiration));
stamped = true;
}
return stamped;
}
@Override
public void messageExpired(ConnectionContext context, MessageReference node, Subscription subscription) {
if (LOG.isDebugEnabled()) {
@ -666,47 +701,42 @@ public class RegionBroker extends EmptyBroker {
}
@Override
public void sendToDeadLetterQueue(ConnectionContext context,
MessageReference node, Subscription subscription){
try{
if(node!=null){
Message message=node.getMessage();
if(message!=null && node.getRegionDestination()!=null){
DeadLetterStrategy deadLetterStrategy=((Destination)node
.getRegionDestination()).getDeadLetterStrategy();
if(deadLetterStrategy!=null){
if(deadLetterStrategy.isSendToDeadLetterQueue(message)){
public void sendToDeadLetterQueue(ConnectionContext context, MessageReference node, Subscription subscription) {
try {
if (node != null) {
Message message = node.getMessage();
if (message != null && node.getRegionDestination() != null) {
DeadLetterStrategy deadLetterStrategy = ((Destination) node.getRegionDestination()).getDeadLetterStrategy();
if (deadLetterStrategy != null) {
if (deadLetterStrategy.isSendToDeadLetterQueue(message)) {
// message may be inflight to other subscriptions so do not modify
message = message.copy();
stampAsExpired(message);
message.setExpiration(0);
if(!message.isPersistent()){
if (!message.isPersistent()) {
message.setPersistent(true);
message.setProperty("originalDeliveryMode",
"NON_PERSISTENT");
message.setProperty("originalDeliveryMode", "NON_PERSISTENT");
}
// The original destination and transaction id do
// not get filled when the message is first sent,
// it is only populated if the message is routed to
// another destination like the DLQ
ActiveMQDestination deadLetterDestination=deadLetterStrategy
.getDeadLetterQueueFor(message, subscription);
if (context.getBroker()==null) {
ActiveMQDestination deadLetterDestination = deadLetterStrategy.getDeadLetterQueueFor(message, subscription);
if (context.getBroker() == null) {
context.setBroker(getRoot());
}
BrokerSupport.resendNoCopy(context,message,
deadLetterDestination);
BrokerSupport.resendNoCopy(context, message, deadLetterDestination);
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Dead Letter message with no DLQ strategy in place, message id: "
+ message.getMessageId() + ", destination: " + message.getDestination());
LOG.debug("Dead Letter message with no DLQ strategy in place, message id: " + message.getMessageId() + ", destination: "
+ message.getDestination());
}
}
}
}
}catch(Exception e){
LOG.warn("Caught an exception sending to DLQ: "+node,e);
} catch (Exception e) {
LOG.warn("Caught an exception sending to DLQ: " + node, e);
}
}
@ -725,12 +755,11 @@ public class RegionBroker extends EmptyBroker {
*/
@Override
public long getBrokerSequenceId() {
synchronized(sequenceGenerator) {
synchronized (sequenceGenerator) {
return sequenceGenerator.getNextSequenceId();
}
}
@Override
public Scheduler getScheduler() {
return this.scheduler;
@ -747,7 +776,7 @@ public class RegionBroker extends EmptyBroker {
try {
getRegion(destination).processConsumerControl(consumerExchange, control);
} catch (JMSException jmse) {
LOG.warn("unmatched destination: " + destination + ", in consumerControl: " + control);
LOG.warn("unmatched destination: " + destination + ", in consumerControl: " + control);
}
}
@ -801,8 +830,7 @@ public class RegionBroker extends EmptyBroker {
if (dest instanceof BaseDestination) {
log = ((BaseDestination) dest).getLog();
}
log.info(dest.getName() + " Inactive for longer than " +
dest.getInactiveTimoutBeforeGC() + " ms - removing ...");
log.info(dest.getName() + " Inactive for longer than " + dest.getInactiveTimoutBeforeGC() + " ms - removing ...");
try {
getRoot().removeDestination(context, dest.getActiveMQDestination(), isAllowTempAutoCreationOnSend() ? 1 : 0);
} catch (Exception e) {

View File

@ -0,0 +1,142 @@
/**
* 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.bugs;
import static org.junit.Assert.assertTrue;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.region.policy.DeadLetterStrategy;
import org.apache.activemq.broker.region.policy.IndividualDeadLetterStrategy;
import org.apache.activemq.broker.region.policy.PolicyEntry;
import org.apache.activemq.broker.region.policy.PolicyMap;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class AMQ4513Test {
private BrokerService brokerService;
private String connectionUri;
@Before
public void setup() throws Exception {
brokerService = new BrokerService();
connectionUri = brokerService.addConnector("tcp://localhost:0").getPublishableConnectString();
// Configure Dead Letter Strategy
DeadLetterStrategy strategy = new IndividualDeadLetterStrategy();
((IndividualDeadLetterStrategy)strategy).setUseQueueForQueueMessages(true);
((IndividualDeadLetterStrategy)strategy).setQueuePrefix("DLQ.");
strategy.setProcessNonPersistent(false);
strategy.setProcessExpired(false);
// Add policy and individual DLQ strategy
PolicyEntry policy = new PolicyEntry();
policy.setTimeBeforeDispatchStarts(3000);
policy.setDeadLetterStrategy(strategy);
PolicyMap pMap = new PolicyMap();
pMap.setDefaultEntry(policy);
brokerService.setDestinationPolicy(pMap);
brokerService.setPersistent(false);
brokerService.start();
}
@After
public void stop() throws Exception {
brokerService.stop();
}
@Test(timeout=360000)
public void test() throws Exception {
final ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(connectionUri);
ExecutorService service = Executors.newFixedThreadPool(25);
final Random ripple = new Random(System.currentTimeMillis());
for (int i = 0; i < 1000; ++i) {
service.execute(new Runnable() {
@Override
public void run() {
try {
ActiveMQConnection connection = (ActiveMQConnection) cf.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createTemporaryQueue();
session.createProducer(destination);
connection.close();
TimeUnit.MILLISECONDS.sleep(ripple.nextInt(20));
} catch (Exception e) {
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
try {
ActiveMQConnection connection = (ActiveMQConnection) cf.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createTemporaryQueue();
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
producer.setTimeToLive(400);
producer.send(session.createTextMessage());
producer.send(session.createTextMessage());
TimeUnit.MILLISECONDS.sleep(500);
connection.close();
} catch (Exception e) {
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
try {
ActiveMQConnection connection = (ActiveMQConnection) cf.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createTemporaryQueue();
session.createProducer(destination);
connection.close();
TimeUnit.MILLISECONDS.sleep(ripple.nextInt(20));
} catch (Exception e) {
}
}
});
}
service.shutdown();
assertTrue(service.awaitTermination(5, TimeUnit.MINUTES));
}
}