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