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