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.Collections; 022import java.util.HashSet; 023import java.util.List; 024import java.util.concurrent.ConcurrentHashMap; 025import java.util.concurrent.ConcurrentMap; 026import java.util.concurrent.atomic.AtomicBoolean; 027import java.util.concurrent.atomic.AtomicLong; 028 029import javax.jms.InvalidSelectorException; 030import javax.jms.JMSException; 031 032import org.apache.activemq.broker.Broker; 033import org.apache.activemq.broker.ConnectionContext; 034import org.apache.activemq.broker.region.cursors.AbstractPendingMessageCursor; 035import org.apache.activemq.broker.region.cursors.PendingMessageCursor; 036import org.apache.activemq.broker.region.cursors.StoreDurableSubscriberCursor; 037import org.apache.activemq.broker.region.policy.PolicyEntry; 038import org.apache.activemq.command.ActiveMQDestination; 039import org.apache.activemq.command.ConsumerInfo; 040import org.apache.activemq.command.Message; 041import org.apache.activemq.command.MessageAck; 042import org.apache.activemq.command.MessageDispatch; 043import org.apache.activemq.command.MessageId; 044import org.apache.activemq.command.RemoveInfo; 045import org.apache.activemq.store.TopicMessageStore; 046import org.apache.activemq.transaction.Synchronization; 047import org.apache.activemq.usage.SystemUsage; 048import org.apache.activemq.usage.Usage; 049import org.apache.activemq.usage.UsageListener; 050import org.apache.activemq.util.SubscriptionKey; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054public class DurableTopicSubscription extends PrefetchSubscription implements UsageListener { 055 056 private static final Logger LOG = LoggerFactory.getLogger(DurableTopicSubscription.class); 057 private final ConcurrentMap<MessageId, Integer> redeliveredMessages = new ConcurrentHashMap<MessageId, Integer>(); 058 private final ConcurrentMap<ActiveMQDestination, Destination> durableDestinations = new ConcurrentHashMap<ActiveMQDestination, Destination>(); 059 private final SubscriptionKey subscriptionKey; 060 private boolean keepDurableSubsActive; 061 private boolean enableMessageExpirationOnActiveDurableSubs; 062 private final AtomicBoolean active = new AtomicBoolean(); 063 private final AtomicLong offlineTimestamp = new AtomicLong(-1); 064 private final HashSet<MessageId> ackedAndPrepared = new HashSet<MessageId>(); 065 066 public DurableTopicSubscription(Broker broker, SystemUsage usageManager, ConnectionContext context, ConsumerInfo info, boolean keepDurableSubsActive) 067 throws JMSException { 068 super(broker, usageManager, context, info); 069 this.pending = new StoreDurableSubscriberCursor(broker, context.getClientId(), info.getSubscriptionName(), info.getPrefetchSize(), this); 070 this.pending.setSystemUsage(usageManager); 071 this.pending.setMemoryUsageHighWaterMark(getCursorMemoryHighWaterMark()); 072 this.keepDurableSubsActive = keepDurableSubsActive; 073 this.enableMessageExpirationOnActiveDurableSubs = broker.getBrokerService().isEnableMessageExpirationOnActiveDurableSubs(); 074 subscriptionKey = new SubscriptionKey(context.getClientId(), info.getSubscriptionName()); 075 } 076 077 public final boolean isActive() { 078 return active.get(); 079 } 080 081 public final long getOfflineTimestamp() { 082 return offlineTimestamp.get(); 083 } 084 085 public void setOfflineTimestamp(long timestamp) { 086 offlineTimestamp.set(timestamp); 087 } 088 089 @Override 090 public boolean isFull() { 091 return !active.get() || super.isFull(); 092 } 093 094 @Override 095 public void gc() { 096 } 097 098 /** 099 * store will have a pending ack for all durables, irrespective of the 100 * selector so we need to ack if node is un-matched 101 */ 102 @Override 103 public void unmatched(MessageReference node) throws IOException { 104 MessageAck ack = new MessageAck(); 105 ack.setAckType(MessageAck.UNMATCHED_ACK_TYPE); 106 ack.setMessageID(node.getMessageId()); 107 Destination regionDestination = (Destination) node.getRegionDestination(); 108 regionDestination.acknowledge(this.getContext(), this, ack, node); 109 } 110 111 @Override 112 protected void setPendingBatchSize(PendingMessageCursor pending, int numberToDispatch) { 113 // statically configured via maxPageSize 114 } 115 116 @Override 117 public void add(ConnectionContext context, Destination destination) throws Exception { 118 if (!destinations.contains(destination)) { 119 super.add(context, destination); 120 } 121 // do it just once per destination 122 if (durableDestinations.containsKey(destination.getActiveMQDestination())) { 123 return; 124 } 125 durableDestinations.put(destination.getActiveMQDestination(), destination); 126 127 if (active.get() || keepDurableSubsActive) { 128 Topic topic = (Topic) destination; 129 topic.activate(context, this); 130 getSubscriptionStatistics().getEnqueues().add(pending.size()); 131 } else if (destination.getMessageStore() != null) { 132 TopicMessageStore store = (TopicMessageStore) destination.getMessageStore(); 133 try { 134 getSubscriptionStatistics().getEnqueues().add(store.getMessageCount(subscriptionKey.getClientId(), subscriptionKey.getSubscriptionName())); 135 } catch (IOException e) { 136 JMSException jmsEx = new JMSException("Failed to retrieve enqueueCount from store " + e); 137 jmsEx.setLinkedException(e); 138 throw jmsEx; 139 } 140 } 141 dispatchPending(); 142 } 143 144 // used by RetaineMessageSubscriptionRecoveryPolicy 145 public boolean isEmpty(Topic topic) { 146 return pending.isEmpty(topic); 147 } 148 149 public void activate(SystemUsage memoryManager, ConnectionContext context, ConsumerInfo info, RegionBroker regionBroker) throws Exception { 150 if (!active.get()) { 151 this.context = context; 152 this.info = info; 153 154 LOG.debug("Activating {}", this); 155 if (!keepDurableSubsActive) { 156 for (Destination destination : durableDestinations.values()) { 157 Topic topic = (Topic) destination; 158 add(context, topic); 159 topic.activate(context, this); 160 } 161 162 // On Activation we should update the configuration based on our new consumer info. 163 ActiveMQDestination dest = this.info.getDestination(); 164 if (dest != null && regionBroker.getDestinationPolicy() != null) { 165 PolicyEntry entry = regionBroker.getDestinationPolicy().getEntryFor(dest); 166 if (entry != null) { 167 entry.configure(broker, usageManager, this); 168 } 169 } 170 } 171 172 synchronized (pendingLock) { 173 if (!((AbstractPendingMessageCursor) pending).isStarted() || !keepDurableSubsActive) { 174 pending.setSystemUsage(memoryManager); 175 pending.setMemoryUsageHighWaterMark(getCursorMemoryHighWaterMark()); 176 pending.setMaxAuditDepth(getMaxAuditDepth()); 177 pending.setMaxProducersToAudit(getMaxProducersToAudit()); 178 pending.start(); 179 } 180 // use recovery policy every time sub is activated for retroactive topics and consumers 181 for (Destination destination : durableDestinations.values()) { 182 Topic topic = (Topic) destination; 183 if (topic.isAlwaysRetroactive() || info.isRetroactive()) { 184 topic.recoverRetroactiveMessages(context, this); 185 } 186 } 187 } 188 this.active.set(true); 189 this.offlineTimestamp.set(-1); 190 dispatchPending(); 191 this.usageManager.getMemoryUsage().addUsageListener(this); 192 } 193 } 194 195 public void deactivate(boolean keepDurableSubsActive, long lastDeliveredSequenceId) throws Exception { 196 LOG.debug("Deactivating keepActive={}, {}", keepDurableSubsActive, this); 197 active.set(false); 198 this.keepDurableSubsActive = keepDurableSubsActive; 199 offlineTimestamp.set(System.currentTimeMillis()); 200 usageManager.getMemoryUsage().removeUsageListener(this); 201 202 ArrayList<Topic> topicsToDeactivate = new ArrayList<Topic>(); 203 List<MessageReference> savedDispateched = null; 204 205 synchronized (pendingLock) { 206 if (!keepDurableSubsActive) { 207 pending.stop(); 208 } 209 210 synchronized (dispatchLock) { 211 for (Destination destination : durableDestinations.values()) { 212 Topic topic = (Topic) destination; 213 if (!keepDurableSubsActive) { 214 topicsToDeactivate.add(topic); 215 } else { 216 topic.getDestinationStatistics().getInflight().subtract(dispatched.size()); 217 } 218 } 219 220 // Before we add these back to pending they need to be in producer order not 221 // dispatch order so we can add them to the front of the pending list. 222 Collections.reverse(dispatched); 223 224 for (final MessageReference node : dispatched) { 225 // Mark the dispatched messages as redelivered for next time. 226 if (lastDeliveredSequenceId == RemoveInfo.LAST_DELIVERED_UNKNOWN || lastDeliveredSequenceId == 0 || 227 (lastDeliveredSequenceId > 0 && node.getMessageId().getBrokerSequenceId() <= lastDeliveredSequenceId)) { 228 Integer count = redeliveredMessages.get(node.getMessageId()); 229 if (count != null) { 230 redeliveredMessages.put(node.getMessageId(), Integer.valueOf(count.intValue() + 1)); 231 } else { 232 redeliveredMessages.put(node.getMessageId(), Integer.valueOf(1)); 233 } 234 } 235 if (keepDurableSubsActive && pending.isTransient()) { 236 pending.addMessageFirst(node); 237 pending.rollback(node.getMessageId()); 238 } 239 // createMessageDispatch increments on remove from pending for dispatch 240 node.decrementReferenceCount(); 241 } 242 243 if (!topicsToDeactivate.isEmpty()) { 244 savedDispateched = new ArrayList<MessageReference>(dispatched); 245 } 246 dispatched.clear(); 247 getSubscriptionStatistics().getInflightMessageSize().reset(); 248 } 249 if (!keepDurableSubsActive && pending.isTransient()) { 250 try { 251 pending.reset(); 252 while (pending.hasNext()) { 253 MessageReference node = pending.next(); 254 node.decrementReferenceCount(); 255 pending.remove(); 256 } 257 } finally { 258 pending.release(); 259 } 260 } 261 } 262 for(Topic topic: topicsToDeactivate) { 263 topic.deactivate(context, this, savedDispateched); 264 } 265 prefetchExtension.set(0); 266 } 267 268 @Override 269 protected MessageDispatch createMessageDispatch(MessageReference node, Message message) { 270 MessageDispatch md = super.createMessageDispatch(node, message); 271 if (node != QueueMessageReference.NULL_MESSAGE) { 272 node.incrementReferenceCount(); 273 Integer count = redeliveredMessages.get(node.getMessageId()); 274 if (count != null) { 275 md.setRedeliveryCounter(count.intValue()); 276 } 277 } 278 return md; 279 } 280 281 @Override 282 public void add(MessageReference node) throws Exception { 283 if (!active.get() && !keepDurableSubsActive) { 284 return; 285 } 286 super.add(node); 287 } 288 289 @Override 290 public void dispatchPending() throws IOException { 291 if (isActive()) { 292 super.dispatchPending(); 293 } 294 } 295 296 public void removePending(MessageReference node) throws IOException { 297 pending.remove(node); 298 } 299 300 @Override 301 protected void doAddRecoveredMessage(MessageReference message) throws Exception { 302 synchronized (pending) { 303 pending.addRecoveredMessage(message); 304 } 305 } 306 307 @Override 308 public int getPendingQueueSize() { 309 if (active.get() || keepDurableSubsActive) { 310 return super.getPendingQueueSize(); 311 } 312 // TODO: need to get from store 313 return 0; 314 } 315 316 @Override 317 public void setSelector(String selector) throws InvalidSelectorException { 318 if (active.get()) { 319 throw new UnsupportedOperationException("You cannot dynamically change the selector for durable topic subscriptions"); 320 } else { 321 super.setSelector(getSelector()); 322 } 323 } 324 325 @Override 326 protected boolean canDispatch(MessageReference node) { 327 return true; // let them go, our dispatchPending gates the active / inactive state. 328 } 329 330 @Override 331 protected boolean trackedInPendingTransaction(MessageReference node) { 332 return !ackedAndPrepared.isEmpty() && ackedAndPrepared.contains(node.getMessageId()); 333 } 334 335 @Override 336 protected void acknowledge(ConnectionContext context, MessageAck ack, final MessageReference node) throws IOException { 337 this.setTimeOfLastMessageAck(System.currentTimeMillis()); 338 Destination regionDestination = (Destination) node.getRegionDestination(); 339 regionDestination.acknowledge(context, this, ack, node); 340 redeliveredMessages.remove(node.getMessageId()); 341 node.decrementReferenceCount(); 342 if (context.isInTransaction() && context.getTransaction().getTransactionId().isXATransaction()) { 343 context.getTransaction().addSynchronization(new Synchronization() { 344 345 @Override 346 public void beforeCommit() throws Exception { 347 // post xa prepare call 348 synchronized (pendingLock) { 349 ackedAndPrepared.add(node.getMessageId()); 350 } 351 } 352 353 @Override 354 public void afterCommit() throws Exception { 355 synchronized (pendingLock) { 356 // may be in the cursor post activate/load from the store 357 pending.remove(node); 358 ackedAndPrepared.remove(node.getMessageId()); 359 } 360 } 361 362 @Override 363 public void afterRollback() throws Exception { 364 synchronized (pendingLock) { 365 ackedAndPrepared.remove(node.getMessageId()); 366 } 367 dispatchPending(); 368 } 369 }); 370 } 371 ((Destination)node.getRegionDestination()).getDestinationStatistics().getDequeues().increment(); 372 if (info.isNetworkSubscription()) { 373 ((Destination)node.getRegionDestination()).getDestinationStatistics().getForwards().add(ack.getMessageCount()); 374 } 375 } 376 377 @Override 378 public synchronized String toString() { 379 return "DurableTopicSubscription-" + getSubscriptionKey() + ", id=" + info.getConsumerId() + ", active=" + isActive() + ", destinations=" 380 + durableDestinations.size() + ", total=" + getSubscriptionStatistics().getEnqueues().getCount() + ", pending=" + getPendingQueueSize() + ", dispatched=" + getSubscriptionStatistics().getDispatched().getCount() 381 + ", inflight=" + dispatched.size() + ", prefetchExtension=" + getPrefetchExtension(); 382 } 383 384 public SubscriptionKey getSubscriptionKey() { 385 return subscriptionKey; 386 } 387 388 /** 389 * Release any references that we are holding. 390 */ 391 @Override 392 public void destroy() { 393 synchronized (pendingLock) { 394 try { 395 pending.reset(); 396 while (pending.hasNext()) { 397 MessageReference node = pending.next(); 398 node.decrementReferenceCount(); 399 } 400 } finally { 401 pending.release(); 402 pending.clear(); 403 } 404 } 405 synchronized (dispatchLock) { 406 for (MessageReference node : dispatched) { 407 node.decrementReferenceCount(); 408 } 409 dispatched.clear(); 410 ackedAndPrepared.clear(); 411 } 412 setSlowConsumer(false); 413 } 414 415 @Override 416 public void onUsageChanged(Usage usage, int oldPercentUsage, int newPercentUsage) { 417 if (oldPercentUsage > newPercentUsage && oldPercentUsage >= 90) { 418 try { 419 dispatchPending(); 420 } catch (IOException e) { 421 LOG.warn("problem calling dispatchMatched", e); 422 } 423 } 424 } 425 426 @Override 427 protected boolean isDropped(MessageReference node) { 428 return false; 429 } 430 431 public boolean isKeepDurableSubsActive() { 432 return keepDurableSubsActive; 433 } 434 435 public boolean isEnableMessageExpirationOnActiveDurableSubs() { 436 return enableMessageExpirationOnActiveDurableSubs; 437 } 438}