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