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