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