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