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.store.kahadb; 018 019import java.io.DataInputStream; 020import java.io.IOException; 021import java.io.InterruptedIOException; 022import java.util.ArrayList; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Iterator; 026import java.util.LinkedHashSet; 027import java.util.LinkedList; 028import java.util.List; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.Set; 032import java.util.concurrent.BlockingQueue; 033import java.util.concurrent.ExecutorService; 034import java.util.concurrent.FutureTask; 035import java.util.concurrent.LinkedBlockingQueue; 036import java.util.concurrent.Semaphore; 037import java.util.concurrent.ThreadFactory; 038import java.util.concurrent.ThreadPoolExecutor; 039import java.util.concurrent.TimeUnit; 040import java.util.concurrent.TimeoutException; 041import java.util.concurrent.atomic.AtomicBoolean; 042import java.util.concurrent.atomic.AtomicInteger; 043import java.util.concurrent.atomic.AtomicLong; 044import java.util.concurrent.atomic.AtomicReference; 045 046import org.apache.activemq.broker.ConnectionContext; 047import org.apache.activemq.broker.region.BaseDestination; 048import org.apache.activemq.broker.scheduler.JobSchedulerStore; 049import org.apache.activemq.command.ActiveMQDestination; 050import org.apache.activemq.command.ActiveMQQueue; 051import org.apache.activemq.command.ActiveMQTempQueue; 052import org.apache.activemq.command.ActiveMQTempTopic; 053import org.apache.activemq.command.ActiveMQTopic; 054import org.apache.activemq.command.Message; 055import org.apache.activemq.command.MessageAck; 056import org.apache.activemq.command.MessageId; 057import org.apache.activemq.command.ProducerId; 058import org.apache.activemq.command.SubscriptionInfo; 059import org.apache.activemq.command.TransactionId; 060import org.apache.activemq.openwire.OpenWireFormat; 061import org.apache.activemq.protobuf.Buffer; 062import org.apache.activemq.store.AbstractMessageStore; 063import org.apache.activemq.store.IndexListener; 064import org.apache.activemq.store.ListenableFuture; 065import org.apache.activemq.store.MessageRecoveryListener; 066import org.apache.activemq.store.MessageStore; 067import org.apache.activemq.store.MessageStoreStatistics; 068import org.apache.activemq.store.MessageStoreSubscriptionStatistics; 069import org.apache.activemq.store.NoLocalSubscriptionAware; 070import org.apache.activemq.store.PersistenceAdapter; 071import org.apache.activemq.store.ProxyMessageStore; 072import org.apache.activemq.store.TopicMessageStore; 073import org.apache.activemq.store.TransactionIdTransformer; 074import org.apache.activemq.store.TransactionStore; 075import org.apache.activemq.store.kahadb.data.KahaAddMessageCommand; 076import org.apache.activemq.store.kahadb.data.KahaDestination; 077import org.apache.activemq.store.kahadb.data.KahaDestination.DestinationType; 078import org.apache.activemq.store.kahadb.data.KahaLocation; 079import org.apache.activemq.store.kahadb.data.KahaRemoveDestinationCommand; 080import org.apache.activemq.store.kahadb.data.KahaRemoveMessageCommand; 081import org.apache.activemq.store.kahadb.data.KahaSubscriptionCommand; 082import org.apache.activemq.store.kahadb.data.KahaUpdateMessageCommand; 083import org.apache.activemq.store.kahadb.disk.journal.Location; 084import org.apache.activemq.store.kahadb.disk.page.Transaction; 085import org.apache.activemq.store.kahadb.disk.util.SequenceSet; 086import org.apache.activemq.store.kahadb.scheduler.JobSchedulerStoreImpl; 087import org.apache.activemq.usage.MemoryUsage; 088import org.apache.activemq.usage.SystemUsage; 089import org.apache.activemq.util.IOExceptionSupport; 090import org.apache.activemq.util.ServiceStopper; 091import org.apache.activemq.util.ThreadPoolUtils; 092import org.apache.activemq.wireformat.WireFormat; 093import org.slf4j.Logger; 094import org.slf4j.LoggerFactory; 095 096public class KahaDBStore extends MessageDatabase implements PersistenceAdapter, NoLocalSubscriptionAware { 097 static final Logger LOG = LoggerFactory.getLogger(KahaDBStore.class); 098 private static final int MAX_ASYNC_JOBS = BaseDestination.MAX_AUDIT_DEPTH; 099 100 public static final String PROPERTY_CANCELED_TASK_MOD_METRIC = "org.apache.activemq.store.kahadb.CANCELED_TASK_MOD_METRIC"; 101 public static final int cancelledTaskModMetric = Integer.parseInt(System.getProperty( 102 PROPERTY_CANCELED_TASK_MOD_METRIC, "0"), 10); 103 public static final String PROPERTY_ASYNC_EXECUTOR_MAX_THREADS = "org.apache.activemq.store.kahadb.ASYNC_EXECUTOR_MAX_THREADS"; 104 private static final int asyncExecutorMaxThreads = Integer.parseInt(System.getProperty( 105 PROPERTY_ASYNC_EXECUTOR_MAX_THREADS, "1"), 10);; 106 107 protected ExecutorService queueExecutor; 108 protected ExecutorService topicExecutor; 109 protected final List<Map<AsyncJobKey, StoreTask>> asyncQueueMaps = new LinkedList<Map<AsyncJobKey, StoreTask>>(); 110 protected final List<Map<AsyncJobKey, StoreTask>> asyncTopicMaps = new LinkedList<Map<AsyncJobKey, StoreTask>>(); 111 final WireFormat wireFormat = new OpenWireFormat(); 112 private SystemUsage usageManager; 113 private LinkedBlockingQueue<Runnable> asyncQueueJobQueue; 114 private LinkedBlockingQueue<Runnable> asyncTopicJobQueue; 115 Semaphore globalQueueSemaphore; 116 Semaphore globalTopicSemaphore; 117 private boolean concurrentStoreAndDispatchQueues = true; 118 // when true, message order may be compromised when cache is exhausted if store is out 119 // or order w.r.t cache 120 private boolean concurrentStoreAndDispatchTopics = false; 121 private final boolean concurrentStoreAndDispatchTransactions = false; 122 private int maxAsyncJobs = MAX_ASYNC_JOBS; 123 private final KahaDBTransactionStore transactionStore; 124 private TransactionIdTransformer transactionIdTransformer; 125 126 public KahaDBStore() { 127 this.transactionStore = new KahaDBTransactionStore(this); 128 this.transactionIdTransformer = new TransactionIdTransformer() { 129 @Override 130 public TransactionId transform(TransactionId txid) { 131 return txid; 132 } 133 }; 134 } 135 136 @Override 137 public String toString() { 138 return "KahaDB:[" + directory.getAbsolutePath() + "]"; 139 } 140 141 @Override 142 public void setBrokerName(String brokerName) { 143 } 144 145 @Override 146 public void setUsageManager(SystemUsage usageManager) { 147 this.usageManager = usageManager; 148 } 149 150 public SystemUsage getUsageManager() { 151 return this.usageManager; 152 } 153 154 /** 155 * @return the concurrentStoreAndDispatch 156 */ 157 public boolean isConcurrentStoreAndDispatchQueues() { 158 return this.concurrentStoreAndDispatchQueues; 159 } 160 161 /** 162 * @param concurrentStoreAndDispatch 163 * the concurrentStoreAndDispatch to set 164 */ 165 public void setConcurrentStoreAndDispatchQueues(boolean concurrentStoreAndDispatch) { 166 this.concurrentStoreAndDispatchQueues = concurrentStoreAndDispatch; 167 } 168 169 /** 170 * @return the concurrentStoreAndDispatch 171 */ 172 public boolean isConcurrentStoreAndDispatchTopics() { 173 return this.concurrentStoreAndDispatchTopics; 174 } 175 176 /** 177 * @param concurrentStoreAndDispatch 178 * the concurrentStoreAndDispatch to set 179 */ 180 public void setConcurrentStoreAndDispatchTopics(boolean concurrentStoreAndDispatch) { 181 this.concurrentStoreAndDispatchTopics = concurrentStoreAndDispatch; 182 } 183 184 public boolean isConcurrentStoreAndDispatchTransactions() { 185 return this.concurrentStoreAndDispatchTransactions; 186 } 187 188 /** 189 * @return the maxAsyncJobs 190 */ 191 public int getMaxAsyncJobs() { 192 return this.maxAsyncJobs; 193 } 194 195 /** 196 * @param maxAsyncJobs 197 * the maxAsyncJobs to set 198 */ 199 public void setMaxAsyncJobs(int maxAsyncJobs) { 200 this.maxAsyncJobs = maxAsyncJobs; 201 } 202 203 204 @Override 205 protected void configureMetadata() { 206 if (brokerService != null) { 207 metadata.openwireVersion = brokerService.getStoreOpenWireVersion(); 208 wireFormat.setVersion(metadata.openwireVersion); 209 210 if (LOG.isDebugEnabled()) { 211 LOG.debug("Store OpenWire version configured as: {}", metadata.openwireVersion); 212 } 213 214 } 215 } 216 217 @Override 218 public void doStart() throws Exception { 219 //configure the metadata before start, right now 220 //this is just the open wire version 221 configureMetadata(); 222 223 super.doStart(); 224 225 if (brokerService != null) { 226 // In case the recovered store used a different OpenWire version log a warning 227 // to assist in determining why journal reads fail. 228 if (metadata.openwireVersion != brokerService.getStoreOpenWireVersion()) { 229 LOG.warn("Existing Store uses a different OpenWire version[{}] " + 230 "than the version configured[{}] reverting to the version " + 231 "used by this store, some newer broker features may not work" + 232 "as expected.", 233 metadata.openwireVersion, brokerService.getStoreOpenWireVersion()); 234 235 // Update the broker service instance to the actual version in use. 236 wireFormat.setVersion(metadata.openwireVersion); 237 brokerService.setStoreOpenWireVersion(metadata.openwireVersion); 238 } 239 } 240 241 this.globalQueueSemaphore = new Semaphore(getMaxAsyncJobs()); 242 this.globalTopicSemaphore = new Semaphore(getMaxAsyncJobs()); 243 this.asyncQueueJobQueue = new LinkedBlockingQueue<Runnable>(getMaxAsyncJobs()); 244 this.asyncTopicJobQueue = new LinkedBlockingQueue<Runnable>(getMaxAsyncJobs()); 245 this.queueExecutor = new StoreTaskExecutor(1, asyncExecutorMaxThreads, 0L, TimeUnit.MILLISECONDS, 246 asyncQueueJobQueue, new ThreadFactory() { 247 @Override 248 public Thread newThread(Runnable runnable) { 249 Thread thread = new Thread(runnable, "ConcurrentQueueStoreAndDispatch"); 250 thread.setDaemon(true); 251 return thread; 252 } 253 }); 254 this.topicExecutor = new StoreTaskExecutor(1, asyncExecutorMaxThreads, 0L, TimeUnit.MILLISECONDS, 255 asyncTopicJobQueue, new ThreadFactory() { 256 @Override 257 public Thread newThread(Runnable runnable) { 258 Thread thread = new Thread(runnable, "ConcurrentTopicStoreAndDispatch"); 259 thread.setDaemon(true); 260 return thread; 261 } 262 }); 263 } 264 265 @Override 266 public void doStop(ServiceStopper stopper) throws Exception { 267 // drain down async jobs 268 LOG.info("Stopping async queue tasks"); 269 if (this.globalQueueSemaphore != null) { 270 this.globalQueueSemaphore.tryAcquire(this.maxAsyncJobs, 60, TimeUnit.SECONDS); 271 } 272 synchronized (this.asyncQueueMaps) { 273 for (Map<AsyncJobKey, StoreTask> m : asyncQueueMaps) { 274 synchronized (m) { 275 for (StoreTask task : m.values()) { 276 task.cancel(); 277 } 278 } 279 } 280 this.asyncQueueMaps.clear(); 281 } 282 LOG.info("Stopping async topic tasks"); 283 if (this.globalTopicSemaphore != null) { 284 this.globalTopicSemaphore.tryAcquire(this.maxAsyncJobs, 60, TimeUnit.SECONDS); 285 } 286 synchronized (this.asyncTopicMaps) { 287 for (Map<AsyncJobKey, StoreTask> m : asyncTopicMaps) { 288 synchronized (m) { 289 for (StoreTask task : m.values()) { 290 task.cancel(); 291 } 292 } 293 } 294 this.asyncTopicMaps.clear(); 295 } 296 if (this.globalQueueSemaphore != null) { 297 this.globalQueueSemaphore.drainPermits(); 298 } 299 if (this.globalTopicSemaphore != null) { 300 this.globalTopicSemaphore.drainPermits(); 301 } 302 if (this.queueExecutor != null) { 303 ThreadPoolUtils.shutdownNow(queueExecutor); 304 queueExecutor = null; 305 } 306 if (this.topicExecutor != null) { 307 ThreadPoolUtils.shutdownNow(topicExecutor); 308 topicExecutor = null; 309 } 310 LOG.info("Stopped KahaDB"); 311 super.doStop(stopper); 312 } 313 314 private Location findMessageLocation(final String key, final KahaDestination destination) throws IOException { 315 return pageFile.tx().execute(new Transaction.CallableClosure<Location, IOException>() { 316 @Override 317 public Location execute(Transaction tx) throws IOException { 318 StoredDestination sd = getStoredDestination(destination, tx); 319 Long sequence = sd.messageIdIndex.get(tx, key); 320 if (sequence == null) { 321 return null; 322 } 323 return sd.orderIndex.get(tx, sequence).location; 324 } 325 }); 326 } 327 328 protected StoreQueueTask removeQueueTask(KahaDBMessageStore store, MessageId id) { 329 StoreQueueTask task = null; 330 synchronized (store.asyncTaskMap) { 331 task = (StoreQueueTask) store.asyncTaskMap.remove(new AsyncJobKey(id, store.getDestination())); 332 } 333 return task; 334 } 335 336 // with asyncTaskMap locked 337 protected void addQueueTask(KahaDBMessageStore store, StoreQueueTask task) throws IOException { 338 store.asyncTaskMap.put(new AsyncJobKey(task.getMessage().getMessageId(), store.getDestination()), task); 339 this.queueExecutor.execute(task); 340 } 341 342 protected StoreTopicTask removeTopicTask(KahaDBTopicMessageStore store, MessageId id) { 343 StoreTopicTask task = null; 344 synchronized (store.asyncTaskMap) { 345 task = (StoreTopicTask) store.asyncTaskMap.remove(new AsyncJobKey(id, store.getDestination())); 346 } 347 return task; 348 } 349 350 protected void addTopicTask(KahaDBTopicMessageStore store, StoreTopicTask task) throws IOException { 351 synchronized (store.asyncTaskMap) { 352 store.asyncTaskMap.put(new AsyncJobKey(task.getMessage().getMessageId(), store.getDestination()), task); 353 } 354 this.topicExecutor.execute(task); 355 } 356 357 @Override 358 public TransactionStore createTransactionStore() throws IOException { 359 return this.transactionStore; 360 } 361 362 public boolean getForceRecoverIndex() { 363 return this.forceRecoverIndex; 364 } 365 366 public void setForceRecoverIndex(boolean forceRecoverIndex) { 367 this.forceRecoverIndex = forceRecoverIndex; 368 } 369 370 public void forgetRecoveredAcks(ArrayList<MessageAck> preparedAcks, boolean isRollback) throws IOException { 371 if (preparedAcks != null) { 372 Map<ActiveMQDestination, KahaDBMessageStore> stores = new HashMap<>(); 373 for (MessageAck ack : preparedAcks) { 374 stores.put(ack.getDestination(), findMatchingStore(ack.getDestination())); 375 } 376 ArrayList<MessageAck> perStoreAcks = new ArrayList<>(); 377 for (Entry<ActiveMQDestination, KahaDBMessageStore> entry : stores.entrySet()) { 378 for (MessageAck ack : preparedAcks) { 379 if (entry.getKey().equals(ack.getDestination())) { 380 perStoreAcks.add(ack); 381 } 382 } 383 entry.getValue().forgetRecoveredAcks(perStoreAcks, isRollback); 384 perStoreAcks.clear(); 385 } 386 } 387 } 388 389 public void trackRecoveredAcks(ArrayList<MessageAck> preparedAcks) throws IOException { 390 Map<ActiveMQDestination, KahaDBMessageStore> stores = new HashMap<>(); 391 for (MessageAck ack : preparedAcks) { 392 stores.put(ack.getDestination(), findMatchingStore(ack.getDestination())); 393 } 394 ArrayList<MessageAck> perStoreAcks = new ArrayList<>(); 395 for (Entry<ActiveMQDestination, KahaDBMessageStore> entry : stores.entrySet()) { 396 for (MessageAck ack : preparedAcks) { 397 if (entry.getKey().equals(ack.getDestination())) { 398 perStoreAcks.add(ack); 399 } 400 } 401 entry.getValue().trackRecoveredAcks(perStoreAcks); 402 perStoreAcks.clear(); 403 } 404 } 405 406 private KahaDBMessageStore findMatchingStore(ActiveMQDestination activeMQDestination) throws IOException { 407 ProxyMessageStore store = (ProxyMessageStore) storeCache.get(key(convert(activeMQDestination))); 408 if (store == null) { 409 if (activeMQDestination.isQueue()) { 410 store = (ProxyMessageStore) createQueueMessageStore((ActiveMQQueue) activeMQDestination); 411 } else { 412 store = (ProxyMessageStore) createTopicMessageStore((ActiveMQTopic) activeMQDestination); 413 } 414 } 415 return (KahaDBMessageStore) store.getDelegate(); 416 } 417 418 public class KahaDBMessageStore extends AbstractMessageStore { 419 protected final Map<AsyncJobKey, StoreTask> asyncTaskMap = new HashMap<AsyncJobKey, StoreTask>(); 420 protected KahaDestination dest; 421 private final int maxAsyncJobs; 422 private final Semaphore localDestinationSemaphore; 423 protected final HashMap<String, Set<String>> ackedAndPreparedMap = new HashMap<String, Set<String>>(); 424 protected final HashMap<String, Set<String>> rolledBackAcksMap = new HashMap<String, Set<String>>(); 425 426 double doneTasks, canceledTasks = 0; 427 428 public KahaDBMessageStore(ActiveMQDestination destination) { 429 super(destination); 430 this.dest = convert(destination); 431 this.maxAsyncJobs = getMaxAsyncJobs(); 432 this.localDestinationSemaphore = new Semaphore(this.maxAsyncJobs); 433 } 434 435 @Override 436 public ActiveMQDestination getDestination() { 437 return destination; 438 } 439 440 441 private final String recoveredTxStateMapKey(ActiveMQDestination destination, MessageAck ack) { 442 return destination.isQueue() ? destination.getPhysicalName() : ack.getConsumerId().getConnectionId(); 443 } 444 445 // messages that have prepared (pending) acks cannot be re-dispatched unless the outcome is rollback, 446 // till then they are skipped by the store. 447 // 'at most once' XA guarantee 448 public void trackRecoveredAcks(ArrayList<MessageAck> acks) { 449 indexLock.writeLock().lock(); 450 try { 451 for (MessageAck ack : acks) { 452 final String key = recoveredTxStateMapKey(destination, ack); 453 Set ackedAndPrepared = ackedAndPreparedMap.get(key); 454 if (ackedAndPrepared == null) { 455 ackedAndPrepared = new LinkedHashSet<String>(); 456 ackedAndPreparedMap.put(key, ackedAndPrepared); 457 } 458 ackedAndPrepared.add(ack.getLastMessageId().toProducerKey()); 459 } 460 } finally { 461 indexLock.writeLock().unlock(); 462 } 463 } 464 465 public void forgetRecoveredAcks(ArrayList<MessageAck> acks, boolean rollback) throws IOException { 466 if (acks != null) { 467 indexLock.writeLock().lock(); 468 try { 469 for (MessageAck ack : acks) { 470 final String id = ack.getLastMessageId().toProducerKey(); 471 final String key = recoveredTxStateMapKey(destination, ack); 472 Set ackedAndPrepared = ackedAndPreparedMap.get(key); 473 if (ackedAndPrepared != null) { 474 ackedAndPrepared.remove(id); 475 if (ackedAndPreparedMap.isEmpty()) { 476 ackedAndPreparedMap.remove(key); 477 } 478 } 479 if (rollback) { 480 Set rolledBackAcks = rolledBackAcksMap.get(key); 481 if (rolledBackAcks == null) { 482 rolledBackAcks = new LinkedHashSet<String>(); 483 rolledBackAcksMap.put(key, rolledBackAcks); 484 } 485 rolledBackAcks.add(id); 486 pageFile.tx().execute(tx -> { 487 incrementAndAddSizeToStoreStat(tx, dest, 0); 488 }); 489 } 490 } 491 } finally { 492 indexLock.writeLock().unlock(); 493 } 494 } 495 } 496 497 @Override 498 public ListenableFuture<Object> asyncAddQueueMessage(final ConnectionContext context, final Message message) 499 throws IOException { 500 if (isConcurrentStoreAndDispatchQueues()) { 501 message.beforeMarshall(wireFormat); 502 StoreQueueTask result = new StoreQueueTask(this, context, message); 503 ListenableFuture<Object> future = result.getFuture(); 504 message.getMessageId().setFutureOrSequenceLong(future); 505 message.setRecievedByDFBridge(true); // flag message as concurrentStoreAndDispatch 506 result.aquireLocks(); 507 synchronized (asyncTaskMap) { 508 addQueueTask(this, result); 509 if (indexListener != null) { 510 indexListener.onAdd(new IndexListener.MessageContext(context, message, null)); 511 } 512 } 513 return future; 514 } else { 515 return super.asyncAddQueueMessage(context, message); 516 } 517 } 518 519 @Override 520 public void removeAsyncMessage(ConnectionContext context, MessageAck ack) throws IOException { 521 if (isConcurrentStoreAndDispatchQueues()) { 522 AsyncJobKey key = new AsyncJobKey(ack.getLastMessageId(), getDestination()); 523 StoreQueueTask task = null; 524 synchronized (asyncTaskMap) { 525 task = (StoreQueueTask) asyncTaskMap.get(key); 526 } 527 if (task != null) { 528 if (ack.isInTransaction() || !task.cancel()) { 529 try { 530 task.future.get(); 531 } catch (InterruptedException e) { 532 throw new InterruptedIOException(e.toString()); 533 } catch (Exception ignored) { 534 LOG.debug("removeAsync: cannot cancel, waiting for add resulted in ex", ignored); 535 } 536 removeMessage(context, ack); 537 } else { 538 indexLock.writeLock().lock(); 539 try { 540 metadata.producerSequenceIdTracker.isDuplicate(ack.getLastMessageId()); 541 } finally { 542 indexLock.writeLock().unlock(); 543 } 544 synchronized (asyncTaskMap) { 545 asyncTaskMap.remove(key); 546 } 547 } 548 } else { 549 removeMessage(context, ack); 550 } 551 } else { 552 removeMessage(context, ack); 553 } 554 } 555 556 @Override 557 public void addMessage(final ConnectionContext context, final Message message) throws IOException { 558 final KahaAddMessageCommand command = new KahaAddMessageCommand(); 559 command.setDestination(dest); 560 command.setMessageId(message.getMessageId().toProducerKey()); 561 command.setTransactionInfo(TransactionIdConversion.convert(transactionIdTransformer.transform(message.getTransactionId()))); 562 command.setPriority(message.getPriority()); 563 command.setPrioritySupported(isPrioritizedMessages()); 564 org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(message); 565 command.setMessage(new Buffer(packet.getData(), packet.getOffset(), packet.getLength())); 566 store(command, isEnableJournalDiskSyncs() && message.isResponseRequired(), new IndexAware() { 567 // sync add? (for async, future present from getFutureOrSequenceLong) 568 Object possibleFuture = message.getMessageId().getFutureOrSequenceLong(); 569 570 @Override 571 public void sequenceAssignedWithIndexLocked(final long sequence) { 572 message.getMessageId().setFutureOrSequenceLong(sequence); 573 if (indexListener != null) { 574 if (possibleFuture == null) { 575 trackPendingAdd(dest, sequence); 576 indexListener.onAdd(new IndexListener.MessageContext(context, message, new Runnable() { 577 @Override 578 public void run() { 579 trackPendingAddComplete(dest, sequence); 580 } 581 })); 582 } 583 } 584 } 585 }, null); 586 } 587 588 @Override 589 public void updateMessage(Message message) throws IOException { 590 if (LOG.isTraceEnabled()) { 591 LOG.trace("updating: " + message.getMessageId() + " with deliveryCount: " + message.getRedeliveryCounter()); 592 } 593 KahaUpdateMessageCommand updateMessageCommand = new KahaUpdateMessageCommand(); 594 KahaAddMessageCommand command = new KahaAddMessageCommand(); 595 command.setDestination(dest); 596 command.setMessageId(message.getMessageId().toProducerKey()); 597 command.setPriority(message.getPriority()); 598 command.setPrioritySupported(prioritizedMessages); 599 org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(message); 600 command.setMessage(new Buffer(packet.getData(), packet.getOffset(), packet.getLength())); 601 updateMessageCommand.setMessage(command); 602 store(updateMessageCommand, isEnableJournalDiskSyncs(), null, null); 603 } 604 605 @Override 606 public void removeMessage(ConnectionContext context, MessageAck ack) throws IOException { 607 KahaRemoveMessageCommand command = new KahaRemoveMessageCommand(); 608 command.setDestination(dest); 609 command.setMessageId(ack.getLastMessageId().toProducerKey()); 610 command.setTransactionInfo(TransactionIdConversion.convert(transactionIdTransformer.transform(ack.getTransactionId()))); 611 612 org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(ack); 613 command.setAck(new Buffer(packet.getData(), packet.getOffset(), packet.getLength())); 614 store(command, isEnableJournalDiskSyncs() && ack.isResponseRequired(), null, null); 615 } 616 617 @Override 618 public void removeAllMessages(ConnectionContext context) throws IOException { 619 KahaRemoveDestinationCommand command = new KahaRemoveDestinationCommand(); 620 command.setDestination(dest); 621 store(command, true, null, null); 622 } 623 624 @Override 625 public Message getMessage(MessageId identity) throws IOException { 626 final String key = identity.toProducerKey(); 627 628 // Hopefully one day the page file supports concurrent read 629 // operations... but for now we must 630 // externally synchronize... 631 Location location; 632 indexLock.writeLock().lock(); 633 try { 634 location = findMessageLocation(key, dest); 635 } finally { 636 indexLock.writeLock().unlock(); 637 } 638 if (location == null) { 639 return null; 640 } 641 642 return loadMessage(location); 643 } 644 645 @Override 646 public boolean isEmpty() throws IOException { 647 indexLock.writeLock().lock(); 648 try { 649 return pageFile.tx().execute(new Transaction.CallableClosure<Boolean, IOException>() { 650 @Override 651 public Boolean execute(Transaction tx) throws IOException { 652 // Iterate through all index entries to get a count of 653 // messages in the destination. 654 StoredDestination sd = getStoredDestination(dest, tx); 655 return sd.locationIndex.isEmpty(tx); 656 } 657 }); 658 } finally { 659 indexLock.writeLock().unlock(); 660 } 661 } 662 663 @Override 664 public void recover(final MessageRecoveryListener listener) throws Exception { 665 // recovery may involve expiry which will modify 666 indexLock.writeLock().lock(); 667 try { 668 pageFile.tx().execute(new Transaction.Closure<Exception>() { 669 @Override 670 public void execute(Transaction tx) throws Exception { 671 StoredDestination sd = getStoredDestination(dest, tx); 672 recoverRolledBackAcks(destination.getPhysicalName(), sd, tx, Integer.MAX_VALUE, listener); 673 sd.orderIndex.resetCursorPosition(); 674 for (Iterator<Entry<Long, MessageKeys>> iterator = sd.orderIndex.iterator(tx); listener.hasSpace() && iterator 675 .hasNext(); ) { 676 Entry<Long, MessageKeys> entry = iterator.next(); 677 Set ackedAndPrepared = ackedAndPreparedMap.get(destination.getPhysicalName()); 678 if (ackedAndPrepared != null && ackedAndPrepared.contains(entry.getValue().messageId)) { 679 continue; 680 } 681 Message msg = loadMessage(entry.getValue().location); 682 listener.recoverMessage(msg); 683 } 684 } 685 }); 686 } finally { 687 indexLock.writeLock().unlock(); 688 } 689 } 690 691 @Override 692 public void recoverNextMessages(final int maxReturned, final MessageRecoveryListener listener) throws Exception { 693 indexLock.writeLock().lock(); 694 try { 695 pageFile.tx().execute(new Transaction.Closure<Exception>() { 696 @Override 697 public void execute(Transaction tx) throws Exception { 698 StoredDestination sd = getStoredDestination(dest, tx); 699 Entry<Long, MessageKeys> entry = null; 700 int counter = recoverRolledBackAcks(destination.getPhysicalName(), sd, tx, maxReturned, listener); 701 Set ackedAndPrepared = ackedAndPreparedMap.get(destination.getPhysicalName()); 702 for (Iterator<Entry<Long, MessageKeys>> iterator = sd.orderIndex.iterator(tx); iterator.hasNext(); ) { 703 entry = iterator.next(); 704 if (ackedAndPrepared != null && ackedAndPrepared.contains(entry.getValue().messageId)) { 705 continue; 706 } 707 Message msg = loadMessage(entry.getValue().location); 708 msg.getMessageId().setFutureOrSequenceLong(entry.getKey()); 709 listener.recoverMessage(msg); 710 counter++; 711 if (counter >= maxReturned || !listener.canRecoveryNextMessage()) { 712 break; 713 } 714 } 715 sd.orderIndex.stoppedIterating(); 716 } 717 }); 718 } finally { 719 indexLock.writeLock().unlock(); 720 } 721 } 722 723 protected int recoverRolledBackAcks(String recoveredTxStateMapKey, StoredDestination sd, Transaction tx, int maxReturned, MessageRecoveryListener listener) throws Exception { 724 int counter = 0; 725 String id; 726 727 Set rolledBackAcks = rolledBackAcksMap.get(recoveredTxStateMapKey); 728 if (rolledBackAcks == null) { 729 return counter; 730 } 731 for (Iterator<String> iterator = rolledBackAcks.iterator(); iterator.hasNext(); ) { 732 id = iterator.next(); 733 iterator.remove(); 734 Long sequence = sd.messageIdIndex.get(tx, id); 735 if (sequence != null) { 736 if (sd.orderIndex.alreadyDispatched(sequence)) { 737 listener.recoverMessage(loadMessage(sd.orderIndex.get(tx, sequence).location)); 738 counter++; 739 if (counter >= maxReturned) { 740 break; 741 } 742 } else { 743 LOG.debug("rolledback ack message {} with seq {} will be picked up in future batch {}", id, sequence, sd.orderIndex.cursor); 744 } 745 } else { 746 LOG.warn("Failed to locate rolled back ack message {} in {}", id, sd); 747 } 748 } 749 if (rolledBackAcks.isEmpty()) { 750 rolledBackAcksMap.remove(recoveredTxStateMapKey); 751 } 752 return counter; 753 } 754 755 756 @Override 757 public void resetBatching() { 758 if (pageFile.isLoaded()) { 759 indexLock.writeLock().lock(); 760 try { 761 pageFile.tx().execute(new Transaction.Closure<Exception>() { 762 @Override 763 public void execute(Transaction tx) throws Exception { 764 StoredDestination sd = getExistingStoredDestination(dest, tx); 765 if (sd != null) { 766 sd.orderIndex.resetCursorPosition();} 767 } 768 }); 769 } catch (Exception e) { 770 LOG.error("Failed to reset batching",e); 771 } finally { 772 indexLock.writeLock().unlock(); 773 } 774 } 775 } 776 777 @Override 778 public void setBatch(final MessageId identity) throws IOException { 779 indexLock.writeLock().lock(); 780 try { 781 pageFile.tx().execute(new Transaction.Closure<IOException>() { 782 @Override 783 public void execute(Transaction tx) throws IOException { 784 StoredDestination sd = getStoredDestination(dest, tx); 785 Long location = (Long) identity.getFutureOrSequenceLong(); 786 Long pending = sd.orderIndex.minPendingAdd(); 787 if (pending != null) { 788 location = Math.min(location, pending-1); 789 } 790 sd.orderIndex.setBatch(tx, location); 791 } 792 }); 793 } finally { 794 indexLock.writeLock().unlock(); 795 } 796 } 797 798 @Override 799 public void setMemoryUsage(MemoryUsage memoryUsage) { 800 } 801 @Override 802 public void start() throws Exception { 803 super.start(); 804 } 805 @Override 806 public void stop() throws Exception { 807 super.stop(); 808 } 809 810 protected void lockAsyncJobQueue() { 811 try { 812 if (!this.localDestinationSemaphore.tryAcquire(this.maxAsyncJobs, 60, TimeUnit.SECONDS)) { 813 throw new TimeoutException(this +" timeout waiting for localDestSem:" + this.localDestinationSemaphore); 814 } 815 } catch (Exception e) { 816 LOG.error("Failed to lock async jobs for " + this.destination, e); 817 } 818 } 819 820 protected void unlockAsyncJobQueue() { 821 this.localDestinationSemaphore.release(this.maxAsyncJobs); 822 } 823 824 protected void acquireLocalAsyncLock() { 825 try { 826 this.localDestinationSemaphore.acquire(); 827 } catch (InterruptedException e) { 828 LOG.error("Failed to aquire async lock for " + this.destination, e); 829 } 830 } 831 832 protected void releaseLocalAsyncLock() { 833 this.localDestinationSemaphore.release(); 834 } 835 836 @Override 837 public String toString(){ 838 return "permits:" + this.localDestinationSemaphore.availablePermits() + ",sd=" + storedDestinations.get(key(dest)); 839 } 840 841 @Override 842 protected void recoverMessageStoreStatistics() throws IOException { 843 try { 844 MessageStoreStatistics recoveredStatistics; 845 lockAsyncJobQueue(); 846 indexLock.writeLock().lock(); 847 try { 848 recoveredStatistics = pageFile.tx().execute(new Transaction.CallableClosure<MessageStoreStatistics, IOException>() { 849 @Override 850 public MessageStoreStatistics execute(Transaction tx) throws IOException { 851 MessageStoreStatistics statistics = getStoredMessageStoreStatistics(dest, tx); 852 853 // Iterate through all index entries to get the size of each message 854 if (statistics == null) { 855 StoredDestination sd = getStoredDestination(dest, tx); 856 statistics = new MessageStoreStatistics(); 857 for (Iterator<Entry<Location, Long>> iterator = sd.locationIndex.iterator(tx); iterator.hasNext(); ) { 858 int locationSize = iterator.next().getKey().getSize(); 859 statistics.getMessageCount().increment(); 860 statistics.getMessageSize().addSize(locationSize > 0 ? locationSize : 0); 861 } 862 } 863 return statistics; 864 } 865 }); 866 Set ackedAndPrepared = ackedAndPreparedMap.get(destination.getPhysicalName()); 867 if (ackedAndPrepared != null) { 868 recoveredStatistics.getMessageCount().subtract(ackedAndPrepared.size()); 869 } 870 getMessageStoreStatistics().getMessageCount().setCount(recoveredStatistics.getMessageCount().getCount()); 871 getMessageStoreStatistics().getMessageSize().setTotalSize(recoveredStatistics.getMessageSize().getTotalSize()); 872 } finally { 873 indexLock.writeLock().unlock(); 874 } 875 } finally { 876 unlockAsyncJobQueue(); 877 } 878 } 879 } 880 881 class KahaDBTopicMessageStore extends KahaDBMessageStore implements TopicMessageStore { 882 private final AtomicInteger subscriptionCount = new AtomicInteger(); 883 protected final MessageStoreSubscriptionStatistics messageStoreSubStats = 884 new MessageStoreSubscriptionStatistics(isEnableSubscriptionStatistics()); 885 886 public KahaDBTopicMessageStore(ActiveMQTopic destination) throws IOException { 887 super(destination); 888 this.subscriptionCount.set(getAllSubscriptions().length); 889 if (isConcurrentStoreAndDispatchTopics()) { 890 asyncTopicMaps.add(asyncTaskMap); 891 } 892 } 893 894 @Override 895 protected void recoverMessageStoreStatistics() throws IOException { 896 super.recoverMessageStoreStatistics(); 897 this.recoverMessageStoreSubMetrics(); 898 } 899 900 @Override 901 public ListenableFuture<Object> asyncAddTopicMessage(final ConnectionContext context, final Message message) 902 throws IOException { 903 if (isConcurrentStoreAndDispatchTopics()) { 904 message.beforeMarshall(wireFormat); 905 StoreTopicTask result = new StoreTopicTask(this, context, message, subscriptionCount.get()); 906 result.aquireLocks(); 907 addTopicTask(this, result); 908 return result.getFuture(); 909 } else { 910 return super.asyncAddTopicMessage(context, message); 911 } 912 } 913 914 @Override 915 public void acknowledge(ConnectionContext context, String clientId, String subscriptionName, 916 MessageId messageId, MessageAck ack) throws IOException { 917 String subscriptionKey = subscriptionKey(clientId, subscriptionName).toString(); 918 if (isConcurrentStoreAndDispatchTopics()) { 919 AsyncJobKey key = new AsyncJobKey(messageId, getDestination()); 920 StoreTopicTask task = null; 921 synchronized (asyncTaskMap) { 922 task = (StoreTopicTask) asyncTaskMap.get(key); 923 } 924 if (task != null) { 925 if (task.addSubscriptionKey(subscriptionKey)) { 926 removeTopicTask(this, messageId); 927 if (task.cancel()) { 928 synchronized (asyncTaskMap) { 929 asyncTaskMap.remove(key); 930 } 931 } 932 } 933 } else { 934 doAcknowledge(context, subscriptionKey, messageId, ack); 935 } 936 } else { 937 doAcknowledge(context, subscriptionKey, messageId, ack); 938 } 939 } 940 941 protected void doAcknowledge(ConnectionContext context, String subscriptionKey, MessageId messageId, MessageAck ack) 942 throws IOException { 943 KahaRemoveMessageCommand command = new KahaRemoveMessageCommand(); 944 command.setDestination(dest); 945 command.setSubscriptionKey(subscriptionKey); 946 command.setMessageId(messageId.toProducerKey()); 947 command.setTransactionInfo(ack != null ? TransactionIdConversion.convert(transactionIdTransformer.transform(ack.getTransactionId())) : null); 948 if (ack != null && ack.isUnmatchedAck()) { 949 command.setAck(UNMATCHED); 950 } else { 951 org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(ack); 952 command.setAck(new Buffer(packet.getData(), packet.getOffset(), packet.getLength())); 953 } 954 store(command, false, null, null); 955 } 956 957 @Override 958 public void addSubscription(SubscriptionInfo subscriptionInfo, boolean retroactive) throws IOException { 959 String subscriptionKey = subscriptionKey(subscriptionInfo.getClientId(), subscriptionInfo 960 .getSubscriptionName()); 961 KahaSubscriptionCommand command = new KahaSubscriptionCommand(); 962 command.setDestination(dest); 963 command.setSubscriptionKey(subscriptionKey.toString()); 964 command.setRetroactive(retroactive); 965 org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(subscriptionInfo); 966 command.setSubscriptionInfo(new Buffer(packet.getData(), packet.getOffset(), packet.getLength())); 967 store(command, isEnableJournalDiskSyncs() && true, null, null); 968 this.subscriptionCount.incrementAndGet(); 969 } 970 971 @Override 972 public void deleteSubscription(String clientId, String subscriptionName) throws IOException { 973 KahaSubscriptionCommand command = new KahaSubscriptionCommand(); 974 command.setDestination(dest); 975 command.setSubscriptionKey(subscriptionKey(clientId, subscriptionName).toString()); 976 store(command, isEnableJournalDiskSyncs() && true, null, null); 977 this.subscriptionCount.decrementAndGet(); 978 } 979 980 @Override 981 public SubscriptionInfo[] getAllSubscriptions() throws IOException { 982 983 final ArrayList<SubscriptionInfo> subscriptions = new ArrayList<SubscriptionInfo>(); 984 indexLock.writeLock().lock(); 985 try { 986 pageFile.tx().execute(new Transaction.Closure<IOException>() { 987 @Override 988 public void execute(Transaction tx) throws IOException { 989 StoredDestination sd = getStoredDestination(dest, tx); 990 for (Iterator<Entry<String, KahaSubscriptionCommand>> iterator = sd.subscriptions.iterator(tx); iterator 991 .hasNext();) { 992 Entry<String, KahaSubscriptionCommand> entry = iterator.next(); 993 SubscriptionInfo info = (SubscriptionInfo) wireFormat.unmarshal(new DataInputStream(entry 994 .getValue().getSubscriptionInfo().newInput())); 995 subscriptions.add(info); 996 997 } 998 } 999 }); 1000 } finally { 1001 indexLock.writeLock().unlock(); 1002 } 1003 1004 SubscriptionInfo[] rc = new SubscriptionInfo[subscriptions.size()]; 1005 subscriptions.toArray(rc); 1006 return rc; 1007 } 1008 1009 @Override 1010 public SubscriptionInfo lookupSubscription(String clientId, String subscriptionName) throws IOException { 1011 final String subscriptionKey = subscriptionKey(clientId, subscriptionName); 1012 indexLock.writeLock().lock(); 1013 try { 1014 return pageFile.tx().execute(new Transaction.CallableClosure<SubscriptionInfo, IOException>() { 1015 @Override 1016 public SubscriptionInfo execute(Transaction tx) throws IOException { 1017 StoredDestination sd = getStoredDestination(dest, tx); 1018 KahaSubscriptionCommand command = sd.subscriptions.get(tx, subscriptionKey); 1019 if (command == null) { 1020 return null; 1021 } 1022 return (SubscriptionInfo) wireFormat.unmarshal(new DataInputStream(command 1023 .getSubscriptionInfo().newInput())); 1024 } 1025 }); 1026 } finally { 1027 indexLock.writeLock().unlock(); 1028 } 1029 } 1030 1031 @Override 1032 public int getMessageCount(String clientId, String subscriptionName) throws IOException { 1033 final String subscriptionKey = subscriptionKey(clientId, subscriptionName); 1034 1035 if (isEnableSubscriptionStatistics()) { 1036 return (int)this.messageStoreSubStats.getMessageCount(subscriptionKey).getCount(); 1037 } else { 1038 1039 indexLock.writeLock().lock(); 1040 try { 1041 return pageFile.tx().execute(new Transaction.CallableClosure<Integer, IOException>() { 1042 @Override 1043 public Integer execute(Transaction tx) throws IOException { 1044 StoredDestination sd = getStoredDestination(dest, tx); 1045 LastAck cursorPos = getLastAck(tx, sd, subscriptionKey); 1046 if (cursorPos == null) { 1047 // The subscription might not exist. 1048 return 0; 1049 } 1050 1051 return (int) getStoredMessageCount(tx, sd, subscriptionKey); 1052 } 1053 }); 1054 } finally { 1055 indexLock.writeLock().unlock(); 1056 } 1057 } 1058 } 1059 1060 1061 @Override 1062 public long getMessageSize(String clientId, String subscriptionName) throws IOException { 1063 final String subscriptionKey = subscriptionKey(clientId, subscriptionName); 1064 if (isEnableSubscriptionStatistics()) { 1065 return this.messageStoreSubStats.getMessageSize(subscriptionKey).getTotalSize(); 1066 } else { 1067 indexLock.writeLock().lock(); 1068 try { 1069 return pageFile.tx().execute(new Transaction.CallableClosure<Long, IOException>() { 1070 @Override 1071 public Long execute(Transaction tx) throws IOException { 1072 StoredDestination sd = getStoredDestination(dest, tx); 1073 LastAck cursorPos = getLastAck(tx, sd, subscriptionKey); 1074 if (cursorPos == null) { 1075 // The subscription might not exist. 1076 return 0l; 1077 } 1078 1079 return getStoredMessageSize(tx, sd, subscriptionKey); 1080 } 1081 }); 1082 } finally { 1083 indexLock.writeLock().unlock(); 1084 } 1085 } 1086 } 1087 1088 protected void recoverMessageStoreSubMetrics() throws IOException { 1089 if (isEnableSubscriptionStatistics()) { 1090 final MessageStoreSubscriptionStatistics statistics = getMessageStoreSubStatistics(); 1091 indexLock.writeLock().lock(); 1092 try { 1093 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1094 @Override 1095 public void execute(Transaction tx) throws IOException { 1096 StoredDestination sd = getStoredDestination(dest, tx); 1097 1098 List<String> subscriptionKeys = new ArrayList<>(); 1099 for (Iterator<Entry<String, KahaSubscriptionCommand>> iterator = sd.subscriptions 1100 .iterator(tx); iterator.hasNext();) { 1101 Entry<String, KahaSubscriptionCommand> entry = iterator.next(); 1102 1103 final String subscriptionKey = entry.getKey(); 1104 final LastAck cursorPos = getLastAck(tx, sd, subscriptionKey); 1105 if (cursorPos != null) { 1106 //add the subscriptions to a list for recovering pending sizes below 1107 subscriptionKeys.add(subscriptionKey); 1108 //recover just the count here as that is fast 1109 statistics.getMessageCount(subscriptionKey) 1110 .setCount(getStoredMessageCount(tx, sd, subscriptionKey)); 1111 } 1112 } 1113 1114 //Recover the message sizes for each subscription by iterating only 1 time over the order index 1115 //to speed up recovery 1116 final Map<String, AtomicLong> subPendingMessageSizes = getStoredMessageSize(tx, sd, subscriptionKeys); 1117 subPendingMessageSizes.forEach((k,v) -> { 1118 statistics.getMessageSize(k).addSize(v.get() > 0 ? v.get() : 0); 1119 }); 1120 } 1121 }); 1122 } finally { 1123 indexLock.writeLock().unlock(); 1124 } 1125 } 1126 } 1127 1128 @Override 1129 public void recoverSubscription(String clientId, String subscriptionName, final MessageRecoveryListener listener) 1130 throws Exception { 1131 final String subscriptionKey = subscriptionKey(clientId, subscriptionName); 1132 @SuppressWarnings("unused") 1133 final SubscriptionInfo info = lookupSubscription(clientId, subscriptionName); 1134 indexLock.writeLock().lock(); 1135 try { 1136 pageFile.tx().execute(new Transaction.Closure<Exception>() { 1137 @Override 1138 public void execute(Transaction tx) throws Exception { 1139 StoredDestination sd = getStoredDestination(dest, tx); 1140 LastAck cursorPos = getLastAck(tx, sd, subscriptionKey); 1141 SequenceSet subAckPositions = getSequenceSet(tx, sd, subscriptionKey); 1142 //If we have ackPositions tracked then compare the first one as individual acknowledge mode 1143 //may have bumped lastAck even though there are earlier messages to still consume 1144 if (subAckPositions != null && !subAckPositions.isEmpty() 1145 && subAckPositions.getHead().getFirst() < cursorPos.lastAckedSequence) { 1146 //we have messages to ack before lastAckedSequence 1147 sd.orderIndex.setBatch(tx, subAckPositions.getHead().getFirst() - 1); 1148 } else { 1149 subAckPositions = null; 1150 sd.orderIndex.setBatch(tx, cursorPos); 1151 } 1152 recoverRolledBackAcks(subscriptionKey, sd, tx, Integer.MAX_VALUE, listener); 1153 Set ackedAndPrepared = ackedAndPreparedMap.get(subscriptionKey); 1154 for (Iterator<Entry<Long, MessageKeys>> iterator = sd.orderIndex.iterator(tx); iterator 1155 .hasNext();) { 1156 Entry<Long, MessageKeys> entry = iterator.next(); 1157 if (ackedAndPrepared != null && ackedAndPrepared.contains(entry.getValue().messageId)) { 1158 continue; 1159 } 1160 //If subAckPositions is set then verify the sequence set contains the message still 1161 //and if it doesn't skip it 1162 if (subAckPositions != null && !subAckPositions.contains(entry.getKey())) { 1163 continue; 1164 } 1165 listener.recoverMessage(loadMessage(entry.getValue().location)); 1166 } 1167 sd.orderIndex.resetCursorPosition(); 1168 } 1169 }); 1170 } finally { 1171 indexLock.writeLock().unlock(); 1172 } 1173 } 1174 1175 @Override 1176 public void recoverNextMessages(String clientId, String subscriptionName, final int maxReturned, 1177 final MessageRecoveryListener listener) throws Exception { 1178 final String subscriptionKey = subscriptionKey(clientId, subscriptionName); 1179 @SuppressWarnings("unused") 1180 final SubscriptionInfo info = lookupSubscription(clientId, subscriptionName); 1181 indexLock.writeLock().lock(); 1182 try { 1183 pageFile.tx().execute(new Transaction.Closure<Exception>() { 1184 @Override 1185 public void execute(Transaction tx) throws Exception { 1186 StoredDestination sd = getStoredDestination(dest, tx); 1187 sd.orderIndex.resetCursorPosition(); 1188 MessageOrderCursor moc = sd.subscriptionCursors.get(subscriptionKey); 1189 SequenceSet subAckPositions = null; 1190 if (moc == null) { 1191 LastAck pos = getLastAck(tx, sd, subscriptionKey); 1192 if (pos == null) { 1193 // sub deleted 1194 return; 1195 } 1196 subAckPositions = getSequenceSet(tx, sd, subscriptionKey); 1197 //If we have ackPositions tracked then compare the first one as individual acknowledge mode 1198 //may have bumped lastAck even though there are earlier messages to still consume 1199 if (subAckPositions != null && !subAckPositions.isEmpty() 1200 && subAckPositions.getHead().getFirst() < pos.lastAckedSequence) { 1201 //we have messages to ack before lastAckedSequence 1202 sd.orderIndex.setBatch(tx, subAckPositions.getHead().getFirst() - 1); 1203 } else { 1204 subAckPositions = null; 1205 sd.orderIndex.setBatch(tx, pos); 1206 } 1207 moc = sd.orderIndex.cursor; 1208 } else { 1209 sd.orderIndex.cursor.sync(moc); 1210 } 1211 1212 Entry<Long, MessageKeys> entry = null; 1213 int counter = recoverRolledBackAcks(subscriptionKey, sd, tx, maxReturned, listener); 1214 Set ackedAndPrepared = ackedAndPreparedMap.get(subscriptionKey); 1215 for (Iterator<Entry<Long, MessageKeys>> iterator = sd.orderIndex.iterator(tx, moc); iterator 1216 .hasNext();) { 1217 entry = iterator.next(); 1218 if (ackedAndPrepared != null && ackedAndPrepared.contains(entry.getValue().messageId)) { 1219 continue; 1220 } 1221 //If subAckPositions is set then verify the sequence set contains the message still 1222 //and if it doesn't skip it 1223 if (subAckPositions != null && !subAckPositions.contains(entry.getKey())) { 1224 continue; 1225 } 1226 if (listener.recoverMessage(loadMessage(entry.getValue().location))) { 1227 counter++; 1228 } 1229 if (counter >= maxReturned || listener.hasSpace() == false) { 1230 break; 1231 } 1232 } 1233 sd.orderIndex.stoppedIterating(); 1234 if (entry != null) { 1235 MessageOrderCursor copy = sd.orderIndex.cursor.copy(); 1236 sd.subscriptionCursors.put(subscriptionKey, copy); 1237 } 1238 } 1239 }); 1240 } finally { 1241 indexLock.writeLock().unlock(); 1242 } 1243 } 1244 1245 @Override 1246 public void resetBatching(String clientId, String subscriptionName) { 1247 try { 1248 final String subscriptionKey = subscriptionKey(clientId, subscriptionName); 1249 indexLock.writeLock().lock(); 1250 try { 1251 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1252 @Override 1253 public void execute(Transaction tx) throws IOException { 1254 StoredDestination sd = getStoredDestination(dest, tx); 1255 sd.subscriptionCursors.remove(subscriptionKey); 1256 } 1257 }); 1258 }finally { 1259 indexLock.writeLock().unlock(); 1260 } 1261 } catch (IOException e) { 1262 throw new RuntimeException(e); 1263 } 1264 } 1265 1266 @Override 1267 public MessageStoreSubscriptionStatistics getMessageStoreSubStatistics() { 1268 return messageStoreSubStats; 1269 } 1270 } 1271 1272 String subscriptionKey(String clientId, String subscriptionName) { 1273 return clientId + ":" + subscriptionName; 1274 } 1275 1276 @Override 1277 public MessageStore createQueueMessageStore(ActiveMQQueue destination) throws IOException { 1278 String key = key(convert(destination)); 1279 MessageStore store = storeCache.get(key(convert(destination))); 1280 if (store == null) { 1281 final MessageStore queueStore = this.transactionStore.proxy(new KahaDBMessageStore(destination)); 1282 store = storeCache.putIfAbsent(key, queueStore); 1283 if (store == null) { 1284 store = queueStore; 1285 } 1286 } 1287 1288 return store; 1289 } 1290 1291 @Override 1292 public TopicMessageStore createTopicMessageStore(ActiveMQTopic destination) throws IOException { 1293 String key = key(convert(destination)); 1294 MessageStore store = storeCache.get(key(convert(destination))); 1295 if (store == null) { 1296 final TopicMessageStore topicStore = this.transactionStore.proxy(new KahaDBTopicMessageStore(destination)); 1297 store = storeCache.putIfAbsent(key, topicStore); 1298 if (store == null) { 1299 store = topicStore; 1300 } 1301 } 1302 1303 return (TopicMessageStore) store; 1304 } 1305 1306 /** 1307 * Cleanup method to remove any state associated with the given destination. 1308 * This method does not stop the message store (it might not be cached). 1309 * 1310 * @param destination 1311 * Destination to forget 1312 */ 1313 @Override 1314 public void removeQueueMessageStore(ActiveMQQueue destination) { 1315 } 1316 1317 /** 1318 * Cleanup method to remove any state associated with the given destination 1319 * This method does not stop the message store (it might not be cached). 1320 * 1321 * @param destination 1322 * Destination to forget 1323 */ 1324 @Override 1325 public void removeTopicMessageStore(ActiveMQTopic destination) { 1326 } 1327 1328 @Override 1329 public void deleteAllMessages() throws IOException { 1330 deleteAllMessages = true; 1331 } 1332 1333 @Override 1334 public Set<ActiveMQDestination> getDestinations() { 1335 try { 1336 final HashSet<ActiveMQDestination> rc = new HashSet<ActiveMQDestination>(); 1337 indexLock.writeLock().lock(); 1338 try { 1339 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1340 @Override 1341 public void execute(Transaction tx) throws IOException { 1342 for (Iterator<Entry<String, StoredDestination>> iterator = metadata.destinations.iterator(tx); iterator 1343 .hasNext();) { 1344 Entry<String, StoredDestination> entry = iterator.next(); 1345 //Removing isEmpty topic check - see AMQ-5875 1346 rc.add(convert(entry.getKey())); 1347 } 1348 } 1349 }); 1350 }finally { 1351 indexLock.writeLock().unlock(); 1352 } 1353 return rc; 1354 } catch (IOException e) { 1355 throw new RuntimeException(e); 1356 } 1357 } 1358 1359 @Override 1360 public long getLastMessageBrokerSequenceId() throws IOException { 1361 return 0; 1362 } 1363 1364 @Override 1365 public long getLastProducerSequenceId(ProducerId id) { 1366 indexLock.writeLock().lock(); 1367 try { 1368 return metadata.producerSequenceIdTracker.getLastSeqId(id); 1369 } finally { 1370 indexLock.writeLock().unlock(); 1371 } 1372 } 1373 1374 @Override 1375 public long size() { 1376 try { 1377 return journalSize.get() + getPageFile().getDiskSize(); 1378 } catch (IOException e) { 1379 throw new RuntimeException(e); 1380 } 1381 } 1382 1383 @Override 1384 public void beginTransaction(ConnectionContext context) throws IOException { 1385 throw new IOException("Not yet implemented."); 1386 } 1387 @Override 1388 public void commitTransaction(ConnectionContext context) throws IOException { 1389 throw new IOException("Not yet implemented."); 1390 } 1391 @Override 1392 public void rollbackTransaction(ConnectionContext context) throws IOException { 1393 throw new IOException("Not yet implemented."); 1394 } 1395 1396 @Override 1397 public void checkpoint(boolean sync) throws IOException { 1398 super.checkpointCleanup(sync); 1399 } 1400 1401 // ///////////////////////////////////////////////////////////////// 1402 // Internal helper methods. 1403 // ///////////////////////////////////////////////////////////////// 1404 1405 /** 1406 * @param location 1407 * @return 1408 * @throws IOException 1409 */ 1410 Message loadMessage(Location location) throws IOException { 1411 try { 1412 JournalCommand<?> command = load(location); 1413 KahaAddMessageCommand addMessage = null; 1414 switch (command.type()) { 1415 case KAHA_UPDATE_MESSAGE_COMMAND: 1416 addMessage = ((KahaUpdateMessageCommand) command).getMessage(); 1417 break; 1418 case KAHA_ADD_MESSAGE_COMMAND: 1419 addMessage = (KahaAddMessageCommand) command; 1420 break; 1421 default: 1422 throw new IOException("Could not load journal record, unexpected command type: " + command.type() + " at location: " + location); 1423 } 1424 if (!addMessage.hasMessage()) { 1425 throw new IOException("Could not load journal record, null message content at location: " + location); 1426 } 1427 Message msg = (Message) wireFormat.unmarshal(new DataInputStream(addMessage.getMessage().newInput())); 1428 return msg; 1429 } catch (Throwable t) { 1430 IOException ioe = IOExceptionSupport.create("Unexpected error on journal read at: " + location , t); 1431 LOG.error("Failed to load message at: {}", location , ioe); 1432 brokerService.handleIOException(ioe); 1433 throw ioe; 1434 } 1435 } 1436 1437 // ///////////////////////////////////////////////////////////////// 1438 // Internal conversion methods. 1439 // ///////////////////////////////////////////////////////////////// 1440 1441 KahaLocation convert(Location location) { 1442 KahaLocation rc = new KahaLocation(); 1443 rc.setLogId(location.getDataFileId()); 1444 rc.setOffset(location.getOffset()); 1445 return rc; 1446 } 1447 1448 KahaDestination convert(ActiveMQDestination dest) { 1449 KahaDestination rc = new KahaDestination(); 1450 rc.setName(dest.getPhysicalName()); 1451 switch (dest.getDestinationType()) { 1452 case ActiveMQDestination.QUEUE_TYPE: 1453 rc.setType(DestinationType.QUEUE); 1454 return rc; 1455 case ActiveMQDestination.TOPIC_TYPE: 1456 rc.setType(DestinationType.TOPIC); 1457 return rc; 1458 case ActiveMQDestination.TEMP_QUEUE_TYPE: 1459 rc.setType(DestinationType.TEMP_QUEUE); 1460 return rc; 1461 case ActiveMQDestination.TEMP_TOPIC_TYPE: 1462 rc.setType(DestinationType.TEMP_TOPIC); 1463 return rc; 1464 default: 1465 return null; 1466 } 1467 } 1468 1469 ActiveMQDestination convert(String dest) { 1470 int p = dest.indexOf(":"); 1471 if (p < 0) { 1472 throw new IllegalArgumentException("Not in the valid destination format"); 1473 } 1474 int type = Integer.parseInt(dest.substring(0, p)); 1475 String name = dest.substring(p + 1); 1476 return convert(type, name); 1477 } 1478 1479 private ActiveMQDestination convert(KahaDestination commandDestination) { 1480 return convert(commandDestination.getType().getNumber(), commandDestination.getName()); 1481 } 1482 1483 private ActiveMQDestination convert(int type, String name) { 1484 switch (KahaDestination.DestinationType.valueOf(type)) { 1485 case QUEUE: 1486 return new ActiveMQQueue(name); 1487 case TOPIC: 1488 return new ActiveMQTopic(name); 1489 case TEMP_QUEUE: 1490 return new ActiveMQTempQueue(name); 1491 case TEMP_TOPIC: 1492 return new ActiveMQTempTopic(name); 1493 default: 1494 throw new IllegalArgumentException("Not in the valid destination format"); 1495 } 1496 } 1497 1498 public TransactionIdTransformer getTransactionIdTransformer() { 1499 return transactionIdTransformer; 1500 } 1501 1502 public void setTransactionIdTransformer(TransactionIdTransformer transactionIdTransformer) { 1503 this.transactionIdTransformer = transactionIdTransformer; 1504 } 1505 1506 static class AsyncJobKey { 1507 MessageId id; 1508 ActiveMQDestination destination; 1509 1510 AsyncJobKey(MessageId id, ActiveMQDestination destination) { 1511 this.id = id; 1512 this.destination = destination; 1513 } 1514 1515 @Override 1516 public boolean equals(Object obj) { 1517 if (obj == this) { 1518 return true; 1519 } 1520 return obj instanceof AsyncJobKey && id.equals(((AsyncJobKey) obj).id) 1521 && destination.equals(((AsyncJobKey) obj).destination); 1522 } 1523 1524 @Override 1525 public int hashCode() { 1526 return id.hashCode() + destination.hashCode(); 1527 } 1528 1529 @Override 1530 public String toString() { 1531 return destination.getPhysicalName() + "-" + id; 1532 } 1533 } 1534 1535 public interface StoreTask { 1536 public boolean cancel(); 1537 1538 public void aquireLocks(); 1539 1540 public void releaseLocks(); 1541 } 1542 1543 class StoreQueueTask implements Runnable, StoreTask { 1544 protected final Message message; 1545 protected final ConnectionContext context; 1546 protected final KahaDBMessageStore store; 1547 protected final InnerFutureTask future; 1548 protected final AtomicBoolean done = new AtomicBoolean(); 1549 protected final AtomicBoolean locked = new AtomicBoolean(); 1550 1551 public StoreQueueTask(KahaDBMessageStore store, ConnectionContext context, Message message) { 1552 this.store = store; 1553 this.context = context; 1554 this.message = message; 1555 this.future = new InnerFutureTask(this); 1556 } 1557 1558 public ListenableFuture<Object> getFuture() { 1559 return this.future; 1560 } 1561 1562 @Override 1563 public boolean cancel() { 1564 if (this.done.compareAndSet(false, true)) { 1565 return this.future.cancel(false); 1566 } 1567 return false; 1568 } 1569 1570 @Override 1571 public void aquireLocks() { 1572 if (this.locked.compareAndSet(false, true)) { 1573 try { 1574 globalQueueSemaphore.acquire(); 1575 store.acquireLocalAsyncLock(); 1576 message.incrementReferenceCount(); 1577 } catch (InterruptedException e) { 1578 LOG.warn("Failed to aquire lock", e); 1579 } 1580 } 1581 1582 } 1583 1584 @Override 1585 public void releaseLocks() { 1586 if (this.locked.compareAndSet(true, false)) { 1587 store.releaseLocalAsyncLock(); 1588 globalQueueSemaphore.release(); 1589 message.decrementReferenceCount(); 1590 } 1591 } 1592 1593 @Override 1594 public void run() { 1595 this.store.doneTasks++; 1596 try { 1597 if (this.done.compareAndSet(false, true)) { 1598 this.store.addMessage(context, message); 1599 removeQueueTask(this.store, this.message.getMessageId()); 1600 this.future.complete(); 1601 } else if (cancelledTaskModMetric > 0 && (++this.store.canceledTasks) % cancelledTaskModMetric == 0) { 1602 System.err.println(this.store.dest.getName() + " cancelled: " 1603 + (this.store.canceledTasks / this.store.doneTasks) * 100); 1604 this.store.canceledTasks = this.store.doneTasks = 0; 1605 } 1606 } catch (Throwable t) { 1607 this.future.setException(t); 1608 removeQueueTask(this.store, this.message.getMessageId()); 1609 } 1610 } 1611 1612 protected Message getMessage() { 1613 return this.message; 1614 } 1615 1616 private class InnerFutureTask extends FutureTask<Object> implements ListenableFuture<Object> { 1617 1618 private final AtomicReference<Runnable> listenerRef = new AtomicReference<>(); 1619 1620 public InnerFutureTask(Runnable runnable) { 1621 super(runnable, null); 1622 } 1623 1624 @Override 1625 public void setException(final Throwable e) { 1626 super.setException(e); 1627 } 1628 1629 public void complete() { 1630 super.set(null); 1631 } 1632 1633 @Override 1634 public void done() { 1635 fireListener(); 1636 } 1637 1638 @Override 1639 public void addListener(Runnable listener) { 1640 this.listenerRef.set(listener); 1641 if (isDone()) { 1642 fireListener(); 1643 } 1644 } 1645 1646 private void fireListener() { 1647 Runnable listener = listenerRef.getAndSet(null); 1648 if (listener != null) { 1649 try { 1650 listener.run(); 1651 } catch (Exception ignored) { 1652 LOG.warn("Unexpected exception from future {} listener callback {}", this, listener, ignored); 1653 } 1654 } 1655 } 1656 } 1657 } 1658 1659 class StoreTopicTask extends StoreQueueTask { 1660 private final int subscriptionCount; 1661 private final List<String> subscriptionKeys = new ArrayList<String>(1); 1662 private final KahaDBTopicMessageStore topicStore; 1663 public StoreTopicTask(KahaDBTopicMessageStore store, ConnectionContext context, Message message, 1664 int subscriptionCount) { 1665 super(store, context, message); 1666 this.topicStore = store; 1667 this.subscriptionCount = subscriptionCount; 1668 1669 } 1670 1671 @Override 1672 public void aquireLocks() { 1673 if (this.locked.compareAndSet(false, true)) { 1674 try { 1675 globalTopicSemaphore.acquire(); 1676 store.acquireLocalAsyncLock(); 1677 message.incrementReferenceCount(); 1678 } catch (InterruptedException e) { 1679 LOG.warn("Failed to aquire lock", e); 1680 } 1681 } 1682 } 1683 1684 @Override 1685 public void releaseLocks() { 1686 if (this.locked.compareAndSet(true, false)) { 1687 message.decrementReferenceCount(); 1688 store.releaseLocalAsyncLock(); 1689 globalTopicSemaphore.release(); 1690 } 1691 } 1692 1693 /** 1694 * add a key 1695 * 1696 * @param key 1697 * @return true if all acknowledgements received 1698 */ 1699 public boolean addSubscriptionKey(String key) { 1700 synchronized (this.subscriptionKeys) { 1701 this.subscriptionKeys.add(key); 1702 } 1703 return this.subscriptionKeys.size() >= this.subscriptionCount; 1704 } 1705 1706 @Override 1707 public void run() { 1708 this.store.doneTasks++; 1709 try { 1710 if (this.done.compareAndSet(false, true)) { 1711 this.topicStore.addMessage(context, message); 1712 // apply any acks we have 1713 synchronized (this.subscriptionKeys) { 1714 for (String key : this.subscriptionKeys) { 1715 this.topicStore.doAcknowledge(context, key, this.message.getMessageId(), null); 1716 1717 } 1718 } 1719 removeTopicTask(this.topicStore, this.message.getMessageId()); 1720 this.future.complete(); 1721 } else if (cancelledTaskModMetric > 0 && this.store.canceledTasks++ % cancelledTaskModMetric == 0) { 1722 System.err.println(this.store.dest.getName() + " cancelled: " 1723 + (this.store.canceledTasks / this.store.doneTasks) * 100); 1724 this.store.canceledTasks = this.store.doneTasks = 0; 1725 } 1726 } catch (Throwable t) { 1727 this.future.setException(t); 1728 removeTopicTask(this.topicStore, this.message.getMessageId()); 1729 } 1730 } 1731 } 1732 1733 public class StoreTaskExecutor extends ThreadPoolExecutor { 1734 1735 public StoreTaskExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit timeUnit, BlockingQueue<Runnable> queue, ThreadFactory threadFactory) { 1736 super(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, queue, threadFactory); 1737 } 1738 1739 @Override 1740 protected void afterExecute(Runnable runnable, Throwable throwable) { 1741 super.afterExecute(runnable, throwable); 1742 1743 if (runnable instanceof StoreTask) { 1744 ((StoreTask)runnable).releaseLocks(); 1745 } 1746 } 1747 } 1748 1749 @Override 1750 public JobSchedulerStore createJobSchedulerStore() throws IOException, UnsupportedOperationException { 1751 return new JobSchedulerStoreImpl(); 1752 } 1753 1754 /* (non-Javadoc) 1755 * @see org.apache.activemq.store.NoLocalSubscriptionAware#isPersistNoLocal() 1756 */ 1757 @Override 1758 public boolean isPersistNoLocal() { 1759 // Prior to v11 the broker did not store the noLocal value for durable subs. 1760 return brokerService.getStoreOpenWireVersion() >= 11; 1761 } 1762}