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.LinkedList; 021import java.util.concurrent.atomic.AtomicInteger; 022import java.util.concurrent.atomic.AtomicLong; 023 024import javax.jms.JMSException; 025 026import org.apache.activemq.ActiveMQMessageAudit; 027import org.apache.activemq.broker.Broker; 028import org.apache.activemq.broker.ConnectionContext; 029import org.apache.activemq.broker.region.cursors.FilePendingMessageCursor; 030import org.apache.activemq.broker.region.cursors.PendingMessageCursor; 031import org.apache.activemq.broker.region.cursors.VMPendingMessageCursor; 032import org.apache.activemq.broker.region.policy.MessageEvictionStrategy; 033import org.apache.activemq.broker.region.policy.OldestMessageEvictionStrategy; 034import org.apache.activemq.command.ConsumerControl; 035import org.apache.activemq.command.ConsumerInfo; 036import org.apache.activemq.command.Message; 037import org.apache.activemq.command.MessageAck; 038import org.apache.activemq.command.MessageDispatch; 039import org.apache.activemq.command.MessageDispatchNotification; 040import org.apache.activemq.command.MessagePull; 041import org.apache.activemq.command.Response; 042import org.apache.activemq.thread.Scheduler; 043import org.apache.activemq.transaction.Synchronization; 044import org.apache.activemq.transport.TransmitCallback; 045import org.apache.activemq.usage.SystemUsage; 046import org.slf4j.Logger; 047import org.slf4j.LoggerFactory; 048 049public class TopicSubscription extends AbstractSubscription { 050 051 private static final Logger LOG = LoggerFactory.getLogger(TopicSubscription.class); 052 private static final AtomicLong CURSOR_NAME_COUNTER = new AtomicLong(0); 053 054 protected PendingMessageCursor matched; 055 protected final SystemUsage usageManager; 056 protected AtomicLong dispatchedCounter = new AtomicLong(); 057 058 boolean singleDestination = true; 059 Destination destination; 060 private final Scheduler scheduler; 061 062 private int maximumPendingMessages = -1; 063 private MessageEvictionStrategy messageEvictionStrategy = new OldestMessageEvictionStrategy(); 064 private int discarded; 065 private final Object matchedListMutex = new Object(); 066 private final AtomicLong enqueueCounter = new AtomicLong(0); 067 private final AtomicLong dequeueCounter = new AtomicLong(0); 068 private final AtomicInteger prefetchExtension = new AtomicInteger(0); 069 private int memoryUsageHighWaterMark = 95; 070 // allow duplicate suppression in a ring network of brokers 071 protected int maxProducersToAudit = 1024; 072 protected int maxAuditDepth = 1000; 073 protected boolean enableAudit = false; 074 protected ActiveMQMessageAudit audit; 075 protected boolean active = false; 076 077 public TopicSubscription(Broker broker,ConnectionContext context, ConsumerInfo info, SystemUsage usageManager) throws Exception { 078 super(broker, context, info); 079 this.usageManager = usageManager; 080 String matchedName = "TopicSubscription:" + CURSOR_NAME_COUNTER.getAndIncrement() + "[" + info.getConsumerId().toString() + "]"; 081 if (info.getDestination().isTemporary() || broker.getTempDataStore()==null ) { 082 this.matched = new VMPendingMessageCursor(false); 083 } else { 084 this.matched = new FilePendingMessageCursor(broker,matchedName,false); 085 } 086 087 this.scheduler = broker.getScheduler(); 088 } 089 090 public void init() throws Exception { 091 this.matched.setSystemUsage(usageManager); 092 this.matched.setMemoryUsageHighWaterMark(getCursorMemoryHighWaterMark()); 093 this.matched.start(); 094 if (enableAudit) { 095 audit= new ActiveMQMessageAudit(maxAuditDepth, maxProducersToAudit); 096 } 097 this.active=true; 098 } 099 100 @Override 101 public void add(MessageReference node) throws Exception { 102 if (isDuplicate(node)) { 103 return; 104 } 105 // Lets use an indirect reference so that we can associate a unique 106 // locator /w the message. 107 node = new IndirectMessageReference(node.getMessage()); 108 enqueueCounter.incrementAndGet(); 109 synchronized (matchedListMutex) { 110 if (!isFull() && matched.isEmpty()) { 111 // if maximumPendingMessages is set we will only discard messages which 112 // have not been dispatched (i.e. we allow the prefetch buffer to be filled) 113 dispatch(node); 114 setSlowConsumer(false); 115 } else { 116 if (info.getPrefetchSize() > 1 && matched.size() > info.getPrefetchSize()) { 117 // Slow consumers should log and set their state as such. 118 if (!isSlowConsumer()) { 119 LOG.warn("{}: has twice its prefetch limit pending, without an ack; it appears to be slow", toString()); 120 setSlowConsumer(true); 121 for (Destination dest: destinations) { 122 dest.slowConsumer(getContext(), this); 123 } 124 } 125 } 126 if (maximumPendingMessages != 0) { 127 boolean warnedAboutWait = false; 128 while (active) { 129 while (matched.isFull()) { 130 if (getContext().getStopping().get()) { 131 LOG.warn("{}: stopped waiting for space in pendingMessage cursor for: {}", toString(), node.getMessageId()); 132 enqueueCounter.decrementAndGet(); 133 return; 134 } 135 if (!warnedAboutWait) { 136 LOG.info("{}: Pending message cursor [{}] is full, temp usag ({}%) or memory usage ({}%) limit reached, blocking message add() pending the release of resources.", 137 new Object[]{ 138 toString(), 139 matched, 140 matched.getSystemUsage().getTempUsage().getPercentUsage(), 141 matched.getSystemUsage().getMemoryUsage().getPercentUsage() 142 }); 143 warnedAboutWait = true; 144 } 145 matchedListMutex.wait(20); 146 } 147 // Temporary storage could be full - so just try to add the message 148 // see https://issues.apache.org/activemq/browse/AMQ-2475 149 if (matched.tryAddMessageLast(node, 10)) { 150 break; 151 } 152 } 153 if (maximumPendingMessages > 0) { 154 // calculate the high water mark from which point we 155 // will eagerly evict expired messages 156 int max = messageEvictionStrategy.getEvictExpiredMessagesHighWatermark(); 157 if (maximumPendingMessages > 0 && maximumPendingMessages < max) { 158 max = maximumPendingMessages; 159 } 160 if (!matched.isEmpty() && matched.size() > max) { 161 removeExpiredMessages(); 162 } 163 // lets discard old messages as we are a slow consumer 164 while (!matched.isEmpty() && matched.size() > maximumPendingMessages) { 165 int pageInSize = matched.size() - maximumPendingMessages; 166 // only page in a 1000 at a time - else we could blow the memory 167 pageInSize = Math.max(1000, pageInSize); 168 LinkedList<MessageReference> list = null; 169 MessageReference[] oldMessages=null; 170 synchronized(matched){ 171 list = matched.pageInList(pageInSize); 172 oldMessages = messageEvictionStrategy.evictMessages(list); 173 for (MessageReference ref : list) { 174 ref.decrementReferenceCount(); 175 } 176 } 177 int messagesToEvict = 0; 178 if (oldMessages != null){ 179 messagesToEvict = oldMessages.length; 180 for (int i = 0; i < messagesToEvict; i++) { 181 MessageReference oldMessage = oldMessages[i]; 182 discard(oldMessage); 183 } 184 } 185 // lets avoid an infinite loop if we are given a bad eviction strategy 186 // for a bad strategy lets just not evict 187 if (messagesToEvict == 0) { 188 LOG.warn("No messages to evict returned for {} from eviction strategy: {} out of {} candidates", new Object[]{ 189 destination, messageEvictionStrategy, list.size() 190 }); 191 break; 192 } 193 } 194 } 195 dispatchMatched(); 196 } 197 } 198 } 199 } 200 201 private boolean isDuplicate(MessageReference node) { 202 boolean duplicate = false; 203 if (enableAudit && audit != null) { 204 duplicate = audit.isDuplicate(node); 205 if (LOG.isDebugEnabled()) { 206 if (duplicate) { 207 LOG.debug("{}, ignoring duplicate add: {}", this, node.getMessageId()); 208 } 209 } 210 } 211 return duplicate; 212 } 213 214 /** 215 * Discard any expired messages from the matched list. Called from a 216 * synchronized block. 217 * 218 * @throws IOException 219 */ 220 protected void removeExpiredMessages() throws IOException { 221 try { 222 matched.reset(); 223 while (matched.hasNext()) { 224 MessageReference node = matched.next(); 225 node.decrementReferenceCount(); 226 if (broker.isExpired(node)) { 227 matched.remove(); 228 dispatchedCounter.incrementAndGet(); 229 node.decrementReferenceCount(); 230 ((Destination)node.getRegionDestination()).getDestinationStatistics().getExpired().increment(); 231 broker.messageExpired(getContext(), node, this); 232 break; 233 } 234 } 235 } finally { 236 matched.release(); 237 } 238 } 239 240 @Override 241 public void processMessageDispatchNotification(MessageDispatchNotification mdn) { 242 synchronized (matchedListMutex) { 243 try { 244 matched.reset(); 245 while (matched.hasNext()) { 246 MessageReference node = matched.next(); 247 node.decrementReferenceCount(); 248 if (node.getMessageId().equals(mdn.getMessageId())) { 249 matched.remove(); 250 dispatchedCounter.incrementAndGet(); 251 node.decrementReferenceCount(); 252 break; 253 } 254 } 255 } finally { 256 matched.release(); 257 } 258 } 259 } 260 261 @Override 262 public synchronized void acknowledge(final ConnectionContext context, final MessageAck ack) throws Exception { 263 super.acknowledge(context, ack); 264 265 // Handle the standard acknowledgment case. 266 if (ack.isStandardAck() || ack.isPoisonAck() || ack.isIndividualAck()) { 267 if (context.isInTransaction()) { 268 context.getTransaction().addSynchronization(new Synchronization() { 269 270 @Override 271 public void afterCommit() throws Exception { 272 synchronized (TopicSubscription.this) { 273 if (singleDestination && destination != null) { 274 destination.getDestinationStatistics().getDequeues().add(ack.getMessageCount()); 275 } 276 } 277 dequeueCounter.addAndGet(ack.getMessageCount()); 278 dispatchMatched(); 279 } 280 }); 281 } else { 282 if (singleDestination && destination != null) { 283 destination.getDestinationStatistics().getDequeues().add(ack.getMessageCount()); 284 destination.getDestinationStatistics().getInflight().subtract(ack.getMessageCount()); 285 if (info.isNetworkSubscription()) { 286 destination.getDestinationStatistics().getForwards().add(ack.getMessageCount()); 287 } 288 } 289 dequeueCounter.addAndGet(ack.getMessageCount()); 290 } 291 while (true) { 292 int currentExtension = prefetchExtension.get(); 293 int newExtension = Math.max(0, currentExtension - ack.getMessageCount()); 294 if (prefetchExtension.compareAndSet(currentExtension, newExtension)) { 295 break; 296 } 297 } 298 dispatchMatched(); 299 return; 300 } else if (ack.isDeliveredAck()) { 301 // Message was delivered but not acknowledged: update pre-fetch counters. 302 prefetchExtension.addAndGet(ack.getMessageCount()); 303 dispatchMatched(); 304 return; 305 } else if (ack.isExpiredAck()) { 306 if (singleDestination && destination != null) { 307 destination.getDestinationStatistics().getInflight().subtract(ack.getMessageCount()); 308 destination.getDestinationStatistics().getExpired().add(ack.getMessageCount()); 309 destination.getDestinationStatistics().getDequeues().add(ack.getMessageCount()); 310 } 311 dequeueCounter.addAndGet(ack.getMessageCount()); 312 while (true) { 313 int currentExtension = prefetchExtension.get(); 314 int newExtension = Math.max(0, currentExtension - ack.getMessageCount()); 315 if (prefetchExtension.compareAndSet(currentExtension, newExtension)) { 316 break; 317 } 318 } 319 dispatchMatched(); 320 return; 321 } else if (ack.isRedeliveredAck()) { 322 // nothing to do atm 323 return; 324 } 325 throw new JMSException("Invalid acknowledgment: " + ack); 326 } 327 328 @Override 329 public Response pullMessage(ConnectionContext context, MessagePull pull) throws Exception { 330 331 // The slave should not deliver pull messages. 332 if (getPrefetchSize() == 0 ) { 333 334 final long currentDispatchedCount = dispatchedCounter.get(); 335 prefetchExtension.incrementAndGet(); 336 dispatchMatched(); 337 338 // If there was nothing dispatched.. we may need to setup a timeout. 339 if (currentDispatchedCount == dispatchedCounter.get()) { 340 341 // immediate timeout used by receiveNoWait() 342 if (pull.getTimeout() == -1) { 343 prefetchExtension.decrementAndGet(); 344 // Send a NULL message to signal nothing pending. 345 dispatch(null); 346 } 347 348 if (pull.getTimeout() > 0) { 349 scheduler.executeAfterDelay(new Runnable() { 350 351 @Override 352 public void run() { 353 pullTimeout(currentDispatchedCount); 354 } 355 }, pull.getTimeout()); 356 } 357 } 358 } 359 return null; 360 } 361 362 /** 363 * Occurs when a pull times out. If nothing has been dispatched since the 364 * timeout was setup, then send the NULL message. 365 */ 366 private final void pullTimeout(long currentDispatchedCount) { 367 synchronized (matchedListMutex) { 368 if (currentDispatchedCount == dispatchedCounter.get()) { 369 try { 370 dispatch(null); 371 } catch (Exception e) { 372 context.getConnection().serviceException(e); 373 } finally { 374 prefetchExtension.decrementAndGet(); 375 } 376 } 377 } 378 } 379 380 @Override 381 public int countBeforeFull() { 382 return getPrefetchSize() == 0 ? prefetchExtension.get() : info.getPrefetchSize() + prefetchExtension.get() - getDispatchedQueueSize(); 383 } 384 385 @Override 386 public int getPendingQueueSize() { 387 return matched(); 388 } 389 390 @Override 391 public int getDispatchedQueueSize() { 392 return (int)(dispatchedCounter.get() - prefetchExtension.get() - dequeueCounter.get()); 393 } 394 395 public int getMaximumPendingMessages() { 396 return maximumPendingMessages; 397 } 398 399 @Override 400 public long getDispatchedCounter() { 401 return dispatchedCounter.get(); 402 } 403 404 @Override 405 public long getEnqueueCounter() { 406 return enqueueCounter.get(); 407 } 408 409 @Override 410 public long getDequeueCounter() { 411 return dequeueCounter.get(); 412 } 413 414 /** 415 * @return the number of messages discarded due to being a slow consumer 416 */ 417 public int discarded() { 418 synchronized (matchedListMutex) { 419 return discarded; 420 } 421 } 422 423 /** 424 * @return the number of matched messages (messages targeted for the 425 * subscription but not yet able to be dispatched due to the 426 * prefetch buffer being full). 427 */ 428 public int matched() { 429 synchronized (matchedListMutex) { 430 return matched.size(); 431 } 432 } 433 434 /** 435 * Sets the maximum number of pending messages that can be matched against 436 * this consumer before old messages are discarded. 437 */ 438 public void setMaximumPendingMessages(int maximumPendingMessages) { 439 this.maximumPendingMessages = maximumPendingMessages; 440 } 441 442 public MessageEvictionStrategy getMessageEvictionStrategy() { 443 return messageEvictionStrategy; 444 } 445 446 /** 447 * Sets the eviction strategy used to decide which message to evict when the 448 * slow consumer needs to discard messages 449 */ 450 public void setMessageEvictionStrategy(MessageEvictionStrategy messageEvictionStrategy) { 451 this.messageEvictionStrategy = messageEvictionStrategy; 452 } 453 454 public int getMaxProducersToAudit() { 455 return maxProducersToAudit; 456 } 457 458 public synchronized void setMaxProducersToAudit(int maxProducersToAudit) { 459 this.maxProducersToAudit = maxProducersToAudit; 460 if (audit != null) { 461 audit.setMaximumNumberOfProducersToTrack(maxProducersToAudit); 462 } 463 } 464 465 public int getMaxAuditDepth() { 466 return maxAuditDepth; 467 } 468 469 public synchronized void setMaxAuditDepth(int maxAuditDepth) { 470 this.maxAuditDepth = maxAuditDepth; 471 if (audit != null) { 472 audit.setAuditDepth(maxAuditDepth); 473 } 474 } 475 476 public boolean isEnableAudit() { 477 return enableAudit; 478 } 479 480 public synchronized void setEnableAudit(boolean enableAudit) { 481 this.enableAudit = enableAudit; 482 if (enableAudit && audit == null) { 483 audit = new ActiveMQMessageAudit(maxAuditDepth,maxProducersToAudit); 484 } 485 } 486 487 // Implementation methods 488 // ------------------------------------------------------------------------- 489 @Override 490 public boolean isFull() { 491 return getDispatchedQueueSize() >= info.getPrefetchSize(); 492 } 493 494 @Override 495 public int getInFlightSize() { 496 return getDispatchedQueueSize(); 497 } 498 499 /** 500 * @return true when 60% or more room is left for dispatching messages 501 */ 502 @Override 503 public boolean isLowWaterMark() { 504 return getDispatchedQueueSize() <= (info.getPrefetchSize() * .4); 505 } 506 507 /** 508 * @return true when 10% or less room is left for dispatching messages 509 */ 510 @Override 511 public boolean isHighWaterMark() { 512 return getDispatchedQueueSize() >= (info.getPrefetchSize() * .9); 513 } 514 515 /** 516 * @param memoryUsageHighWaterMark the memoryUsageHighWaterMark to set 517 */ 518 public void setMemoryUsageHighWaterMark(int memoryUsageHighWaterMark) { 519 this.memoryUsageHighWaterMark = memoryUsageHighWaterMark; 520 } 521 522 /** 523 * @return the memoryUsageHighWaterMark 524 */ 525 public int getMemoryUsageHighWaterMark() { 526 return this.memoryUsageHighWaterMark; 527 } 528 529 /** 530 * @return the usageManager 531 */ 532 public SystemUsage getUsageManager() { 533 return this.usageManager; 534 } 535 536 /** 537 * @return the matched 538 */ 539 public PendingMessageCursor getMatched() { 540 return this.matched; 541 } 542 543 /** 544 * @param matched the matched to set 545 */ 546 public void setMatched(PendingMessageCursor matched) { 547 this.matched = matched; 548 } 549 550 /** 551 * inform the MessageConsumer on the client to change it's prefetch 552 * 553 * @param newPrefetch 554 */ 555 @Override 556 public void updateConsumerPrefetch(int newPrefetch) { 557 if (context != null && context.getConnection() != null && context.getConnection().isManageable()) { 558 ConsumerControl cc = new ConsumerControl(); 559 cc.setConsumerId(info.getConsumerId()); 560 cc.setPrefetch(newPrefetch); 561 context.getConnection().dispatchAsync(cc); 562 } 563 } 564 565 private void dispatchMatched() throws IOException { 566 synchronized (matchedListMutex) { 567 if (!matched.isEmpty() && !isFull()) { 568 try { 569 matched.reset(); 570 571 while (matched.hasNext() && !isFull()) { 572 MessageReference message = matched.next(); 573 message.decrementReferenceCount(); 574 matched.remove(); 575 // Message may have been sitting in the matched list a while 576 // waiting for the consumer to ak the message. 577 if (message.isExpired()) { 578 discard(message); 579 continue; // just drop it. 580 } 581 dispatch(message); 582 } 583 } finally { 584 matched.release(); 585 } 586 } 587 } 588 } 589 590 private void dispatch(final MessageReference node) throws IOException { 591 Message message = node.getMessage(); 592 if (node != null) { 593 node.incrementReferenceCount(); 594 } 595 // Make sure we can dispatch a message. 596 MessageDispatch md = new MessageDispatch(); 597 md.setMessage(message); 598 md.setConsumerId(info.getConsumerId()); 599 if (node != null) { 600 md.setDestination(((Destination)node.getRegionDestination()).getActiveMQDestination()); 601 dispatchedCounter.incrementAndGet(); 602 // Keep track if this subscription is receiving messages from a single destination. 603 if (singleDestination) { 604 if (destination == null) { 605 destination = (Destination)node.getRegionDestination(); 606 } else { 607 if (destination != node.getRegionDestination()) { 608 singleDestination = false; 609 } 610 } 611 } 612 } 613 if (info.isDispatchAsync()) { 614 if (node != null) { 615 md.setTransmitCallback(new TransmitCallback() { 616 617 @Override 618 public void onSuccess() { 619 Destination regionDestination = (Destination) node.getRegionDestination(); 620 regionDestination.getDestinationStatistics().getDispatched().increment(); 621 regionDestination.getDestinationStatistics().getInflight().increment(); 622 node.decrementReferenceCount(); 623 } 624 625 @Override 626 public void onFailure() { 627 Destination regionDestination = (Destination) node.getRegionDestination(); 628 regionDestination.getDestinationStatistics().getDispatched().increment(); 629 regionDestination.getDestinationStatistics().getInflight().increment(); 630 node.decrementReferenceCount(); 631 } 632 }); 633 } 634 context.getConnection().dispatchAsync(md); 635 } else { 636 context.getConnection().dispatchSync(md); 637 if (node != null) { 638 Destination regionDestination = (Destination) node.getRegionDestination(); 639 regionDestination.getDestinationStatistics().getDispatched().increment(); 640 regionDestination.getDestinationStatistics().getInflight().increment(); 641 node.decrementReferenceCount(); 642 } 643 } 644 } 645 646 private void discard(MessageReference message) { 647 message.decrementReferenceCount(); 648 matched.remove(message); 649 discarded++; 650 if(destination != null) { 651 destination.getDestinationStatistics().getDequeues().increment(); 652 } 653 LOG.debug("{}, discarding message {}", this, message); 654 Destination dest = (Destination) message.getRegionDestination(); 655 if (dest != null) { 656 dest.messageDiscarded(getContext(), this, message); 657 } 658 broker.getRoot().sendToDeadLetterQueue(getContext(), message, this, new Throwable("TopicSubDiscard. ID:" + info.getConsumerId())); 659 } 660 661 @Override 662 public String toString() { 663 return "TopicSubscription:" + " consumer=" + info.getConsumerId() + ", destinations=" + destinations.size() + ", dispatched=" + getDispatchedQueueSize() + ", delivered=" 664 + getDequeueCounter() + ", matched=" + matched() + ", discarded=" + discarded(); 665 } 666 667 @Override 668 public void destroy() { 669 this.active=false; 670 synchronized (matchedListMutex) { 671 try { 672 matched.destroy(); 673 } catch (Exception e) { 674 LOG.warn("Failed to destroy cursor", e); 675 } 676 } 677 setSlowConsumer(false); 678 } 679 680 @Override 681 public int getPrefetchSize() { 682 return info.getPrefetchSize(); 683 } 684 685 @Override 686 public void setPrefetchSize(int newSize) { 687 info.setPrefetchSize(newSize); 688 try { 689 dispatchMatched(); 690 } catch(Exception e) { 691 LOG.trace("Caught exception on dispatch after prefetch size change."); 692 } 693 } 694}