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