001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.broker.region; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.LinkedList; 022import java.util.List; 023import java.util.Map; 024import java.util.concurrent.CancellationException; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.concurrent.ConcurrentMap; 027import java.util.concurrent.CopyOnWriteArrayList; 028import java.util.concurrent.Future; 029import java.util.concurrent.atomic.AtomicBoolean; 030import java.util.concurrent.locks.ReentrantReadWriteLock; 031 032import org.apache.activemq.advisory.AdvisorySupport; 033import org.apache.activemq.broker.BrokerService; 034import org.apache.activemq.broker.ConnectionContext; 035import org.apache.activemq.broker.ProducerBrokerExchange; 036import org.apache.activemq.broker.region.policy.DispatchPolicy; 037import org.apache.activemq.broker.region.policy.LastImageSubscriptionRecoveryPolicy; 038import org.apache.activemq.broker.region.policy.RetainedMessageSubscriptionRecoveryPolicy; 039import org.apache.activemq.broker.region.policy.SimpleDispatchPolicy; 040import org.apache.activemq.broker.region.policy.SubscriptionRecoveryPolicy; 041import org.apache.activemq.broker.util.InsertionCountList; 042import org.apache.activemq.command.ActiveMQDestination; 043import org.apache.activemq.command.ExceptionResponse; 044import org.apache.activemq.command.Message; 045import org.apache.activemq.command.MessageAck; 046import org.apache.activemq.command.MessageId; 047import org.apache.activemq.command.ProducerAck; 048import org.apache.activemq.command.ProducerInfo; 049import org.apache.activemq.command.Response; 050import org.apache.activemq.command.SubscriptionInfo; 051import org.apache.activemq.filter.MessageEvaluationContext; 052import org.apache.activemq.filter.NonCachedMessageEvaluationContext; 053import org.apache.activemq.store.MessageRecoveryListener; 054import org.apache.activemq.store.TopicMessageStore; 055import org.apache.activemq.thread.Task; 056import org.apache.activemq.thread.TaskRunner; 057import org.apache.activemq.thread.TaskRunnerFactory; 058import org.apache.activemq.transaction.Synchronization; 059import org.apache.activemq.util.SubscriptionKey; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063import javax.jms.JMSException; 064 065import static org.apache.activemq.transaction.Transaction.IN_USE_STATE; 066 067/** 068 * The Topic is a destination that sends a copy of a message to every active 069 * Subscription registered. 070 */ 071public class Topic extends BaseDestination implements Task { 072 protected static final Logger LOG = LoggerFactory.getLogger(Topic.class); 073 private final TopicMessageStore topicStore; 074 protected final CopyOnWriteArrayList<Subscription> consumers = new CopyOnWriteArrayList<Subscription>(); 075 private final ReentrantReadWriteLock dispatchLock = new ReentrantReadWriteLock(); 076 private DispatchPolicy dispatchPolicy = new SimpleDispatchPolicy(); 077 private SubscriptionRecoveryPolicy subscriptionRecoveryPolicy; 078 private final ConcurrentMap<SubscriptionKey, DurableTopicSubscription> durableSubscribers = new ConcurrentHashMap<SubscriptionKey, DurableTopicSubscription>(); 079 private final TaskRunner taskRunner; 080 private final TaskRunnerFactory taskRunnerFactor; 081 private final LinkedList<Runnable> messagesWaitingForSpace = new LinkedList<Runnable>(); 082 private final Runnable sendMessagesWaitingForSpaceTask = new Runnable() { 083 @Override 084 public void run() { 085 try { 086 Topic.this.taskRunner.wakeup(); 087 } catch (InterruptedException e) { 088 } 089 } 090 }; 091 092 public Topic(BrokerService brokerService, ActiveMQDestination destination, TopicMessageStore store, 093 DestinationStatistics parentStats, TaskRunnerFactory taskFactory) throws Exception { 094 super(brokerService, store, destination, parentStats); 095 this.topicStore = store; 096 subscriptionRecoveryPolicy = new RetainedMessageSubscriptionRecoveryPolicy(null); 097 this.taskRunner = taskFactory.createTaskRunner(this, "Topic " + destination.getPhysicalName()); 098 this.taskRunnerFactor = taskFactory; 099 } 100 101 @Override 102 public void initialize() throws Exception { 103 super.initialize(); 104 // set non default subscription recovery policy (override policyEntries) 105 if (AdvisorySupport.isMasterBrokerAdvisoryTopic(destination)) { 106 subscriptionRecoveryPolicy = new LastImageSubscriptionRecoveryPolicy(); 107 setAlwaysRetroactive(true); 108 } 109 if (store != null) { 110 // AMQ-2586: Better to leave this stat at zero than to give the user 111 // misleading metrics. 112 // int messageCount = store.getMessageCount(); 113 // destinationStatistics.getMessages().setCount(messageCount); 114 } 115 } 116 117 @Override 118 public List<Subscription> getConsumers() { 119 synchronized (consumers) { 120 return new ArrayList<Subscription>(consumers); 121 } 122 } 123 124 public boolean lock(MessageReference node, LockOwner sub) { 125 return true; 126 } 127 128 @Override 129 public void addSubscription(ConnectionContext context, final Subscription sub) throws Exception { 130 if (!sub.getConsumerInfo().isDurable()) { 131 132 // Do a retroactive recovery if needed. 133 if (sub.getConsumerInfo().isRetroactive() || isAlwaysRetroactive()) { 134 135 // synchronize with dispatch method so that no new messages are sent 136 // while we are recovering a subscription to avoid out of order messages. 137 dispatchLock.writeLock().lock(); 138 try { 139 boolean applyRecovery = false; 140 synchronized (consumers) { 141 if (!consumers.contains(sub)){ 142 sub.add(context, this); 143 consumers.add(sub); 144 applyRecovery=true; 145 super.addSubscription(context, sub); 146 } 147 } 148 if (applyRecovery){ 149 subscriptionRecoveryPolicy.recover(context, this, sub); 150 } 151 } finally { 152 dispatchLock.writeLock().unlock(); 153 } 154 155 } else { 156 synchronized (consumers) { 157 if (!consumers.contains(sub)){ 158 sub.add(context, this); 159 consumers.add(sub); 160 super.addSubscription(context, sub); 161 } 162 } 163 } 164 } else { 165 DurableTopicSubscription dsub = (DurableTopicSubscription) sub; 166 super.addSubscription(context, sub); 167 sub.add(context, this); 168 if(dsub.isActive()) { 169 synchronized (consumers) { 170 boolean hasSubscription = false; 171 172 if (consumers.size() == 0) { 173 hasSubscription = false; 174 } else { 175 for (Subscription currentSub : consumers) { 176 if (currentSub.getConsumerInfo().isDurable()) { 177 DurableTopicSubscription dcurrentSub = (DurableTopicSubscription) currentSub; 178 if (dcurrentSub.getSubscriptionKey().equals(dsub.getSubscriptionKey())) { 179 hasSubscription = true; 180 break; 181 } 182 } 183 } 184 } 185 186 if (!hasSubscription) { 187 consumers.add(sub); 188 } 189 } 190 } 191 durableSubscribers.put(dsub.getSubscriptionKey(), dsub); 192 } 193 } 194 195 @Override 196 public void removeSubscription(ConnectionContext context, Subscription sub, long lastDeliveredSequenceId) throws Exception { 197 if (!sub.getConsumerInfo().isDurable()) { 198 boolean removed = false; 199 synchronized (consumers) { 200 removed = consumers.remove(sub); 201 } 202 if (removed) { 203 super.removeSubscription(context, sub, lastDeliveredSequenceId); 204 } 205 } 206 sub.remove(context, this); 207 } 208 209 public void deleteSubscription(ConnectionContext context, SubscriptionKey key) throws Exception { 210 if (topicStore != null) { 211 topicStore.deleteSubscription(key.clientId, key.subscriptionName); 212 DurableTopicSubscription removed = durableSubscribers.remove(key); 213 if (removed != null) { 214 destinationStatistics.getConsumers().decrement(); 215 // deactivate and remove 216 removed.deactivate(false, 0l); 217 consumers.remove(removed); 218 } 219 } 220 } 221 222 public void activate(ConnectionContext context, final DurableTopicSubscription subscription) throws Exception { 223 // synchronize with dispatch method so that no new messages are sent 224 // while we are recovering a subscription to avoid out of order messages. 225 dispatchLock.writeLock().lock(); 226 try { 227 228 if (topicStore == null) { 229 return; 230 } 231 232 // Recover the durable subscription. 233 String clientId = subscription.getSubscriptionKey().getClientId(); 234 String subscriptionName = subscription.getSubscriptionKey().getSubscriptionName(); 235 String selector = subscription.getConsumerInfo().getSelector(); 236 SubscriptionInfo info = topicStore.lookupSubscription(clientId, subscriptionName); 237 if (info != null) { 238 // Check to see if selector changed. 239 String s1 = info.getSelector(); 240 if (s1 == null ^ selector == null || (s1 != null && !s1.equals(selector))) { 241 // Need to delete the subscription 242 topicStore.deleteSubscription(clientId, subscriptionName); 243 info = null; 244 synchronized (consumers) { 245 consumers.remove(subscription); 246 } 247 } else { 248 synchronized (consumers) { 249 if (!consumers.contains(subscription)) { 250 consumers.add(subscription); 251 } 252 } 253 } 254 } 255 256 // Do we need to create the subscription? 257 if (info == null) { 258 info = new SubscriptionInfo(); 259 info.setClientId(clientId); 260 info.setSelector(selector); 261 info.setSubscriptionName(subscriptionName); 262 info.setDestination(getActiveMQDestination()); 263 // This destination is an actual destination id. 264 info.setSubscribedDestination(subscription.getConsumerInfo().getDestination()); 265 // This destination might be a pattern 266 synchronized (consumers) { 267 consumers.add(subscription); 268 topicStore.addSubscription(info, subscription.getConsumerInfo().isRetroactive()); 269 } 270 } 271 272 final MessageEvaluationContext msgContext = new NonCachedMessageEvaluationContext(); 273 msgContext.setDestination(destination); 274 if (subscription.isRecoveryRequired()) { 275 topicStore.recoverSubscription(clientId, subscriptionName, new MessageRecoveryListener() { 276 @Override 277 public boolean recoverMessage(Message message) throws Exception { 278 message.setRegionDestination(Topic.this); 279 try { 280 msgContext.setMessageReference(message); 281 if (subscription.matches(message, msgContext)) { 282 subscription.add(message); 283 } 284 } catch (IOException e) { 285 LOG.error("Failed to recover this message {}", message, e); 286 } 287 return true; 288 } 289 290 @Override 291 public boolean recoverMessageReference(MessageId messageReference) throws Exception { 292 throw new RuntimeException("Should not be called."); 293 } 294 295 @Override 296 public boolean hasSpace() { 297 return true; 298 } 299 300 @Override 301 public boolean isDuplicate(MessageId id) { 302 return false; 303 } 304 }); 305 } 306 } finally { 307 dispatchLock.writeLock().unlock(); 308 } 309 } 310 311 public void deactivate(ConnectionContext context, DurableTopicSubscription sub, List<MessageReference> dispatched) throws Exception { 312 synchronized (consumers) { 313 consumers.remove(sub); 314 } 315 sub.remove(context, this, dispatched); 316 } 317 318 public void recoverRetroactiveMessages(ConnectionContext context, Subscription subscription) throws Exception { 319 if (subscription.getConsumerInfo().isRetroactive()) { 320 subscriptionRecoveryPolicy.recover(context, this, subscription); 321 } 322 } 323 324 @Override 325 public void send(final ProducerBrokerExchange producerExchange, final Message message) throws Exception { 326 final ConnectionContext context = producerExchange.getConnectionContext(); 327 328 final ProducerInfo producerInfo = producerExchange.getProducerState().getInfo(); 329 producerExchange.incrementSend(); 330 final boolean sendProducerAck = !message.isResponseRequired() && producerInfo.getWindowSize() > 0 331 && !context.isInRecoveryMode(); 332 333 message.setRegionDestination(this); 334 335 // There is delay between the client sending it and it arriving at the 336 // destination.. it may have expired. 337 if (message.isExpired()) { 338 broker.messageExpired(context, message, null); 339 getDestinationStatistics().getExpired().increment(); 340 if (sendProducerAck) { 341 ProducerAck ack = new ProducerAck(producerInfo.getProducerId(), message.getSize()); 342 context.getConnection().dispatchAsync(ack); 343 } 344 return; 345 } 346 347 if (memoryUsage.isFull()) { 348 isFull(context, memoryUsage); 349 fastProducer(context, producerInfo); 350 351 if (isProducerFlowControl() && context.isProducerFlowControl()) { 352 353 if (isFlowControlLogRequired()) { 354 LOG.warn("{}, Usage Manager memory limit reached {}. Producers will be throttled to the rate at which messages are removed from this destination to prevent flooding it. See http://activemq.apache.org/producer-flow-control.html for more info.", 355 getActiveMQDestination().getQualifiedName(), memoryUsage.getLimit()); 356 } else { 357 LOG.debug("{}, Usage Manager memory limit reached {}. Producers will be throttled to the rate at which messages are removed from this destination to prevent flooding it. See http://activemq.apache.org/producer-flow-control.html for more info.", 358 getActiveMQDestination().getQualifiedName(), memoryUsage.getLimit()); 359 } 360 361 if (!context.isNetworkConnection() && systemUsage.isSendFailIfNoSpace()) { 362 throw new javax.jms.ResourceAllocationException("Usage Manager memory limit (" 363 + memoryUsage.getLimit() + ") reached. Rejecting send for producer (" + message.getProducerId() 364 + ") to prevent flooding " + getActiveMQDestination().getQualifiedName() + "." 365 + " See http://activemq.apache.org/producer-flow-control.html for more info"); 366 } 367 368 // We can avoid blocking due to low usage if the producer is sending a sync message or 369 // if it is using a producer window 370 if (producerInfo.getWindowSize() > 0 || message.isResponseRequired()) { 371 synchronized (messagesWaitingForSpace) { 372 messagesWaitingForSpace.add(new Runnable() { 373 @Override 374 public void run() { 375 try { 376 377 // While waiting for space to free up... 378 // the transaction may be done 379 if (message.isInTransaction()) { 380 if (context.getTransaction() == null || context.getTransaction().getState() > IN_USE_STATE) { 381 throw new JMSException("Send transaction completed while waiting for space"); 382 } 383 } 384 385 // the message may have expired. 386 if (message.isExpired()) { 387 broker.messageExpired(context, message, null); 388 getDestinationStatistics().getExpired().increment(); 389 } else { 390 doMessageSend(producerExchange, message); 391 } 392 393 if (sendProducerAck) { 394 ProducerAck ack = new ProducerAck(producerInfo.getProducerId(), message 395 .getSize()); 396 context.getConnection().dispatchAsync(ack); 397 } else { 398 Response response = new Response(); 399 response.setCorrelationId(message.getCommandId()); 400 context.getConnection().dispatchAsync(response); 401 } 402 403 } catch (Exception e) { 404 if (!sendProducerAck && !context.isInRecoveryMode()) { 405 ExceptionResponse response = new ExceptionResponse(e); 406 response.setCorrelationId(message.getCommandId()); 407 context.getConnection().dispatchAsync(response); 408 } 409 } 410 } 411 }); 412 413 registerCallbackForNotFullNotification(); 414 context.setDontSendReponse(true); 415 return; 416 } 417 418 } else { 419 // Producer flow control cannot be used, so we have do the flow control 420 // at the broker by blocking this thread until there is space available. 421 422 if (memoryUsage.isFull()) { 423 if (context.isInTransaction()) { 424 425 int count = 0; 426 while (!memoryUsage.waitForSpace(1000)) { 427 if (context.getStopping().get()) { 428 throw new IOException("Connection closed, send aborted."); 429 } 430 if (count > 2 && context.isInTransaction()) { 431 count = 0; 432 int size = context.getTransaction().size(); 433 LOG.warn("Waiting for space to send transacted message - transaction elements = {} need more space to commit. Message = {}", size, message); 434 } 435 count++; 436 } 437 } else { 438 waitForSpace( 439 context, 440 producerExchange, 441 memoryUsage, 442 "Usage Manager Memory Usage limit reached. Stopping producer (" 443 + message.getProducerId() 444 + ") to prevent flooding " 445 + getActiveMQDestination().getQualifiedName() 446 + "." 447 + " See http://activemq.apache.org/producer-flow-control.html for more info"); 448 } 449 } 450 451 // The usage manager could have delayed us by the time 452 // we unblock the message could have expired.. 453 if (message.isExpired()) { 454 getDestinationStatistics().getExpired().increment(); 455 LOG.debug("Expired message: {}", message); 456 return; 457 } 458 } 459 } 460 } 461 462 doMessageSend(producerExchange, message); 463 messageDelivered(context, message); 464 if (sendProducerAck) { 465 ProducerAck ack = new ProducerAck(producerInfo.getProducerId(), message.getSize()); 466 context.getConnection().dispatchAsync(ack); 467 } 468 } 469 470 /** 471 * do send the message - this needs to be synchronized to ensure messages 472 * are stored AND dispatched in the right order 473 * 474 * @param producerExchange 475 * @param message 476 * @throws IOException 477 * @throws Exception 478 */ 479 synchronized void doMessageSend(final ProducerBrokerExchange producerExchange, final Message message) 480 throws IOException, Exception { 481 final ConnectionContext context = producerExchange.getConnectionContext(); 482 message.getMessageId().setBrokerSequenceId(getDestinationSequenceId()); 483 Future<Object> result = null; 484 485 if (topicStore != null && message.isPersistent() && !canOptimizeOutPersistence()) { 486 if (systemUsage.getStoreUsage().isFull(getStoreUsageHighWaterMark())) { 487 final String logMessage = "Persistent store is Full, " + getStoreUsageHighWaterMark() + "% of " 488 + systemUsage.getStoreUsage().getLimit() + ". Stopping producer (" + message.getProducerId() 489 + ") to prevent flooding " + getActiveMQDestination().getQualifiedName() + "." 490 + " See http://activemq.apache.org/producer-flow-control.html for more info"; 491 if (!context.isNetworkConnection() && systemUsage.isSendFailIfNoSpace()) { 492 throw new javax.jms.ResourceAllocationException(logMessage); 493 } 494 495 waitForSpace(context,producerExchange, systemUsage.getStoreUsage(), getStoreUsageHighWaterMark(), logMessage); 496 } 497 result = topicStore.asyncAddTopicMessage(context, message,isOptimizeStorage()); 498 } 499 500 message.incrementReferenceCount(); 501 502 if (context.isInTransaction()) { 503 context.getTransaction().addSynchronization(new Synchronization() { 504 @Override 505 public void afterCommit() throws Exception { 506 // It could take while before we receive the commit 507 // operation.. by that time the message could have 508 // expired.. 509 if (message.isExpired()) { 510 if (broker.isExpired(message)) { 511 getDestinationStatistics().getExpired().increment(); 512 broker.messageExpired(context, message, null); 513 } 514 message.decrementReferenceCount(); 515 return; 516 } 517 try { 518 dispatch(context, message); 519 } finally { 520 message.decrementReferenceCount(); 521 } 522 } 523 524 @Override 525 public void afterRollback() throws Exception { 526 message.decrementReferenceCount(); 527 } 528 }); 529 530 } else { 531 try { 532 dispatch(context, message); 533 } finally { 534 message.decrementReferenceCount(); 535 } 536 } 537 538 if (result != null && !result.isCancelled()) { 539 try { 540 result.get(); 541 } catch (CancellationException e) { 542 // ignore - the task has been cancelled if the message 543 // has already been deleted 544 } 545 } 546 } 547 548 private boolean canOptimizeOutPersistence() { 549 return durableSubscribers.size() == 0; 550 } 551 552 @Override 553 public String toString() { 554 return "Topic: destination=" + destination.getPhysicalName() + ", subscriptions=" + consumers.size(); 555 } 556 557 @Override 558 public void acknowledge(ConnectionContext context, Subscription sub, final MessageAck ack, 559 final MessageReference node) throws IOException { 560 if (topicStore != null && node.isPersistent()) { 561 DurableTopicSubscription dsub = (DurableTopicSubscription) sub; 562 SubscriptionKey key = dsub.getSubscriptionKey(); 563 topicStore.acknowledge(context, key.getClientId(), key.getSubscriptionName(), node.getMessageId(), 564 convertToNonRangedAck(ack, node)); 565 } 566 messageConsumed(context, node); 567 } 568 569 @Override 570 public void gc() { 571 } 572 573 public Message loadMessage(MessageId messageId) throws IOException { 574 return topicStore != null ? topicStore.getMessage(messageId) : null; 575 } 576 577 @Override 578 public void start() throws Exception { 579 if (started.compareAndSet(false, true)) { 580 this.subscriptionRecoveryPolicy.start(); 581 if (memoryUsage != null) { 582 memoryUsage.start(); 583 } 584 585 if (getExpireMessagesPeriod() > 0 && !AdvisorySupport.isAdvisoryTopic(getActiveMQDestination())) { 586 scheduler.executePeriodically(expireMessagesTask, getExpireMessagesPeriod()); 587 } 588 } 589 } 590 591 @Override 592 public void stop() throws Exception { 593 if (started.compareAndSet(true, false)) { 594 if (taskRunner != null) { 595 taskRunner.shutdown(); 596 } 597 this.subscriptionRecoveryPolicy.stop(); 598 if (memoryUsage != null) { 599 memoryUsage.stop(); 600 } 601 if (this.topicStore != null) { 602 this.topicStore.stop(); 603 } 604 605 scheduler.cancel(expireMessagesTask); 606 } 607 } 608 609 @Override 610 public Message[] browse() { 611 final List<Message> result = new ArrayList<Message>(); 612 doBrowse(result, getMaxBrowsePageSize()); 613 return result.toArray(new Message[result.size()]); 614 } 615 616 private void doBrowse(final List<Message> browseList, final int max) { 617 try { 618 if (topicStore != null) { 619 final List<Message> toExpire = new ArrayList<Message>(); 620 topicStore.recover(new MessageRecoveryListener() { 621 @Override 622 public boolean recoverMessage(Message message) throws Exception { 623 if (message.isExpired()) { 624 toExpire.add(message); 625 } 626 browseList.add(message); 627 return true; 628 } 629 630 @Override 631 public boolean recoverMessageReference(MessageId messageReference) throws Exception { 632 return true; 633 } 634 635 @Override 636 public boolean hasSpace() { 637 return browseList.size() < max; 638 } 639 640 @Override 641 public boolean isDuplicate(MessageId id) { 642 return false; 643 } 644 }); 645 final ConnectionContext connectionContext = createConnectionContext(); 646 for (Message message : toExpire) { 647 for (DurableTopicSubscription sub : durableSubscribers.values()) { 648 if (!sub.isActive()) { 649 message.setRegionDestination(this); 650 messageExpired(connectionContext, sub, message); 651 } 652 } 653 } 654 Message[] msgs = subscriptionRecoveryPolicy.browse(getActiveMQDestination()); 655 if (msgs != null) { 656 for (int i = 0; i < msgs.length && browseList.size() < max; i++) { 657 browseList.add(msgs[i]); 658 } 659 } 660 } 661 } catch (Throwable e) { 662 LOG.warn("Failed to browse Topic: {}", getActiveMQDestination().getPhysicalName(), e); 663 } 664 } 665 666 @Override 667 public boolean iterate() { 668 synchronized (messagesWaitingForSpace) { 669 while (!memoryUsage.isFull() && !messagesWaitingForSpace.isEmpty()) { 670 Runnable op = messagesWaitingForSpace.removeFirst(); 671 op.run(); 672 } 673 674 if (!messagesWaitingForSpace.isEmpty()) { 675 registerCallbackForNotFullNotification(); 676 } 677 } 678 return false; 679 } 680 681 private void registerCallbackForNotFullNotification() { 682 // If the usage manager is not full, then the task will not 683 // get called.. 684 if (!memoryUsage.notifyCallbackWhenNotFull(sendMessagesWaitingForSpaceTask)) { 685 // so call it directly here. 686 sendMessagesWaitingForSpaceTask.run(); 687 } 688 } 689 690 // Properties 691 // ------------------------------------------------------------------------- 692 693 public DispatchPolicy getDispatchPolicy() { 694 return dispatchPolicy; 695 } 696 697 public void setDispatchPolicy(DispatchPolicy dispatchPolicy) { 698 this.dispatchPolicy = dispatchPolicy; 699 } 700 701 public SubscriptionRecoveryPolicy getSubscriptionRecoveryPolicy() { 702 return subscriptionRecoveryPolicy; 703 } 704 705 public void setSubscriptionRecoveryPolicy(SubscriptionRecoveryPolicy recoveryPolicy) { 706 if (this.subscriptionRecoveryPolicy != null && this.subscriptionRecoveryPolicy instanceof RetainedMessageSubscriptionRecoveryPolicy) { 707 // allow users to combine retained message policy with other ActiveMQ policies 708 RetainedMessageSubscriptionRecoveryPolicy policy = (RetainedMessageSubscriptionRecoveryPolicy) this.subscriptionRecoveryPolicy; 709 policy.setWrapped(recoveryPolicy); 710 } else { 711 this.subscriptionRecoveryPolicy = recoveryPolicy; 712 } 713 } 714 715 // Implementation methods 716 // ------------------------------------------------------------------------- 717 718 @Override 719 public final void wakeup() { 720 } 721 722 protected void dispatch(final ConnectionContext context, Message message) throws Exception { 723 // AMQ-2586: Better to leave this stat at zero than to give the user 724 // misleading metrics. 725 // destinationStatistics.getMessages().increment(); 726 destinationStatistics.getEnqueues().increment(); 727 destinationStatistics.getMessageSize().addSize(message.getSize()); 728 MessageEvaluationContext msgContext = null; 729 730 dispatchLock.readLock().lock(); 731 try { 732 if (!subscriptionRecoveryPolicy.add(context, message)) { 733 return; 734 } 735 synchronized (consumers) { 736 if (consumers.isEmpty()) { 737 onMessageWithNoConsumers(context, message); 738 return; 739 } 740 } 741 msgContext = context.getMessageEvaluationContext(); 742 msgContext.setDestination(destination); 743 msgContext.setMessageReference(message); 744 if (!dispatchPolicy.dispatch(message, msgContext, consumers)) { 745 onMessageWithNoConsumers(context, message); 746 } 747 748 } finally { 749 dispatchLock.readLock().unlock(); 750 if (msgContext != null) { 751 msgContext.clear(); 752 } 753 } 754 } 755 756 private final AtomicBoolean expiryTaskInProgress = new AtomicBoolean(false); 757 private final Runnable expireMessagesWork = new Runnable() { 758 @Override 759 public void run() { 760 List<Message> browsedMessages = new InsertionCountList<Message>(); 761 doBrowse(browsedMessages, getMaxExpirePageSize()); 762 expiryTaskInProgress.set(false); 763 } 764 }; 765 private final Runnable expireMessagesTask = new Runnable() { 766 @Override 767 public void run() { 768 if (expiryTaskInProgress.compareAndSet(false, true)) { 769 taskRunnerFactor.execute(expireMessagesWork); 770 } 771 } 772 }; 773 774 @Override 775 public void messageExpired(ConnectionContext context, Subscription subs, MessageReference reference) { 776 broker.messageExpired(context, reference, subs); 777 // AMQ-2586: Better to leave this stat at zero than to give the user 778 // misleading metrics. 779 // destinationStatistics.getMessages().decrement(); 780 destinationStatistics.getExpired().increment(); 781 MessageAck ack = new MessageAck(); 782 ack.setAckType(MessageAck.STANDARD_ACK_TYPE); 783 ack.setDestination(destination); 784 ack.setMessageID(reference.getMessageId()); 785 try { 786 if (subs instanceof DurableTopicSubscription) { 787 ((DurableTopicSubscription)subs).removePending(reference); 788 } 789 acknowledge(context, subs, ack, reference); 790 } catch (Exception e) { 791 LOG.error("Failed to remove expired Message from the store ", e); 792 } 793 } 794 795 @Override 796 protected Logger getLog() { 797 return LOG; 798 } 799 800 protected boolean isOptimizeStorage(){ 801 boolean result = false; 802 803 if (isDoOptimzeMessageStorage() && durableSubscribers.isEmpty()==false){ 804 result = true; 805 for (DurableTopicSubscription s : durableSubscribers.values()) { 806 if (s.isActive()== false){ 807 result = false; 808 break; 809 } 810 if (s.getPrefetchSize()==0){ 811 result = false; 812 break; 813 } 814 if (s.isSlowConsumer()){ 815 result = false; 816 break; 817 } 818 if (s.getInFlightUsage() > getOptimizeMessageStoreInFlightLimit()){ 819 result = false; 820 break; 821 } 822 } 823 } 824 return result; 825 } 826 827 /** 828 * force a reread of the store - after transaction recovery completion 829 * @param pendingAdditionsCount 830 */ 831 @Override 832 public void clearPendingMessages(int pendingAdditionsCount) { 833 dispatchLock.readLock().lock(); 834 try { 835 for (DurableTopicSubscription durableTopicSubscription : durableSubscribers.values()) { 836 clearPendingAndDispatch(durableTopicSubscription); 837 } 838 } finally { 839 dispatchLock.readLock().unlock(); 840 } 841 } 842 843 private void clearPendingAndDispatch(DurableTopicSubscription durableTopicSubscription) { 844 synchronized (durableTopicSubscription.pendingLock) { 845 durableTopicSubscription.pending.clear(); 846 try { 847 durableTopicSubscription.dispatchPending(); 848 } catch (IOException exception) { 849 LOG.warn("After clear of pending, failed to dispatch to: {}, for: {}, pending: {}", new Object[]{ 850 durableTopicSubscription, 851 destination, 852 durableTopicSubscription.pending }, exception); 853 } 854 } 855 } 856 857 private void rollback(MessageId poisoned) { 858 dispatchLock.readLock().lock(); 859 try { 860 for (DurableTopicSubscription durableTopicSubscription : durableSubscribers.values()) { 861 durableTopicSubscription.getPending().rollback(poisoned); 862 } 863 } finally { 864 dispatchLock.readLock().unlock(); 865 } 866 } 867 868 public Map<SubscriptionKey, DurableTopicSubscription> getDurableTopicSubs() { 869 return durableSubscribers; 870 } 871}