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.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.DataInput; 022import java.io.DataOutput; 023import java.io.EOFException; 024import java.io.File; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.InterruptedIOException; 028import java.io.ObjectInputStream; 029import java.io.ObjectOutputStream; 030import java.io.OutputStream; 031import java.util.ArrayList; 032import java.util.Arrays; 033import java.util.Collection; 034import java.util.Collections; 035import java.util.Date; 036import java.util.HashMap; 037import java.util.HashSet; 038import java.util.Iterator; 039import java.util.LinkedHashMap; 040import java.util.LinkedHashSet; 041import java.util.LinkedList; 042import java.util.List; 043import java.util.Map; 044import java.util.Map.Entry; 045import java.util.Set; 046import java.util.SortedSet; 047import java.util.TreeMap; 048import java.util.TreeSet; 049import java.util.concurrent.ConcurrentHashMap; 050import java.util.concurrent.ConcurrentMap; 051import java.util.concurrent.atomic.AtomicBoolean; 052import java.util.concurrent.atomic.AtomicLong; 053import java.util.concurrent.locks.ReentrantReadWriteLock; 054 055import org.apache.activemq.ActiveMQMessageAuditNoSync; 056import org.apache.activemq.broker.BrokerService; 057import org.apache.activemq.broker.BrokerServiceAware; 058import org.apache.activemq.broker.region.Destination; 059import org.apache.activemq.broker.region.Queue; 060import org.apache.activemq.broker.region.Topic; 061import org.apache.activemq.command.MessageAck; 062import org.apache.activemq.command.TransactionId; 063import org.apache.activemq.openwire.OpenWireFormat; 064import org.apache.activemq.protobuf.Buffer; 065import org.apache.activemq.store.MessageStore; 066import org.apache.activemq.store.MessageStoreStatistics; 067import org.apache.activemq.store.kahadb.data.KahaAckMessageFileMapCommand; 068import org.apache.activemq.store.kahadb.data.KahaAddMessageCommand; 069import org.apache.activemq.store.kahadb.data.KahaCommitCommand; 070import org.apache.activemq.store.kahadb.data.KahaDestination; 071import org.apache.activemq.store.kahadb.data.KahaEntryType; 072import org.apache.activemq.store.kahadb.data.KahaPrepareCommand; 073import org.apache.activemq.store.kahadb.data.KahaProducerAuditCommand; 074import org.apache.activemq.store.kahadb.data.KahaRemoveDestinationCommand; 075import org.apache.activemq.store.kahadb.data.KahaRemoveMessageCommand; 076import org.apache.activemq.store.kahadb.data.KahaRollbackCommand; 077import org.apache.activemq.store.kahadb.data.KahaSubscriptionCommand; 078import org.apache.activemq.store.kahadb.data.KahaTraceCommand; 079import org.apache.activemq.store.kahadb.data.KahaTransactionInfo; 080import org.apache.activemq.store.kahadb.data.KahaUpdateMessageCommand; 081import org.apache.activemq.store.kahadb.disk.index.BTreeIndex; 082import org.apache.activemq.store.kahadb.disk.index.BTreeVisitor; 083import org.apache.activemq.store.kahadb.disk.index.ListIndex; 084import org.apache.activemq.store.kahadb.disk.journal.DataFile; 085import org.apache.activemq.store.kahadb.disk.journal.Journal; 086import org.apache.activemq.store.kahadb.disk.journal.Location; 087import org.apache.activemq.store.kahadb.disk.page.Page; 088import org.apache.activemq.store.kahadb.disk.page.PageFile; 089import org.apache.activemq.store.kahadb.disk.page.Transaction; 090import org.apache.activemq.store.kahadb.disk.util.LocationMarshaller; 091import org.apache.activemq.store.kahadb.disk.util.LongMarshaller; 092import org.apache.activemq.store.kahadb.disk.util.Marshaller; 093import org.apache.activemq.store.kahadb.disk.util.Sequence; 094import org.apache.activemq.store.kahadb.disk.util.SequenceSet; 095import org.apache.activemq.store.kahadb.disk.util.StringMarshaller; 096import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller; 097import org.apache.activemq.util.ByteSequence; 098import org.apache.activemq.util.DataByteArrayInputStream; 099import org.apache.activemq.util.DataByteArrayOutputStream; 100import org.apache.activemq.util.IOHelper; 101import org.apache.activemq.util.ServiceStopper; 102import org.apache.activemq.util.ServiceSupport; 103import org.slf4j.Logger; 104import org.slf4j.LoggerFactory; 105 106public abstract class MessageDatabase extends ServiceSupport implements BrokerServiceAware { 107 108 protected BrokerService brokerService; 109 110 public static final String PROPERTY_LOG_SLOW_ACCESS_TIME = "org.apache.activemq.store.kahadb.LOG_SLOW_ACCESS_TIME"; 111 public static final int LOG_SLOW_ACCESS_TIME = Integer.getInteger(PROPERTY_LOG_SLOW_ACCESS_TIME, 0); 112 public static final File DEFAULT_DIRECTORY = new File("KahaDB"); 113 protected static final Buffer UNMATCHED; 114 static { 115 UNMATCHED = new Buffer(new byte[]{}); 116 } 117 private static final Logger LOG = LoggerFactory.getLogger(MessageDatabase.class); 118 119 static final int CLOSED_STATE = 1; 120 static final int OPEN_STATE = 2; 121 static final long NOT_ACKED = -1; 122 123 static final int VERSION = 6; 124 125 protected class Metadata { 126 protected Page<Metadata> page; 127 protected int state; 128 protected BTreeIndex<String, StoredDestination> destinations; 129 protected Location lastUpdate; 130 protected Location firstInProgressTransactionLocation; 131 protected Location producerSequenceIdTrackerLocation = null; 132 protected Location ackMessageFileMapLocation = null; 133 protected transient ActiveMQMessageAuditNoSync producerSequenceIdTracker = new ActiveMQMessageAuditNoSync(); 134 protected transient Map<Integer, Set<Integer>> ackMessageFileMap = new HashMap<Integer, Set<Integer>>(); 135 protected int version = VERSION; 136 protected int openwireVersion = OpenWireFormat.DEFAULT_STORE_VERSION; 137 138 public void read(DataInput is) throws IOException { 139 state = is.readInt(); 140 destinations = new BTreeIndex<String, StoredDestination>(pageFile, is.readLong()); 141 if (is.readBoolean()) { 142 lastUpdate = LocationMarshaller.INSTANCE.readPayload(is); 143 } else { 144 lastUpdate = null; 145 } 146 if (is.readBoolean()) { 147 firstInProgressTransactionLocation = LocationMarshaller.INSTANCE.readPayload(is); 148 } else { 149 firstInProgressTransactionLocation = null; 150 } 151 try { 152 if (is.readBoolean()) { 153 producerSequenceIdTrackerLocation = LocationMarshaller.INSTANCE.readPayload(is); 154 } else { 155 producerSequenceIdTrackerLocation = null; 156 } 157 } catch (EOFException expectedOnUpgrade) { 158 } 159 try { 160 version = is.readInt(); 161 } catch (EOFException expectedOnUpgrade) { 162 version = 1; 163 } 164 if (version >= 5 && is.readBoolean()) { 165 ackMessageFileMapLocation = LocationMarshaller.INSTANCE.readPayload(is); 166 } else { 167 ackMessageFileMapLocation = null; 168 } 169 try { 170 openwireVersion = is.readInt(); 171 } catch (EOFException expectedOnUpgrade) { 172 openwireVersion = OpenWireFormat.DEFAULT_LEGACY_VERSION; 173 } 174 LOG.info("KahaDB is version " + version); 175 } 176 177 public void write(DataOutput os) throws IOException { 178 os.writeInt(state); 179 os.writeLong(destinations.getPageId()); 180 181 if (lastUpdate != null) { 182 os.writeBoolean(true); 183 LocationMarshaller.INSTANCE.writePayload(lastUpdate, os); 184 } else { 185 os.writeBoolean(false); 186 } 187 188 if (firstInProgressTransactionLocation != null) { 189 os.writeBoolean(true); 190 LocationMarshaller.INSTANCE.writePayload(firstInProgressTransactionLocation, os); 191 } else { 192 os.writeBoolean(false); 193 } 194 195 if (producerSequenceIdTrackerLocation != null) { 196 os.writeBoolean(true); 197 LocationMarshaller.INSTANCE.writePayload(producerSequenceIdTrackerLocation, os); 198 } else { 199 os.writeBoolean(false); 200 } 201 os.writeInt(VERSION); 202 if (ackMessageFileMapLocation != null) { 203 os.writeBoolean(true); 204 LocationMarshaller.INSTANCE.writePayload(ackMessageFileMapLocation, os); 205 } else { 206 os.writeBoolean(false); 207 } 208 os.writeInt(this.openwireVersion); 209 } 210 } 211 212 class MetadataMarshaller extends VariableMarshaller<Metadata> { 213 @Override 214 public Metadata readPayload(DataInput dataIn) throws IOException { 215 Metadata rc = createMetadata(); 216 rc.read(dataIn); 217 return rc; 218 } 219 220 @Override 221 public void writePayload(Metadata object, DataOutput dataOut) throws IOException { 222 object.write(dataOut); 223 } 224 } 225 226 protected PageFile pageFile; 227 protected Journal journal; 228 protected Metadata metadata = new Metadata(); 229 230 protected MetadataMarshaller metadataMarshaller = new MetadataMarshaller(); 231 232 protected boolean failIfDatabaseIsLocked; 233 234 protected boolean deleteAllMessages; 235 protected File directory = DEFAULT_DIRECTORY; 236 protected File indexDirectory = null; 237 protected Thread checkpointThread; 238 protected boolean enableJournalDiskSyncs=true; 239 protected boolean archiveDataLogs; 240 protected File directoryArchive; 241 protected AtomicLong journalSize = new AtomicLong(0); 242 long checkpointInterval = 5*1000; 243 long cleanupInterval = 30*1000; 244 int journalMaxFileLength = Journal.DEFAULT_MAX_FILE_LENGTH; 245 int journalMaxWriteBatchSize = Journal.DEFAULT_MAX_WRITE_BATCH_SIZE; 246 boolean enableIndexWriteAsync = false; 247 int setIndexWriteBatchSize = PageFile.DEFAULT_WRITE_BATCH_SIZE; 248 private String preallocationScope = Journal.PreallocationScope.ENTIRE_JOURNAL.name(); 249 private String preallocationStrategy = Journal.PreallocationStrategy.SPARSE_FILE.name(); 250 251 protected AtomicBoolean opened = new AtomicBoolean(); 252 private boolean ignoreMissingJournalfiles = false; 253 private int indexCacheSize = 10000; 254 private boolean checkForCorruptJournalFiles = false; 255 private boolean checksumJournalFiles = true; 256 protected boolean forceRecoverIndex = false; 257 private final Object checkpointThreadLock = new Object(); 258 private boolean archiveCorruptedIndex = false; 259 private boolean useIndexLFRUEviction = false; 260 private float indexLFUEvictionFactor = 0.2f; 261 private boolean enableIndexDiskSyncs = true; 262 private boolean enableIndexRecoveryFile = true; 263 private boolean enableIndexPageCaching = true; 264 ReentrantReadWriteLock checkpointLock = new ReentrantReadWriteLock(); 265 266 @Override 267 public void doStart() throws Exception { 268 load(); 269 } 270 271 @Override 272 public void doStop(ServiceStopper stopper) throws Exception { 273 unload(); 274 } 275 276 private void loadPageFile() throws IOException { 277 this.indexLock.writeLock().lock(); 278 try { 279 final PageFile pageFile = getPageFile(); 280 pageFile.load(); 281 pageFile.tx().execute(new Transaction.Closure<IOException>() { 282 @Override 283 public void execute(Transaction tx) throws IOException { 284 if (pageFile.getPageCount() == 0) { 285 // First time this is created.. Initialize the metadata 286 Page<Metadata> page = tx.allocate(); 287 assert page.getPageId() == 0; 288 page.set(metadata); 289 metadata.page = page; 290 metadata.state = CLOSED_STATE; 291 metadata.destinations = new BTreeIndex<String, StoredDestination>(pageFile, tx.allocate().getPageId()); 292 293 tx.store(metadata.page, metadataMarshaller, true); 294 } else { 295 Page<Metadata> page = tx.load(0, metadataMarshaller); 296 metadata = page.get(); 297 metadata.page = page; 298 } 299 metadata.destinations.setKeyMarshaller(StringMarshaller.INSTANCE); 300 metadata.destinations.setValueMarshaller(new StoredDestinationMarshaller()); 301 metadata.destinations.load(tx); 302 } 303 }); 304 // Load up all the destinations since we need to scan all the indexes to figure out which journal files can be deleted. 305 // Perhaps we should just keep an index of file 306 storedDestinations.clear(); 307 pageFile.tx().execute(new Transaction.Closure<IOException>() { 308 @Override 309 public void execute(Transaction tx) throws IOException { 310 for (Iterator<Entry<String, StoredDestination>> iterator = metadata.destinations.iterator(tx); iterator.hasNext();) { 311 Entry<String, StoredDestination> entry = iterator.next(); 312 StoredDestination sd = loadStoredDestination(tx, entry.getKey(), entry.getValue().subscriptions!=null); 313 storedDestinations.put(entry.getKey(), sd); 314 315 if (checkForCorruptJournalFiles) { 316 // sanity check the index also 317 if (!entry.getValue().locationIndex.isEmpty(tx)) { 318 if (entry.getValue().orderIndex.nextMessageId <= 0) { 319 throw new IOException("Detected uninitialized orderIndex nextMessageId with pending messages for " + entry.getKey()); 320 } 321 } 322 } 323 } 324 } 325 }); 326 pageFile.flush(); 327 } finally { 328 this.indexLock.writeLock().unlock(); 329 } 330 } 331 332 private void startCheckpoint() { 333 if (checkpointInterval == 0 && cleanupInterval == 0) { 334 LOG.info("periodic checkpoint/cleanup disabled, will ocurr on clean shutdown/restart"); 335 return; 336 } 337 synchronized (checkpointThreadLock) { 338 boolean start = false; 339 if (checkpointThread == null) { 340 start = true; 341 } else if (!checkpointThread.isAlive()) { 342 start = true; 343 LOG.info("KahaDB: Recovering checkpoint thread after death"); 344 } 345 if (start) { 346 checkpointThread = new Thread("ActiveMQ Journal Checkpoint Worker") { 347 @Override 348 public void run() { 349 try { 350 long lastCleanup = System.currentTimeMillis(); 351 long lastCheckpoint = System.currentTimeMillis(); 352 // Sleep for a short time so we can periodically check 353 // to see if we need to exit this thread. 354 long sleepTime = Math.min(checkpointInterval > 0 ? checkpointInterval : cleanupInterval, 500); 355 while (opened.get()) { 356 Thread.sleep(sleepTime); 357 long now = System.currentTimeMillis(); 358 if( cleanupInterval > 0 && (now - lastCleanup >= cleanupInterval) ) { 359 checkpointCleanup(true); 360 lastCleanup = now; 361 lastCheckpoint = now; 362 } else if( checkpointInterval > 0 && (now - lastCheckpoint >= checkpointInterval )) { 363 checkpointCleanup(false); 364 lastCheckpoint = now; 365 } 366 } 367 } catch (InterruptedException e) { 368 // Looks like someone really wants us to exit this thread... 369 } catch (IOException ioe) { 370 LOG.error("Checkpoint failed", ioe); 371 brokerService.handleIOException(ioe); 372 } 373 } 374 }; 375 376 checkpointThread.setDaemon(true); 377 checkpointThread.start(); 378 } 379 } 380 } 381 382 public void open() throws IOException { 383 if( opened.compareAndSet(false, true) ) { 384 getJournal().start(); 385 try { 386 loadPageFile(); 387 } catch (Throwable t) { 388 LOG.warn("Index corrupted. Recovering the index through journal replay. Cause:" + t); 389 if (LOG.isDebugEnabled()) { 390 LOG.debug("Index load failure", t); 391 } 392 // try to recover index 393 try { 394 pageFile.unload(); 395 } catch (Exception ignore) {} 396 if (archiveCorruptedIndex) { 397 pageFile.archive(); 398 } else { 399 pageFile.delete(); 400 } 401 metadata = createMetadata(); 402 pageFile = null; 403 loadPageFile(); 404 } 405 startCheckpoint(); 406 recover(); 407 } 408 } 409 410 public void load() throws IOException { 411 this.indexLock.writeLock().lock(); 412 IOHelper.mkdirs(directory); 413 try { 414 if (deleteAllMessages) { 415 getJournal().start(); 416 getJournal().delete(); 417 getJournal().close(); 418 journal = null; 419 getPageFile().delete(); 420 LOG.info("Persistence store purged."); 421 deleteAllMessages = false; 422 } 423 424 open(); 425 store(new KahaTraceCommand().setMessage("LOADED " + new Date())); 426 } finally { 427 this.indexLock.writeLock().unlock(); 428 } 429 } 430 431 public void close() throws IOException, InterruptedException { 432 if( opened.compareAndSet(true, false)) { 433 checkpointLock.writeLock().lock(); 434 try { 435 if (metadata.page != null) { 436 checkpointUpdate(true); 437 } 438 pageFile.unload(); 439 metadata = createMetadata(); 440 } finally { 441 checkpointLock.writeLock().unlock(); 442 } 443 journal.close(); 444 synchronized (checkpointThreadLock) { 445 if (checkpointThread != null) { 446 checkpointThread.join(); 447 } 448 } 449 //clear the cache on shutdown of the store 450 storeCache.clear(); 451 } 452 } 453 454 public void unload() throws IOException, InterruptedException { 455 this.indexLock.writeLock().lock(); 456 try { 457 if( pageFile != null && pageFile.isLoaded() ) { 458 metadata.state = CLOSED_STATE; 459 metadata.firstInProgressTransactionLocation = getInProgressTxLocationRange()[0]; 460 461 if (metadata.page != null) { 462 pageFile.tx().execute(new Transaction.Closure<IOException>() { 463 @Override 464 public void execute(Transaction tx) throws IOException { 465 tx.store(metadata.page, metadataMarshaller, true); 466 } 467 }); 468 } 469 } 470 } finally { 471 this.indexLock.writeLock().unlock(); 472 } 473 close(); 474 } 475 476 // public for testing 477 @SuppressWarnings("rawtypes") 478 public Location[] getInProgressTxLocationRange() { 479 Location[] range = new Location[]{null, null}; 480 synchronized (inflightTransactions) { 481 if (!inflightTransactions.isEmpty()) { 482 for (List<Operation> ops : inflightTransactions.values()) { 483 if (!ops.isEmpty()) { 484 trackMaxAndMin(range, ops); 485 } 486 } 487 } 488 if (!preparedTransactions.isEmpty()) { 489 for (List<Operation> ops : preparedTransactions.values()) { 490 if (!ops.isEmpty()) { 491 trackMaxAndMin(range, ops); 492 } 493 } 494 } 495 } 496 return range; 497 } 498 499 @SuppressWarnings("rawtypes") 500 private void trackMaxAndMin(Location[] range, List<Operation> ops) { 501 Location t = ops.get(0).getLocation(); 502 if (range[0]==null || t.compareTo(range[0]) <= 0) { 503 range[0] = t; 504 } 505 t = ops.get(ops.size() -1).getLocation(); 506 if (range[1]==null || t.compareTo(range[1]) >= 0) { 507 range[1] = t; 508 } 509 } 510 511 class TranInfo { 512 TransactionId id; 513 Location location; 514 515 class opCount { 516 int add; 517 int remove; 518 } 519 HashMap<KahaDestination, opCount> destinationOpCount = new HashMap<KahaDestination, opCount>(); 520 521 @SuppressWarnings("rawtypes") 522 public void track(Operation operation) { 523 if (location == null ) { 524 location = operation.getLocation(); 525 } 526 KahaDestination destination; 527 boolean isAdd = false; 528 if (operation instanceof AddOperation) { 529 AddOperation add = (AddOperation) operation; 530 destination = add.getCommand().getDestination(); 531 isAdd = true; 532 } else { 533 RemoveOperation removeOpperation = (RemoveOperation) operation; 534 destination = removeOpperation.getCommand().getDestination(); 535 } 536 opCount opCount = destinationOpCount.get(destination); 537 if (opCount == null) { 538 opCount = new opCount(); 539 destinationOpCount.put(destination, opCount); 540 } 541 if (isAdd) { 542 opCount.add++; 543 } else { 544 opCount.remove++; 545 } 546 } 547 548 @Override 549 public String toString() { 550 StringBuffer buffer = new StringBuffer(); 551 buffer.append(location).append(";").append(id).append(";\n"); 552 for (Entry<KahaDestination, opCount> op : destinationOpCount.entrySet()) { 553 buffer.append(op.getKey()).append('+').append(op.getValue().add).append(',').append('-').append(op.getValue().remove).append(';'); 554 } 555 return buffer.toString(); 556 } 557 } 558 559 @SuppressWarnings("rawtypes") 560 public String getTransactions() { 561 562 ArrayList<TranInfo> infos = new ArrayList<TranInfo>(); 563 synchronized (inflightTransactions) { 564 if (!inflightTransactions.isEmpty()) { 565 for (Entry<TransactionId, List<Operation>> entry : inflightTransactions.entrySet()) { 566 TranInfo info = new TranInfo(); 567 info.id = entry.getKey(); 568 for (Operation operation : entry.getValue()) { 569 info.track(operation); 570 } 571 infos.add(info); 572 } 573 } 574 } 575 synchronized (preparedTransactions) { 576 if (!preparedTransactions.isEmpty()) { 577 for (Entry<TransactionId, List<Operation>> entry : preparedTransactions.entrySet()) { 578 TranInfo info = new TranInfo(); 579 info.id = entry.getKey(); 580 for (Operation operation : entry.getValue()) { 581 info.track(operation); 582 } 583 infos.add(info); 584 } 585 } 586 } 587 return infos.toString(); 588 } 589 590 /** 591 * Move all the messages that were in the journal into long term storage. We 592 * just replay and do a checkpoint. 593 * 594 * @throws IOException 595 * @throws IOException 596 * @throws IllegalStateException 597 */ 598 private void recover() throws IllegalStateException, IOException { 599 this.indexLock.writeLock().lock(); 600 try { 601 602 long start = System.currentTimeMillis(); 603 Location producerAuditPosition = recoverProducerAudit(); 604 Location ackMessageFileLocation = recoverAckMessageFileMap(); 605 Location lastIndoubtPosition = getRecoveryPosition(); 606 607 Location recoveryPosition = minimum(producerAuditPosition, ackMessageFileLocation); 608 recoveryPosition = minimum(recoveryPosition, lastIndoubtPosition); 609 610 if (recoveryPosition != null) { 611 int redoCounter = 0; 612 LOG.info("Recovering from the journal @" + recoveryPosition); 613 while (recoveryPosition != null) { 614 try { 615 JournalCommand<?> message = load(recoveryPosition); 616 metadata.lastUpdate = recoveryPosition; 617 process(message, recoveryPosition, lastIndoubtPosition); 618 redoCounter++; 619 } catch (IOException failedRecovery) { 620 if (isIgnoreMissingJournalfiles()) { 621 LOG.debug("Failed to recover data at position:" + recoveryPosition, failedRecovery); 622 // track this dud location 623 journal.corruptRecoveryLocation(recoveryPosition); 624 } else { 625 throw new IOException("Failed to recover data at position:" + recoveryPosition, failedRecovery); 626 } 627 } 628 recoveryPosition = journal.getNextLocation(recoveryPosition); 629 if (LOG.isInfoEnabled() && redoCounter % 100000 == 0) { 630 LOG.info("@" + recoveryPosition + ", " + redoCounter + " entries recovered .."); 631 } 632 } 633 if (LOG.isInfoEnabled()) { 634 long end = System.currentTimeMillis(); 635 LOG.info("Recovery replayed " + redoCounter + " operations from the journal in " + ((end - start) / 1000.0f) + " seconds."); 636 } 637 } 638 639 // We may have to undo some index updates. 640 pageFile.tx().execute(new Transaction.Closure<IOException>() { 641 @Override 642 public void execute(Transaction tx) throws IOException { 643 recoverIndex(tx); 644 } 645 }); 646 647 // rollback any recovered inflight local transactions, and discard any inflight XA transactions. 648 Set<TransactionId> toRollback = new HashSet<TransactionId>(); 649 Set<TransactionId> toDiscard = new HashSet<TransactionId>(); 650 synchronized (inflightTransactions) { 651 for (Iterator<TransactionId> it = inflightTransactions.keySet().iterator(); it.hasNext(); ) { 652 TransactionId id = it.next(); 653 if (id.isLocalTransaction()) { 654 toRollback.add(id); 655 } else { 656 toDiscard.add(id); 657 } 658 } 659 for (TransactionId tx: toRollback) { 660 if (LOG.isDebugEnabled()) { 661 LOG.debug("rolling back recovered indoubt local transaction " + tx); 662 } 663 store(new KahaRollbackCommand().setTransactionInfo(TransactionIdConversion.convertToLocal(tx)), false, null, null); 664 } 665 for (TransactionId tx: toDiscard) { 666 if (LOG.isDebugEnabled()) { 667 LOG.debug("discarding recovered in-flight XA transaction " + tx); 668 } 669 inflightTransactions.remove(tx); 670 } 671 } 672 673 synchronized (preparedTransactions) { 674 for (TransactionId txId : preparedTransactions.keySet()) { 675 LOG.warn("Recovered prepared XA TX: [{}]", txId); 676 } 677 } 678 679 } finally { 680 this.indexLock.writeLock().unlock(); 681 } 682 } 683 684 @SuppressWarnings("unused") 685 private KahaTransactionInfo createLocalTransactionInfo(TransactionId tx) { 686 return TransactionIdConversion.convertToLocal(tx); 687 } 688 689 private Location minimum(Location producerAuditPosition, 690 Location lastIndoubtPosition) { 691 Location min = null; 692 if (producerAuditPosition != null) { 693 min = producerAuditPosition; 694 if (lastIndoubtPosition != null && lastIndoubtPosition.compareTo(producerAuditPosition) < 0) { 695 min = lastIndoubtPosition; 696 } 697 } else { 698 min = lastIndoubtPosition; 699 } 700 return min; 701 } 702 703 private Location recoverProducerAudit() throws IOException { 704 if (metadata.producerSequenceIdTrackerLocation != null) { 705 KahaProducerAuditCommand audit = (KahaProducerAuditCommand) load(metadata.producerSequenceIdTrackerLocation); 706 try { 707 ObjectInputStream objectIn = new ObjectInputStream(audit.getAudit().newInput()); 708 int maxNumProducers = getMaxFailoverProducersToTrack(); 709 int maxAuditDepth = getFailoverProducersAuditDepth(); 710 metadata.producerSequenceIdTracker = (ActiveMQMessageAuditNoSync) objectIn.readObject(); 711 metadata.producerSequenceIdTracker.setAuditDepth(maxAuditDepth); 712 metadata.producerSequenceIdTracker.setMaximumNumberOfProducersToTrack(maxNumProducers); 713 return journal.getNextLocation(metadata.producerSequenceIdTrackerLocation); 714 } catch (Exception e) { 715 LOG.warn("Cannot recover message audit", e); 716 return journal.getNextLocation(null); 717 } 718 } else { 719 // got no audit stored so got to recreate via replay from start of the journal 720 return journal.getNextLocation(null); 721 } 722 } 723 724 @SuppressWarnings("unchecked") 725 private Location recoverAckMessageFileMap() throws IOException { 726 if (metadata.ackMessageFileMapLocation != null) { 727 KahaAckMessageFileMapCommand audit = (KahaAckMessageFileMapCommand) load(metadata.ackMessageFileMapLocation); 728 try { 729 ObjectInputStream objectIn = new ObjectInputStream(audit.getAckMessageFileMap().newInput()); 730 metadata.ackMessageFileMap = (Map<Integer, Set<Integer>>) objectIn.readObject(); 731 return journal.getNextLocation(metadata.ackMessageFileMapLocation); 732 } catch (Exception e) { 733 LOG.warn("Cannot recover ackMessageFileMap", e); 734 return journal.getNextLocation(null); 735 } 736 } else { 737 // got no ackMessageFileMap stored so got to recreate via replay from start of the journal 738 return journal.getNextLocation(null); 739 } 740 } 741 742 protected void recoverIndex(Transaction tx) throws IOException { 743 long start = System.currentTimeMillis(); 744 // It is possible index updates got applied before the journal updates.. 745 // in that case we need to removed references to messages that are not in the journal 746 final Location lastAppendLocation = journal.getLastAppendLocation(); 747 long undoCounter=0; 748 749 // Go through all the destinations to see if they have messages past the lastAppendLocation 750 for (String key : storedDestinations.keySet()) { 751 StoredDestination sd = storedDestinations.get(key); 752 753 final ArrayList<Long> matches = new ArrayList<Long>(); 754 // Find all the Locations that are >= than the last Append Location. 755 sd.locationIndex.visit(tx, new BTreeVisitor.GTEVisitor<Location, Long>(lastAppendLocation) { 756 @Override 757 protected void matched(Location key, Long value) { 758 matches.add(value); 759 } 760 }); 761 762 for (Long sequenceId : matches) { 763 MessageKeys keys = sd.orderIndex.remove(tx, sequenceId); 764 sd.locationIndex.remove(tx, keys.location); 765 sd.messageIdIndex.remove(tx, keys.messageId); 766 metadata.producerSequenceIdTracker.rollback(keys.messageId); 767 undoCounter++; 768 decrementAndSubSizeToStoreStat(key, keys.location.getSize()); 769 // TODO: do we need to modify the ack positions for the pub sub case? 770 } 771 } 772 773 if( undoCounter > 0 ) { 774 // The rolledback operations are basically in flight journal writes. To avoid getting 775 // these the end user should do sync writes to the journal. 776 if (LOG.isInfoEnabled()) { 777 long end = System.currentTimeMillis(); 778 LOG.info("Rolled back " + undoCounter + " messages from the index in " + ((end - start) / 1000.0f) + " seconds."); 779 } 780 } 781 782 undoCounter = 0; 783 start = System.currentTimeMillis(); 784 785 // Lets be extra paranoid here and verify that all the datafiles being referenced 786 // by the indexes still exists. 787 788 final SequenceSet ss = new SequenceSet(); 789 for (StoredDestination sd : storedDestinations.values()) { 790 // Use a visitor to cut down the number of pages that we load 791 sd.locationIndex.visit(tx, new BTreeVisitor<Location, Long>() { 792 int last=-1; 793 794 @Override 795 public boolean isInterestedInKeysBetween(Location first, Location second) { 796 if( first==null ) { 797 return !ss.contains(0, second.getDataFileId()); 798 } else if( second==null ) { 799 return true; 800 } else { 801 return !ss.contains(first.getDataFileId(), second.getDataFileId()); 802 } 803 } 804 805 @Override 806 public void visit(List<Location> keys, List<Long> values) { 807 for (Location l : keys) { 808 int fileId = l.getDataFileId(); 809 if( last != fileId ) { 810 ss.add(fileId); 811 last = fileId; 812 } 813 } 814 } 815 816 }); 817 } 818 HashSet<Integer> missingJournalFiles = new HashSet<Integer>(); 819 while (!ss.isEmpty()) { 820 missingJournalFiles.add((int) ss.removeFirst()); 821 } 822 missingJournalFiles.removeAll(journal.getFileMap().keySet()); 823 824 if (!missingJournalFiles.isEmpty()) { 825 if (LOG.isInfoEnabled()) { 826 LOG.info("Some journal files are missing: " + missingJournalFiles); 827 } 828 } 829 830 ArrayList<BTreeVisitor.Predicate<Location>> missingPredicates = new ArrayList<BTreeVisitor.Predicate<Location>>(); 831 for (Integer missing : missingJournalFiles) { 832 missingPredicates.add(new BTreeVisitor.BetweenVisitor<Location, Long>(new Location(missing, 0), new Location(missing + 1, 0))); 833 } 834 835 if (checkForCorruptJournalFiles) { 836 Collection<DataFile> dataFiles = journal.getFileMap().values(); 837 for (DataFile dataFile : dataFiles) { 838 int id = dataFile.getDataFileId(); 839 missingPredicates.add(new BTreeVisitor.BetweenVisitor<Location, Long>(new Location(id, dataFile.getLength()), new Location(id + 1, 0))); 840 Sequence seq = dataFile.getCorruptedBlocks().getHead(); 841 while (seq != null) { 842 missingPredicates.add(new BTreeVisitor.BetweenVisitor<Location, Long>(new Location(id, (int) seq.getFirst()), new Location(id, (int) seq.getLast() + 1))); 843 seq = seq.getNext(); 844 } 845 } 846 } 847 848 if (!missingPredicates.isEmpty()) { 849 for (Entry<String, StoredDestination> sdEntry : storedDestinations.entrySet()) { 850 final StoredDestination sd = sdEntry.getValue(); 851 final ArrayList<Long> matches = new ArrayList<Long>(); 852 sd.locationIndex.visit(tx, new BTreeVisitor.OrVisitor<Location, Long>(missingPredicates) { 853 @Override 854 protected void matched(Location key, Long value) { 855 matches.add(value); 856 } 857 }); 858 859 // If somes message references are affected by the missing data files... 860 if (!matches.isEmpty()) { 861 862 // We either 'gracefully' recover dropping the missing messages or 863 // we error out. 864 if( ignoreMissingJournalfiles ) { 865 // Update the index to remove the references to the missing data 866 for (Long sequenceId : matches) { 867 MessageKeys keys = sd.orderIndex.remove(tx, sequenceId); 868 sd.locationIndex.remove(tx, keys.location); 869 sd.messageIdIndex.remove(tx, keys.messageId); 870 LOG.info("[" + sdEntry.getKey() + "] dropped: " + keys.messageId + " at corrupt location: " + keys.location); 871 undoCounter++; 872 decrementAndSubSizeToStoreStat(sdEntry.getKey(), keys.location.getSize()); 873 // TODO: do we need to modify the ack positions for the pub sub case? 874 } 875 } else { 876 throw new IOException("Detected missing/corrupt journal files. "+matches.size()+" messages affected."); 877 } 878 } 879 } 880 } 881 882 if( undoCounter > 0 ) { 883 // The rolledback operations are basically in flight journal writes. To avoid getting these the end user 884 // should do sync writes to the journal. 885 if (LOG.isInfoEnabled()) { 886 long end = System.currentTimeMillis(); 887 LOG.info("Detected missing/corrupt journal files. Dropped " + undoCounter + " messages from the index in " + ((end - start) / 1000.0f) + " seconds."); 888 } 889 } 890 } 891 892 private Location nextRecoveryPosition; 893 private Location lastRecoveryPosition; 894 895 public void incrementalRecover() throws IOException { 896 this.indexLock.writeLock().lock(); 897 try { 898 if( nextRecoveryPosition == null ) { 899 if( lastRecoveryPosition==null ) { 900 nextRecoveryPosition = getRecoveryPosition(); 901 } else { 902 nextRecoveryPosition = journal.getNextLocation(lastRecoveryPosition); 903 } 904 } 905 while (nextRecoveryPosition != null) { 906 lastRecoveryPosition = nextRecoveryPosition; 907 metadata.lastUpdate = lastRecoveryPosition; 908 JournalCommand<?> message = load(lastRecoveryPosition); 909 process(message, lastRecoveryPosition, (IndexAware) null); 910 nextRecoveryPosition = journal.getNextLocation(lastRecoveryPosition); 911 } 912 } finally { 913 this.indexLock.writeLock().unlock(); 914 } 915 } 916 917 public Location getLastUpdatePosition() throws IOException { 918 return metadata.lastUpdate; 919 } 920 921 private Location getRecoveryPosition() throws IOException { 922 923 if (!this.forceRecoverIndex) { 924 925 // If we need to recover the transactions.. 926 if (metadata.firstInProgressTransactionLocation != null) { 927 return metadata.firstInProgressTransactionLocation; 928 } 929 930 // Perhaps there were no transactions... 931 if( metadata.lastUpdate!=null) { 932 // Start replay at the record after the last one recorded in the index file. 933 return journal.getNextLocation(metadata.lastUpdate); 934 } 935 } 936 // This loads the first position. 937 return journal.getNextLocation(null); 938 } 939 940 protected void checkpointCleanup(final boolean cleanup) throws IOException { 941 long start; 942 this.indexLock.writeLock().lock(); 943 try { 944 start = System.currentTimeMillis(); 945 if( !opened.get() ) { 946 return; 947 } 948 } finally { 949 this.indexLock.writeLock().unlock(); 950 } 951 checkpointUpdate(cleanup); 952 long end = System.currentTimeMillis(); 953 if (LOG_SLOW_ACCESS_TIME > 0 && end - start > LOG_SLOW_ACCESS_TIME) { 954 if (LOG.isInfoEnabled()) { 955 LOG.info("Slow KahaDB access: cleanup took " + (end - start)); 956 } 957 } 958 } 959 960 public ByteSequence toByteSequence(JournalCommand<?> data) throws IOException { 961 int size = data.serializedSizeFramed(); 962 DataByteArrayOutputStream os = new DataByteArrayOutputStream(size + 1); 963 os.writeByte(data.type().getNumber()); 964 data.writeFramed(os); 965 return os.toByteSequence(); 966 } 967 968 // ///////////////////////////////////////////////////////////////// 969 // Methods call by the broker to update and query the store. 970 // ///////////////////////////////////////////////////////////////// 971 public Location store(JournalCommand<?> data) throws IOException { 972 return store(data, false, null,null); 973 } 974 975 public Location store(JournalCommand<?> data, Runnable onJournalStoreComplete) throws IOException { 976 return store(data, false, null, null, onJournalStoreComplete); 977 } 978 979 public Location store(JournalCommand<?> data, boolean sync, IndexAware before,Runnable after) throws IOException { 980 return store(data, sync, before, after, null); 981 } 982 983 /** 984 * All updated are are funneled through this method. The updates are converted 985 * to a JournalMessage which is logged to the journal and then the data from 986 * the JournalMessage is used to update the index just like it would be done 987 * during a recovery process. 988 */ 989 public Location store(JournalCommand<?> data, boolean sync, IndexAware before, Runnable after, Runnable onJournalStoreComplete) throws IOException { 990 try { 991 ByteSequence sequence = toByteSequence(data); 992 993 Location location; 994 checkpointLock.readLock().lock(); 995 try { 996 997 long start = System.currentTimeMillis(); 998 location = onJournalStoreComplete == null ? journal.write(sequence, sync) : journal.write(sequence, onJournalStoreComplete) ; 999 long start2 = System.currentTimeMillis(); 1000 process(data, location, before); 1001 1002 long end = System.currentTimeMillis(); 1003 if( LOG_SLOW_ACCESS_TIME>0 && end-start > LOG_SLOW_ACCESS_TIME) { 1004 if (LOG.isInfoEnabled()) { 1005 LOG.info("Slow KahaDB access: Journal append took: "+(start2-start)+" ms, Index update took "+(end-start2)+" ms"); 1006 } 1007 } 1008 1009 } finally{ 1010 checkpointLock.readLock().unlock(); 1011 } 1012 if (after != null) { 1013 after.run(); 1014 } 1015 1016 if (checkpointThread != null && !checkpointThread.isAlive() && opened.get()) { 1017 startCheckpoint(); 1018 } 1019 return location; 1020 } catch (IOException ioe) { 1021 LOG.error("KahaDB failed to store to Journal", ioe); 1022 brokerService.handleIOException(ioe); 1023 throw ioe; 1024 } 1025 } 1026 1027 /** 1028 * Loads a previously stored JournalMessage 1029 * 1030 * @param location 1031 * @return 1032 * @throws IOException 1033 */ 1034 public JournalCommand<?> load(Location location) throws IOException { 1035 long start = System.currentTimeMillis(); 1036 ByteSequence data = journal.read(location); 1037 long end = System.currentTimeMillis(); 1038 if( LOG_SLOW_ACCESS_TIME>0 && end-start > LOG_SLOW_ACCESS_TIME) { 1039 if (LOG.isInfoEnabled()) { 1040 LOG.info("Slow KahaDB access: Journal read took: "+(end-start)+" ms"); 1041 } 1042 } 1043 DataByteArrayInputStream is = new DataByteArrayInputStream(data); 1044 byte readByte = is.readByte(); 1045 KahaEntryType type = KahaEntryType.valueOf(readByte); 1046 if( type == null ) { 1047 try { 1048 is.close(); 1049 } catch (IOException e) {} 1050 throw new IOException("Could not load journal record. Invalid location: "+location); 1051 } 1052 JournalCommand<?> message = (JournalCommand<?>)type.createMessage(); 1053 message.mergeFramed(is); 1054 return message; 1055 } 1056 1057 /** 1058 * do minimal recovery till we reach the last inDoubtLocation 1059 * @param data 1060 * @param location 1061 * @param inDoubtlocation 1062 * @throws IOException 1063 */ 1064 void process(JournalCommand<?> data, final Location location, final Location inDoubtlocation) throws IOException { 1065 if (inDoubtlocation != null && location.compareTo(inDoubtlocation) >= 0) { 1066 process(data, location, (IndexAware) null); 1067 } else { 1068 // just recover producer audit 1069 data.visit(new Visitor() { 1070 @Override 1071 public void visit(KahaAddMessageCommand command) throws IOException { 1072 metadata.producerSequenceIdTracker.isDuplicate(command.getMessageId()); 1073 } 1074 }); 1075 } 1076 } 1077 1078 // ///////////////////////////////////////////////////////////////// 1079 // Journaled record processing methods. Once the record is journaled, 1080 // these methods handle applying the index updates. These may be called 1081 // from the recovery method too so they need to be idempotent 1082 // ///////////////////////////////////////////////////////////////// 1083 1084 void process(JournalCommand<?> data, final Location location, final IndexAware onSequenceAssignedCallback) throws IOException { 1085 data.visit(new Visitor() { 1086 @Override 1087 public void visit(KahaAddMessageCommand command) throws IOException { 1088 process(command, location, onSequenceAssignedCallback); 1089 } 1090 1091 @Override 1092 public void visit(KahaRemoveMessageCommand command) throws IOException { 1093 process(command, location); 1094 } 1095 1096 @Override 1097 public void visit(KahaPrepareCommand command) throws IOException { 1098 process(command, location); 1099 } 1100 1101 @Override 1102 public void visit(KahaCommitCommand command) throws IOException { 1103 process(command, location, onSequenceAssignedCallback); 1104 } 1105 1106 @Override 1107 public void visit(KahaRollbackCommand command) throws IOException { 1108 process(command, location); 1109 } 1110 1111 @Override 1112 public void visit(KahaRemoveDestinationCommand command) throws IOException { 1113 process(command, location); 1114 } 1115 1116 @Override 1117 public void visit(KahaSubscriptionCommand command) throws IOException { 1118 process(command, location); 1119 } 1120 1121 @Override 1122 public void visit(KahaProducerAuditCommand command) throws IOException { 1123 processLocation(location); 1124 } 1125 1126 @Override 1127 public void visit(KahaAckMessageFileMapCommand command) throws IOException { 1128 processLocation(location); 1129 } 1130 1131 @Override 1132 public void visit(KahaTraceCommand command) { 1133 processLocation(location); 1134 } 1135 1136 @Override 1137 public void visit(KahaUpdateMessageCommand command) throws IOException { 1138 process(command, location); 1139 } 1140 }); 1141 } 1142 1143 @SuppressWarnings("rawtypes") 1144 protected void process(final KahaAddMessageCommand command, final Location location, final IndexAware runWithIndexLock) throws IOException { 1145 if (command.hasTransactionInfo()) { 1146 List<Operation> inflightTx = getInflightTx(command.getTransactionInfo()); 1147 inflightTx.add(new AddOperation(command, location, runWithIndexLock)); 1148 } else { 1149 this.indexLock.writeLock().lock(); 1150 try { 1151 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1152 @Override 1153 public void execute(Transaction tx) throws IOException { 1154 long assignedIndex = updateIndex(tx, command, location); 1155 if (runWithIndexLock != null) { 1156 runWithIndexLock.sequenceAssignedWithIndexLocked(assignedIndex); 1157 } 1158 } 1159 }); 1160 1161 } finally { 1162 this.indexLock.writeLock().unlock(); 1163 } 1164 } 1165 } 1166 1167 protected void process(final KahaUpdateMessageCommand command, final Location location) throws IOException { 1168 this.indexLock.writeLock().lock(); 1169 try { 1170 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1171 @Override 1172 public void execute(Transaction tx) throws IOException { 1173 updateIndex(tx, command, location); 1174 } 1175 }); 1176 } finally { 1177 this.indexLock.writeLock().unlock(); 1178 } 1179 } 1180 1181 @SuppressWarnings("rawtypes") 1182 protected void process(final KahaRemoveMessageCommand command, final Location location) throws IOException { 1183 if (command.hasTransactionInfo()) { 1184 List<Operation> inflightTx = getInflightTx(command.getTransactionInfo()); 1185 inflightTx.add(new RemoveOperation(command, location)); 1186 } else { 1187 this.indexLock.writeLock().lock(); 1188 try { 1189 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1190 @Override 1191 public void execute(Transaction tx) throws IOException { 1192 updateIndex(tx, command, location); 1193 } 1194 }); 1195 } finally { 1196 this.indexLock.writeLock().unlock(); 1197 } 1198 } 1199 } 1200 1201 protected void process(final KahaRemoveDestinationCommand command, final Location location) throws IOException { 1202 this.indexLock.writeLock().lock(); 1203 try { 1204 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1205 @Override 1206 public void execute(Transaction tx) throws IOException { 1207 updateIndex(tx, command, location); 1208 } 1209 }); 1210 } finally { 1211 this.indexLock.writeLock().unlock(); 1212 } 1213 } 1214 1215 protected void process(final KahaSubscriptionCommand command, final Location location) throws IOException { 1216 this.indexLock.writeLock().lock(); 1217 try { 1218 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1219 @Override 1220 public void execute(Transaction tx) throws IOException { 1221 updateIndex(tx, command, location); 1222 } 1223 }); 1224 } finally { 1225 this.indexLock.writeLock().unlock(); 1226 } 1227 } 1228 1229 protected void processLocation(final Location location) { 1230 this.indexLock.writeLock().lock(); 1231 try { 1232 metadata.lastUpdate = location; 1233 } finally { 1234 this.indexLock.writeLock().unlock(); 1235 } 1236 } 1237 1238 @SuppressWarnings("rawtypes") 1239 protected void process(KahaCommitCommand command, final Location location, final IndexAware before) throws IOException { 1240 TransactionId key = TransactionIdConversion.convert(command.getTransactionInfo()); 1241 List<Operation> inflightTx; 1242 synchronized (inflightTransactions) { 1243 inflightTx = inflightTransactions.remove(key); 1244 if (inflightTx == null) { 1245 inflightTx = preparedTransactions.remove(key); 1246 } 1247 } 1248 if (inflightTx == null) { 1249 // only non persistent messages in this tx 1250 if (before != null) { 1251 before.sequenceAssignedWithIndexLocked(-1); 1252 } 1253 return; 1254 } 1255 1256 final List<Operation> messagingTx = inflightTx; 1257 indexLock.writeLock().lock(); 1258 try { 1259 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1260 @Override 1261 public void execute(Transaction tx) throws IOException { 1262 for (Operation op : messagingTx) { 1263 op.execute(tx); 1264 } 1265 } 1266 }); 1267 metadata.lastUpdate = location; 1268 } finally { 1269 indexLock.writeLock().unlock(); 1270 } 1271 } 1272 1273 @SuppressWarnings("rawtypes") 1274 protected void process(KahaPrepareCommand command, Location location) { 1275 TransactionId key = TransactionIdConversion.convert(command.getTransactionInfo()); 1276 synchronized (inflightTransactions) { 1277 List<Operation> tx = inflightTransactions.remove(key); 1278 if (tx != null) { 1279 preparedTransactions.put(key, tx); 1280 } 1281 } 1282 } 1283 1284 @SuppressWarnings("rawtypes") 1285 protected void process(KahaRollbackCommand command, Location location) throws IOException { 1286 TransactionId key = TransactionIdConversion.convert(command.getTransactionInfo()); 1287 List<Operation> updates = null; 1288 synchronized (inflightTransactions) { 1289 updates = inflightTransactions.remove(key); 1290 if (updates == null) { 1291 updates = preparedTransactions.remove(key); 1292 } 1293 } 1294 } 1295 1296 // ///////////////////////////////////////////////////////////////// 1297 // These methods do the actual index updates. 1298 // ///////////////////////////////////////////////////////////////// 1299 1300 protected final ReentrantReadWriteLock indexLock = new ReentrantReadWriteLock(); 1301 private final HashSet<Integer> journalFilesBeingReplicated = new HashSet<Integer>(); 1302 1303 long updateIndex(Transaction tx, KahaAddMessageCommand command, Location location) throws IOException { 1304 StoredDestination sd = getStoredDestination(command.getDestination(), tx); 1305 1306 // Skip adding the message to the index if this is a topic and there are 1307 // no subscriptions. 1308 if (sd.subscriptions != null && sd.subscriptions.isEmpty(tx)) { 1309 return -1; 1310 } 1311 1312 // Add the message. 1313 int priority = command.getPrioritySupported() ? command.getPriority() : javax.jms.Message.DEFAULT_PRIORITY; 1314 long id = sd.orderIndex.getNextMessageId(priority); 1315 Long previous = sd.locationIndex.put(tx, location, id); 1316 if (previous == null) { 1317 previous = sd.messageIdIndex.put(tx, command.getMessageId(), id); 1318 if (previous == null) { 1319 incrementAndAddSizeToStoreStat(command.getDestination(), location.getSize()); 1320 sd.orderIndex.put(tx, priority, id, new MessageKeys(command.getMessageId(), location)); 1321 if (sd.subscriptions != null && !sd.subscriptions.isEmpty(tx)) { 1322 addAckLocationForNewMessage(tx, sd, id); 1323 } 1324 metadata.lastUpdate = location; 1325 } else { 1326 1327 MessageKeys messageKeys = sd.orderIndex.get(tx, previous); 1328 if (messageKeys != null && messageKeys.location.compareTo(location) < 0) { 1329 // If the message ID is indexed, then the broker asked us to store a duplicate before the message was dispatched and acked, we ignore this add attempt 1330 LOG.warn("Duplicate message add attempt rejected. Destination: {}://{}, Message id: {}", command.getDestination().getType(), command.getDestination().getName(), command.getMessageId()); 1331 } 1332 sd.messageIdIndex.put(tx, command.getMessageId(), previous); 1333 sd.locationIndex.remove(tx, location); 1334 id = -1; 1335 } 1336 } else { 1337 // restore the previous value.. Looks like this was a redo of a previously 1338 // added message. We don't want to assign it a new id as the other indexes would 1339 // be wrong.. 1340 sd.locationIndex.put(tx, location, previous); 1341 metadata.lastUpdate = location; 1342 } 1343 // record this id in any event, initial send or recovery 1344 metadata.producerSequenceIdTracker.isDuplicate(command.getMessageId()); 1345 1346 return id; 1347 } 1348 1349 void trackPendingAdd(KahaDestination destination, Long seq) { 1350 StoredDestination sd = storedDestinations.get(key(destination)); 1351 if (sd != null) { 1352 sd.trackPendingAdd(seq); 1353 } 1354 } 1355 1356 void trackPendingAddComplete(KahaDestination destination, Long seq) { 1357 StoredDestination sd = storedDestinations.get(key(destination)); 1358 if (sd != null) { 1359 sd.trackPendingAddComplete(seq); 1360 } 1361 } 1362 1363 void updateIndex(Transaction tx, KahaUpdateMessageCommand updateMessageCommand, Location location) throws IOException { 1364 KahaAddMessageCommand command = updateMessageCommand.getMessage(); 1365 StoredDestination sd = getStoredDestination(command.getDestination(), tx); 1366 1367 Long id = sd.messageIdIndex.get(tx, command.getMessageId()); 1368 if (id != null) { 1369 MessageKeys previousKeys = sd.orderIndex.put( 1370 tx, 1371 command.getPrioritySupported() ? command.getPriority() : javax.jms.Message.DEFAULT_PRIORITY, 1372 id, 1373 new MessageKeys(command.getMessageId(), location) 1374 ); 1375 sd.locationIndex.put(tx, location, id); 1376 incrementAndAddSizeToStoreStat(command.getDestination(), location.getSize()); 1377 // on first update previous is original location, on recovery/replay it may be the updated location 1378 if(previousKeys != null && !previousKeys.location.equals(location)) { 1379 sd.locationIndex.remove(tx, previousKeys.location); 1380 decrementAndSubSizeToStoreStat(command.getDestination(), previousKeys.location.getSize()); 1381 } 1382 metadata.lastUpdate = location; 1383 } else { 1384 LOG.warn("Non existent message update attempt rejected. Destination: {}://{}, Message id: {}", command.getDestination().getType(), command.getDestination().getName(), command.getMessageId()); 1385 } 1386 } 1387 1388 void updateIndex(Transaction tx, KahaRemoveMessageCommand command, Location ackLocation) throws IOException { 1389 StoredDestination sd = getStoredDestination(command.getDestination(), tx); 1390 if (!command.hasSubscriptionKey()) { 1391 1392 // In the queue case we just remove the message from the index.. 1393 Long sequenceId = sd.messageIdIndex.remove(tx, command.getMessageId()); 1394 if (sequenceId != null) { 1395 MessageKeys keys = sd.orderIndex.remove(tx, sequenceId); 1396 if (keys != null) { 1397 sd.locationIndex.remove(tx, keys.location); 1398 decrementAndSubSizeToStoreStat(command.getDestination(), keys.location.getSize()); 1399 recordAckMessageReferenceLocation(ackLocation, keys.location); 1400 metadata.lastUpdate = ackLocation; 1401 } else if (LOG.isDebugEnabled()) { 1402 LOG.debug("message not found in order index: " + sequenceId + " for: " + command.getMessageId()); 1403 } 1404 } else if (LOG.isDebugEnabled()) { 1405 LOG.debug("message not found in sequence id index: " + command.getMessageId()); 1406 } 1407 } else { 1408 // In the topic case we need remove the message once it's been acked 1409 // by all the subs 1410 Long sequence = sd.messageIdIndex.get(tx, command.getMessageId()); 1411 1412 // Make sure it's a valid message id... 1413 if (sequence != null) { 1414 String subscriptionKey = command.getSubscriptionKey(); 1415 if (command.getAck() != UNMATCHED) { 1416 sd.orderIndex.get(tx, sequence); 1417 byte priority = sd.orderIndex.lastGetPriority(); 1418 sd.subscriptionAcks.put(tx, subscriptionKey, new LastAck(sequence, priority)); 1419 } 1420 1421 MessageKeys keys = sd.orderIndex.get(tx, sequence); 1422 if (keys != null) { 1423 recordAckMessageReferenceLocation(ackLocation, keys.location); 1424 } 1425 // The following method handles deleting un-referenced messages. 1426 removeAckLocation(command, tx, sd, subscriptionKey, sequence); 1427 metadata.lastUpdate = ackLocation; 1428 } else if (LOG.isDebugEnabled()) { 1429 LOG.debug("no message sequence exists for id: " + command.getMessageId() + " and sub: " + command.getSubscriptionKey()); 1430 } 1431 1432 } 1433 } 1434 1435 private void recordAckMessageReferenceLocation(Location ackLocation, Location messageLocation) { 1436 Set<Integer> referenceFileIds = metadata.ackMessageFileMap.get(Integer.valueOf(ackLocation.getDataFileId())); 1437 if (referenceFileIds == null) { 1438 referenceFileIds = new HashSet<Integer>(); 1439 referenceFileIds.add(messageLocation.getDataFileId()); 1440 metadata.ackMessageFileMap.put(ackLocation.getDataFileId(), referenceFileIds); 1441 } else { 1442 Integer id = Integer.valueOf(messageLocation.getDataFileId()); 1443 if (!referenceFileIds.contains(id)) { 1444 referenceFileIds.add(id); 1445 } 1446 } 1447 } 1448 1449 void updateIndex(Transaction tx, KahaRemoveDestinationCommand command, Location location) throws IOException { 1450 StoredDestination sd = getStoredDestination(command.getDestination(), tx); 1451 sd.orderIndex.remove(tx); 1452 1453 sd.locationIndex.clear(tx); 1454 sd.locationIndex.unload(tx); 1455 tx.free(sd.locationIndex.getPageId()); 1456 1457 sd.messageIdIndex.clear(tx); 1458 sd.messageIdIndex.unload(tx); 1459 tx.free(sd.messageIdIndex.getPageId()); 1460 1461 if (sd.subscriptions != null) { 1462 sd.subscriptions.clear(tx); 1463 sd.subscriptions.unload(tx); 1464 tx.free(sd.subscriptions.getPageId()); 1465 1466 sd.subscriptionAcks.clear(tx); 1467 sd.subscriptionAcks.unload(tx); 1468 tx.free(sd.subscriptionAcks.getPageId()); 1469 1470 sd.ackPositions.clear(tx); 1471 sd.ackPositions.unload(tx); 1472 tx.free(sd.ackPositions.getHeadPageId()); 1473 1474 sd.subLocations.clear(tx); 1475 sd.subLocations.unload(tx); 1476 tx.free(sd.subLocations.getHeadPageId()); 1477 } 1478 1479 String key = key(command.getDestination()); 1480 storedDestinations.remove(key); 1481 metadata.destinations.remove(tx, key); 1482 clearStoreStats(command.getDestination()); 1483 storeCache.remove(key(command.getDestination())); 1484 } 1485 1486 void updateIndex(Transaction tx, KahaSubscriptionCommand command, Location location) throws IOException { 1487 StoredDestination sd = getStoredDestination(command.getDestination(), tx); 1488 final String subscriptionKey = command.getSubscriptionKey(); 1489 1490 // If set then we are creating it.. otherwise we are destroying the sub 1491 if (command.hasSubscriptionInfo()) { 1492 Location existing = sd.subLocations.get(tx, subscriptionKey); 1493 if (existing != null && existing.compareTo(location) == 0) { 1494 // replay on recovery, ignore 1495 LOG.trace("ignoring journal replay of replay of sub from: " + location); 1496 return; 1497 } 1498 1499 sd.subscriptions.put(tx, subscriptionKey, command); 1500 sd.subLocations.put(tx, subscriptionKey, location); 1501 long ackLocation=NOT_ACKED; 1502 if (!command.getRetroactive()) { 1503 ackLocation = sd.orderIndex.nextMessageId-1; 1504 } else { 1505 addAckLocationForRetroactiveSub(tx, sd, subscriptionKey); 1506 } 1507 sd.subscriptionAcks.put(tx, subscriptionKey, new LastAck(ackLocation)); 1508 sd.subscriptionCache.add(subscriptionKey); 1509 } else { 1510 // delete the sub... 1511 sd.subscriptions.remove(tx, subscriptionKey); 1512 sd.subLocations.remove(tx, subscriptionKey); 1513 sd.subscriptionAcks.remove(tx, subscriptionKey); 1514 sd.subscriptionCache.remove(subscriptionKey); 1515 removeAckLocationsForSub(command, tx, sd, subscriptionKey); 1516 1517 if (sd.subscriptions.isEmpty(tx)) { 1518 // remove the stored destination 1519 KahaRemoveDestinationCommand removeDestinationCommand = new KahaRemoveDestinationCommand(); 1520 removeDestinationCommand.setDestination(command.getDestination()); 1521 updateIndex(tx, removeDestinationCommand, null); 1522 clearStoreStats(command.getDestination()); 1523 } 1524 } 1525 } 1526 1527 private void checkpointUpdate(final boolean cleanup) throws IOException { 1528 checkpointLock.writeLock().lock(); 1529 try { 1530 this.indexLock.writeLock().lock(); 1531 try { 1532 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1533 @Override 1534 public void execute(Transaction tx) throws IOException { 1535 checkpointUpdate(tx, cleanup); 1536 } 1537 }); 1538 } finally { 1539 this.indexLock.writeLock().unlock(); 1540 } 1541 1542 } finally { 1543 checkpointLock.writeLock().unlock(); 1544 } 1545 } 1546 1547 /** 1548 * @param tx 1549 * @throws IOException 1550 */ 1551 void checkpointUpdate(Transaction tx, boolean cleanup) throws IOException { 1552 LOG.debug("Checkpoint started."); 1553 1554 // reflect last update exclusive of current checkpoint 1555 Location lastUpdate = metadata.lastUpdate; 1556 1557 metadata.state = OPEN_STATE; 1558 metadata.producerSequenceIdTrackerLocation = checkpointProducerAudit(); 1559 metadata.ackMessageFileMapLocation = checkpointAckMessageFileMap(); 1560 Location[] inProgressTxRange = getInProgressTxLocationRange(); 1561 metadata.firstInProgressTransactionLocation = inProgressTxRange[0]; 1562 tx.store(metadata.page, metadataMarshaller, true); 1563 pageFile.flush(); 1564 1565 if( cleanup ) { 1566 1567 final TreeSet<Integer> completeFileSet = new TreeSet<Integer>(journal.getFileMap().keySet()); 1568 final TreeSet<Integer> gcCandidateSet = new TreeSet<Integer>(completeFileSet); 1569 1570 if (LOG.isTraceEnabled()) { 1571 LOG.trace("Last update: " + lastUpdate + ", full gc candidates set: " + gcCandidateSet); 1572 } 1573 1574 if (lastUpdate != null) { 1575 gcCandidateSet.remove(lastUpdate.getDataFileId()); 1576 } 1577 1578 // Don't GC files under replication 1579 if( journalFilesBeingReplicated!=null ) { 1580 gcCandidateSet.removeAll(journalFilesBeingReplicated); 1581 } 1582 1583 if (metadata.producerSequenceIdTrackerLocation != null) { 1584 int dataFileId = metadata.producerSequenceIdTrackerLocation.getDataFileId(); 1585 if (gcCandidateSet.contains(dataFileId) && gcCandidateSet.first() == dataFileId) { 1586 // rewrite so we don't prevent gc 1587 metadata.producerSequenceIdTracker.setModified(true); 1588 if (LOG.isTraceEnabled()) { 1589 LOG.trace("rewriting producerSequenceIdTracker:" + metadata.producerSequenceIdTrackerLocation); 1590 } 1591 } 1592 gcCandidateSet.remove(dataFileId); 1593 if (LOG.isTraceEnabled()) { 1594 LOG.trace("gc candidates after producerSequenceIdTrackerLocation:" + dataFileId + ", " + gcCandidateSet); 1595 } 1596 } 1597 1598 if (metadata.ackMessageFileMapLocation != null) { 1599 int dataFileId = metadata.ackMessageFileMapLocation.getDataFileId(); 1600 gcCandidateSet.remove(dataFileId); 1601 if (LOG.isTraceEnabled()) { 1602 LOG.trace("gc candidates after ackMessageFileMapLocation:" + dataFileId + ", " + gcCandidateSet); 1603 } 1604 } 1605 1606 // Don't GC files referenced by in-progress tx 1607 if (inProgressTxRange[0] != null) { 1608 for (int pendingTx=inProgressTxRange[0].getDataFileId(); pendingTx <= inProgressTxRange[1].getDataFileId(); pendingTx++) { 1609 gcCandidateSet.remove(pendingTx); 1610 } 1611 } 1612 if (LOG.isTraceEnabled()) { 1613 LOG.trace("gc candidates after tx range:" + Arrays.asList(inProgressTxRange) + ", " + gcCandidateSet); 1614 } 1615 1616 // Go through all the destinations to see if any of them can remove GC candidates. 1617 for (Entry<String, StoredDestination> entry : storedDestinations.entrySet()) { 1618 if( gcCandidateSet.isEmpty() ) { 1619 break; 1620 } 1621 1622 // Use a visitor to cut down the number of pages that we load 1623 entry.getValue().locationIndex.visit(tx, new BTreeVisitor<Location, Long>() { 1624 int last=-1; 1625 @Override 1626 public boolean isInterestedInKeysBetween(Location first, Location second) { 1627 if( first==null ) { 1628 SortedSet<Integer> subset = gcCandidateSet.headSet(second.getDataFileId()+1); 1629 if( !subset.isEmpty() && subset.last() == second.getDataFileId() ) { 1630 subset.remove(second.getDataFileId()); 1631 } 1632 return !subset.isEmpty(); 1633 } else if( second==null ) { 1634 SortedSet<Integer> subset = gcCandidateSet.tailSet(first.getDataFileId()); 1635 if( !subset.isEmpty() && subset.first() == first.getDataFileId() ) { 1636 subset.remove(first.getDataFileId()); 1637 } 1638 return !subset.isEmpty(); 1639 } else { 1640 SortedSet<Integer> subset = gcCandidateSet.subSet(first.getDataFileId(), second.getDataFileId()+1); 1641 if( !subset.isEmpty() && subset.first() == first.getDataFileId() ) { 1642 subset.remove(first.getDataFileId()); 1643 } 1644 if( !subset.isEmpty() && subset.last() == second.getDataFileId() ) { 1645 subset.remove(second.getDataFileId()); 1646 } 1647 return !subset.isEmpty(); 1648 } 1649 } 1650 1651 @Override 1652 public void visit(List<Location> keys, List<Long> values) { 1653 for (Location l : keys) { 1654 int fileId = l.getDataFileId(); 1655 if( last != fileId ) { 1656 gcCandidateSet.remove(fileId); 1657 last = fileId; 1658 } 1659 } 1660 } 1661 }); 1662 1663 // Durable Subscription 1664 if (entry.getValue().subLocations != null) { 1665 Iterator<Entry<String, Location>> iter = entry.getValue().subLocations.iterator(tx); 1666 while (iter.hasNext()) { 1667 Entry<String, Location> subscription = iter.next(); 1668 int dataFileId = subscription.getValue().getDataFileId(); 1669 1670 // Move subscription along if it has no outstanding messages that need ack'd 1671 // and its in the last log file in the journal. 1672 if (!gcCandidateSet.isEmpty() && gcCandidateSet.first() == dataFileId) { 1673 final StoredDestination destination = entry.getValue(); 1674 final String subscriptionKey = subscription.getKey(); 1675 SequenceSet pendingAcks = destination.ackPositions.get(tx, subscriptionKey); 1676 1677 // When pending is size one that is the next message Id meaning there 1678 // are no pending messages currently. 1679 if (pendingAcks == null || pendingAcks.size() <= 1) { 1680 if (LOG.isTraceEnabled()) { 1681 LOG.trace("Found candidate for rewrite: {} from file {}", entry.getKey(), dataFileId); 1682 } 1683 1684 final KahaSubscriptionCommand kahaSub = 1685 destination.subscriptions.get(tx, subscriptionKey); 1686 destination.subLocations.put( 1687 tx, subscriptionKey, checkpointSubscriptionCommand(kahaSub)); 1688 1689 // Skips the remove from candidates if we rewrote the subscription 1690 // in order to prevent duplicate subscription commands on recover. 1691 // If another subscription is on the same file and isn't rewritten 1692 // than it will remove the file from the set. 1693 continue; 1694 } 1695 } 1696 1697 gcCandidateSet.remove(dataFileId); 1698 } 1699 } 1700 1701 if (LOG.isTraceEnabled()) { 1702 LOG.trace("gc candidates after dest:" + entry.getKey() + ", " + gcCandidateSet); 1703 } 1704 } 1705 1706 // check we are not deleting file with ack for in-use journal files 1707 if (LOG.isTraceEnabled()) { 1708 LOG.trace("gc candidates: " + gcCandidateSet); 1709 } 1710 Iterator<Integer> candidates = gcCandidateSet.iterator(); 1711 while (candidates.hasNext()) { 1712 Integer candidate = candidates.next(); 1713 Set<Integer> referencedFileIds = metadata.ackMessageFileMap.get(candidate); 1714 if (referencedFileIds != null) { 1715 for (Integer referencedFileId : referencedFileIds) { 1716 if (completeFileSet.contains(referencedFileId) && !gcCandidateSet.contains(referencedFileId)) { 1717 // active file that is not targeted for deletion is referenced so don't delete 1718 candidates.remove(); 1719 break; 1720 } 1721 } 1722 if (gcCandidateSet.contains(candidate)) { 1723 metadata.ackMessageFileMap.remove(candidate); 1724 } else { 1725 if (LOG.isTraceEnabled()) { 1726 LOG.trace("not removing data file: " + candidate 1727 + " as contained ack(s) refer to referenced file: " + referencedFileIds); 1728 } 1729 } 1730 } 1731 } 1732 1733 if (!gcCandidateSet.isEmpty()) { 1734 if (LOG.isDebugEnabled()) { 1735 LOG.debug("Cleanup removing the data files: " + gcCandidateSet); 1736 } 1737 journal.removeDataFiles(gcCandidateSet); 1738 } 1739 } 1740 1741 LOG.debug("Checkpoint done."); 1742 } 1743 1744 final Runnable nullCompletionCallback = new Runnable() { 1745 @Override 1746 public void run() { 1747 } 1748 }; 1749 1750 private Location checkpointProducerAudit() throws IOException { 1751 if (metadata.producerSequenceIdTracker == null || metadata.producerSequenceIdTracker.modified()) { 1752 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 1753 ObjectOutputStream oout = new ObjectOutputStream(baos); 1754 oout.writeObject(metadata.producerSequenceIdTracker); 1755 oout.flush(); 1756 oout.close(); 1757 // using completion callback allows a disk sync to be avoided when enableJournalDiskSyncs = false 1758 Location location = store(new KahaProducerAuditCommand().setAudit(new Buffer(baos.toByteArray())), nullCompletionCallback); 1759 try { 1760 location.getLatch().await(); 1761 } catch (InterruptedException e) { 1762 throw new InterruptedIOException(e.toString()); 1763 } 1764 return location; 1765 } 1766 return metadata.producerSequenceIdTrackerLocation; 1767 } 1768 1769 private Location checkpointAckMessageFileMap() throws IOException { 1770 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 1771 ObjectOutputStream oout = new ObjectOutputStream(baos); 1772 oout.writeObject(metadata.ackMessageFileMap); 1773 oout.flush(); 1774 oout.close(); 1775 // using completion callback allows a disk sync to be avoided when enableJournalDiskSyncs = false 1776 Location location = store(new KahaAckMessageFileMapCommand().setAckMessageFileMap(new Buffer(baos.toByteArray())), nullCompletionCallback); 1777 try { 1778 location.getLatch().await(); 1779 } catch (InterruptedException e) { 1780 throw new InterruptedIOException(e.toString()); 1781 } 1782 return location; 1783 } 1784 1785 private Location checkpointSubscriptionCommand(KahaSubscriptionCommand subscription) throws IOException { 1786 1787 ByteSequence sequence = toByteSequence(subscription); 1788 Location location = journal.write(sequence, nullCompletionCallback) ; 1789 1790 try { 1791 location.getLatch().await(); 1792 } catch (InterruptedException e) { 1793 throw new InterruptedIOException(e.toString()); 1794 } 1795 return location; 1796 } 1797 1798 public HashSet<Integer> getJournalFilesBeingReplicated() { 1799 return journalFilesBeingReplicated; 1800 } 1801 1802 // ///////////////////////////////////////////////////////////////// 1803 // StoredDestination related implementation methods. 1804 // ///////////////////////////////////////////////////////////////// 1805 1806 protected final HashMap<String, StoredDestination> storedDestinations = new HashMap<String, StoredDestination>(); 1807 1808 static class MessageKeys { 1809 final String messageId; 1810 final Location location; 1811 1812 public MessageKeys(String messageId, Location location) { 1813 this.messageId=messageId; 1814 this.location=location; 1815 } 1816 1817 @Override 1818 public String toString() { 1819 return "["+messageId+","+location+"]"; 1820 } 1821 } 1822 1823 protected class MessageKeysMarshaller extends VariableMarshaller<MessageKeys> { 1824 final LocationSizeMarshaller locationSizeMarshaller = new LocationSizeMarshaller(); 1825 1826 @Override 1827 public MessageKeys readPayload(DataInput dataIn) throws IOException { 1828 return new MessageKeys(dataIn.readUTF(), locationSizeMarshaller.readPayload(dataIn)); 1829 } 1830 1831 @Override 1832 public void writePayload(MessageKeys object, DataOutput dataOut) throws IOException { 1833 dataOut.writeUTF(object.messageId); 1834 locationSizeMarshaller.writePayload(object.location, dataOut); 1835 } 1836 } 1837 1838 class LastAck { 1839 long lastAckedSequence; 1840 byte priority; 1841 1842 public LastAck(LastAck source) { 1843 this.lastAckedSequence = source.lastAckedSequence; 1844 this.priority = source.priority; 1845 } 1846 1847 public LastAck() { 1848 this.priority = MessageOrderIndex.HI; 1849 } 1850 1851 public LastAck(long ackLocation) { 1852 this.lastAckedSequence = ackLocation; 1853 this.priority = MessageOrderIndex.LO; 1854 } 1855 1856 public LastAck(long ackLocation, byte priority) { 1857 this.lastAckedSequence = ackLocation; 1858 this.priority = priority; 1859 } 1860 1861 @Override 1862 public String toString() { 1863 return "[" + lastAckedSequence + ":" + priority + "]"; 1864 } 1865 } 1866 1867 protected class LastAckMarshaller implements Marshaller<LastAck> { 1868 1869 @Override 1870 public void writePayload(LastAck object, DataOutput dataOut) throws IOException { 1871 dataOut.writeLong(object.lastAckedSequence); 1872 dataOut.writeByte(object.priority); 1873 } 1874 1875 @Override 1876 public LastAck readPayload(DataInput dataIn) throws IOException { 1877 LastAck lastAcked = new LastAck(); 1878 lastAcked.lastAckedSequence = dataIn.readLong(); 1879 if (metadata.version >= 3) { 1880 lastAcked.priority = dataIn.readByte(); 1881 } 1882 return lastAcked; 1883 } 1884 1885 @Override 1886 public int getFixedSize() { 1887 return 9; 1888 } 1889 1890 @Override 1891 public LastAck deepCopy(LastAck source) { 1892 return new LastAck(source); 1893 } 1894 1895 @Override 1896 public boolean isDeepCopySupported() { 1897 return true; 1898 } 1899 } 1900 1901 1902 class StoredDestination { 1903 1904 MessageOrderIndex orderIndex = new MessageOrderIndex(); 1905 BTreeIndex<Location, Long> locationIndex; 1906 BTreeIndex<String, Long> messageIdIndex; 1907 1908 // These bits are only set for Topics 1909 BTreeIndex<String, KahaSubscriptionCommand> subscriptions; 1910 BTreeIndex<String, LastAck> subscriptionAcks; 1911 HashMap<String, MessageOrderCursor> subscriptionCursors; 1912 ListIndex<String, SequenceSet> ackPositions; 1913 ListIndex<String, Location> subLocations; 1914 1915 // Transient data used to track which Messages are no longer needed. 1916 final TreeMap<Long, Long> messageReferences = new TreeMap<Long, Long>(); 1917 final HashSet<String> subscriptionCache = new LinkedHashSet<String>(); 1918 1919 public void trackPendingAdd(Long seq) { 1920 orderIndex.trackPendingAdd(seq); 1921 } 1922 1923 public void trackPendingAddComplete(Long seq) { 1924 orderIndex.trackPendingAddComplete(seq); 1925 } 1926 1927 @Override 1928 public String toString() { 1929 return "nextSeq:" + orderIndex.nextMessageId + ",lastRet:" + orderIndex.cursor + ",pending:" + orderIndex.pendingAdditions.size(); 1930 } 1931 } 1932 1933 protected class StoredDestinationMarshaller extends VariableMarshaller<StoredDestination> { 1934 1935 final MessageKeysMarshaller messageKeysMarshaller = new MessageKeysMarshaller(); 1936 1937 @Override 1938 public StoredDestination readPayload(final DataInput dataIn) throws IOException { 1939 final StoredDestination value = new StoredDestination(); 1940 value.orderIndex.defaultPriorityIndex = new BTreeIndex<Long, MessageKeys>(pageFile, dataIn.readLong()); 1941 value.locationIndex = new BTreeIndex<Location, Long>(pageFile, dataIn.readLong()); 1942 value.messageIdIndex = new BTreeIndex<String, Long>(pageFile, dataIn.readLong()); 1943 1944 if (dataIn.readBoolean()) { 1945 value.subscriptions = new BTreeIndex<String, KahaSubscriptionCommand>(pageFile, dataIn.readLong()); 1946 value.subscriptionAcks = new BTreeIndex<String, LastAck>(pageFile, dataIn.readLong()); 1947 if (metadata.version >= 4) { 1948 value.ackPositions = new ListIndex<String, SequenceSet>(pageFile, dataIn.readLong()); 1949 } else { 1950 // upgrade 1951 pageFile.tx().execute(new Transaction.Closure<IOException>() { 1952 @Override 1953 public void execute(Transaction tx) throws IOException { 1954 LinkedHashMap<String, SequenceSet> temp = new LinkedHashMap<String, SequenceSet>(); 1955 1956 if (metadata.version >= 3) { 1957 // migrate 1958 BTreeIndex<Long, HashSet<String>> oldAckPositions = 1959 new BTreeIndex<Long, HashSet<String>>(pageFile, dataIn.readLong()); 1960 oldAckPositions.setKeyMarshaller(LongMarshaller.INSTANCE); 1961 oldAckPositions.setValueMarshaller(HashSetStringMarshaller.INSTANCE); 1962 oldAckPositions.load(tx); 1963 1964 1965 // Do the initial build of the data in memory before writing into the store 1966 // based Ack Positions List to avoid a lot of disk thrashing. 1967 Iterator<Entry<Long, HashSet<String>>> iterator = oldAckPositions.iterator(tx); 1968 while (iterator.hasNext()) { 1969 Entry<Long, HashSet<String>> entry = iterator.next(); 1970 1971 for(String subKey : entry.getValue()) { 1972 SequenceSet pendingAcks = temp.get(subKey); 1973 if (pendingAcks == null) { 1974 pendingAcks = new SequenceSet(); 1975 temp.put(subKey, pendingAcks); 1976 } 1977 1978 pendingAcks.add(entry.getKey()); 1979 } 1980 } 1981 } 1982 // Now move the pending messages to ack data into the store backed 1983 // structure. 1984 value.ackPositions = new ListIndex<String, SequenceSet>(pageFile, tx.allocate()); 1985 value.ackPositions.setKeyMarshaller(StringMarshaller.INSTANCE); 1986 value.ackPositions.setValueMarshaller(SequenceSet.Marshaller.INSTANCE); 1987 value.ackPositions.load(tx); 1988 for(String subscriptionKey : temp.keySet()) { 1989 value.ackPositions.put(tx, subscriptionKey, temp.get(subscriptionKey)); 1990 } 1991 1992 } 1993 }); 1994 } 1995 1996 if (metadata.version >= 5) { 1997 value.subLocations = new ListIndex<String, Location>(pageFile, dataIn.readLong()); 1998 } else { 1999 // upgrade 2000 pageFile.tx().execute(new Transaction.Closure<IOException>() { 2001 @Override 2002 public void execute(Transaction tx) throws IOException { 2003 value.subLocations = new ListIndex<String, Location>(pageFile, tx.allocate()); 2004 value.subLocations.setKeyMarshaller(StringMarshaller.INSTANCE); 2005 value.subLocations.setValueMarshaller(LocationMarshaller.INSTANCE); 2006 value.subLocations.load(tx); 2007 } 2008 }); 2009 } 2010 } 2011 if (metadata.version >= 2) { 2012 value.orderIndex.lowPriorityIndex = new BTreeIndex<Long, MessageKeys>(pageFile, dataIn.readLong()); 2013 value.orderIndex.highPriorityIndex = new BTreeIndex<Long, MessageKeys>(pageFile, dataIn.readLong()); 2014 } else { 2015 // upgrade 2016 pageFile.tx().execute(new Transaction.Closure<IOException>() { 2017 @Override 2018 public void execute(Transaction tx) throws IOException { 2019 value.orderIndex.lowPriorityIndex = new BTreeIndex<Long, MessageKeys>(pageFile, tx.allocate()); 2020 value.orderIndex.lowPriorityIndex.setKeyMarshaller(LongMarshaller.INSTANCE); 2021 value.orderIndex.lowPriorityIndex.setValueMarshaller(messageKeysMarshaller); 2022 value.orderIndex.lowPriorityIndex.load(tx); 2023 2024 value.orderIndex.highPriorityIndex = new BTreeIndex<Long, MessageKeys>(pageFile, tx.allocate()); 2025 value.orderIndex.highPriorityIndex.setKeyMarshaller(LongMarshaller.INSTANCE); 2026 value.orderIndex.highPriorityIndex.setValueMarshaller(messageKeysMarshaller); 2027 value.orderIndex.highPriorityIndex.load(tx); 2028 } 2029 }); 2030 } 2031 2032 return value; 2033 } 2034 2035 @Override 2036 public void writePayload(StoredDestination value, DataOutput dataOut) throws IOException { 2037 dataOut.writeLong(value.orderIndex.defaultPriorityIndex.getPageId()); 2038 dataOut.writeLong(value.locationIndex.getPageId()); 2039 dataOut.writeLong(value.messageIdIndex.getPageId()); 2040 if (value.subscriptions != null) { 2041 dataOut.writeBoolean(true); 2042 dataOut.writeLong(value.subscriptions.getPageId()); 2043 dataOut.writeLong(value.subscriptionAcks.getPageId()); 2044 dataOut.writeLong(value.ackPositions.getHeadPageId()); 2045 dataOut.writeLong(value.subLocations.getHeadPageId()); 2046 } else { 2047 dataOut.writeBoolean(false); 2048 } 2049 dataOut.writeLong(value.orderIndex.lowPriorityIndex.getPageId()); 2050 dataOut.writeLong(value.orderIndex.highPriorityIndex.getPageId()); 2051 } 2052 } 2053 2054 static class KahaSubscriptionCommandMarshaller extends VariableMarshaller<KahaSubscriptionCommand> { 2055 final static KahaSubscriptionCommandMarshaller INSTANCE = new KahaSubscriptionCommandMarshaller(); 2056 2057 @Override 2058 public KahaSubscriptionCommand readPayload(DataInput dataIn) throws IOException { 2059 KahaSubscriptionCommand rc = new KahaSubscriptionCommand(); 2060 rc.mergeFramed((InputStream)dataIn); 2061 return rc; 2062 } 2063 2064 @Override 2065 public void writePayload(KahaSubscriptionCommand object, DataOutput dataOut) throws IOException { 2066 object.writeFramed((OutputStream)dataOut); 2067 } 2068 } 2069 2070 protected StoredDestination getStoredDestination(KahaDestination destination, Transaction tx) throws IOException { 2071 String key = key(destination); 2072 StoredDestination rc = storedDestinations.get(key); 2073 if (rc == null) { 2074 boolean topic = destination.getType() == KahaDestination.DestinationType.TOPIC || destination.getType() == KahaDestination.DestinationType.TEMP_TOPIC; 2075 rc = loadStoredDestination(tx, key, topic); 2076 // Cache it. We may want to remove/unload destinations from the 2077 // cache that are not used for a while 2078 // to reduce memory usage. 2079 storedDestinations.put(key, rc); 2080 } 2081 return rc; 2082 } 2083 2084 protected StoredDestination getExistingStoredDestination(KahaDestination destination, Transaction tx) throws IOException { 2085 String key = key(destination); 2086 StoredDestination rc = storedDestinations.get(key); 2087 if (rc == null && metadata.destinations.containsKey(tx, key)) { 2088 rc = getStoredDestination(destination, tx); 2089 } 2090 return rc; 2091 } 2092 2093 /** 2094 * @param tx 2095 * @param key 2096 * @param topic 2097 * @return 2098 * @throws IOException 2099 */ 2100 private StoredDestination loadStoredDestination(Transaction tx, String key, boolean topic) throws IOException { 2101 // Try to load the existing indexes.. 2102 StoredDestination rc = metadata.destinations.get(tx, key); 2103 if (rc == null) { 2104 // Brand new destination.. allocate indexes for it. 2105 rc = new StoredDestination(); 2106 rc.orderIndex.allocate(tx); 2107 rc.locationIndex = new BTreeIndex<Location, Long>(pageFile, tx.allocate()); 2108 rc.messageIdIndex = new BTreeIndex<String, Long>(pageFile, tx.allocate()); 2109 2110 if (topic) { 2111 rc.subscriptions = new BTreeIndex<String, KahaSubscriptionCommand>(pageFile, tx.allocate()); 2112 rc.subscriptionAcks = new BTreeIndex<String, LastAck>(pageFile, tx.allocate()); 2113 rc.ackPositions = new ListIndex<String, SequenceSet>(pageFile, tx.allocate()); 2114 rc.subLocations = new ListIndex<String, Location>(pageFile, tx.allocate()); 2115 } 2116 metadata.destinations.put(tx, key, rc); 2117 } 2118 2119 // Configure the marshalers and load. 2120 rc.orderIndex.load(tx); 2121 2122 // Figure out the next key using the last entry in the destination. 2123 rc.orderIndex.configureLast(tx); 2124 2125 rc.locationIndex.setKeyMarshaller(new LocationSizeMarshaller()); 2126 rc.locationIndex.setValueMarshaller(LongMarshaller.INSTANCE); 2127 rc.locationIndex.load(tx); 2128 2129 rc.messageIdIndex.setKeyMarshaller(StringMarshaller.INSTANCE); 2130 rc.messageIdIndex.setValueMarshaller(LongMarshaller.INSTANCE); 2131 rc.messageIdIndex.load(tx); 2132 2133 //go through an upgrade old index if older than version 6 2134 if (metadata.version < 6) { 2135 for (Iterator<Entry<Location, Long>> iterator = rc.locationIndex.iterator(tx); iterator.hasNext(); ) { 2136 Entry<Location, Long> entry = iterator.next(); 2137 // modify so it is upgraded 2138 rc.locationIndex.put(tx, entry.getKey(), entry.getValue()); 2139 } 2140 //upgrade the order index 2141 for (Iterator<Entry<Long, MessageKeys>> iterator = rc.orderIndex.iterator(tx); iterator.hasNext(); ) { 2142 Entry<Long, MessageKeys> entry = iterator.next(); 2143 //call get so that the last priority is updated 2144 rc.orderIndex.get(tx, entry.getKey()); 2145 rc.orderIndex.put(tx, rc.orderIndex.lastGetPriority(), entry.getKey(), entry.getValue()); 2146 } 2147 } 2148 2149 // If it was a topic... 2150 if (topic) { 2151 2152 rc.subscriptions.setKeyMarshaller(StringMarshaller.INSTANCE); 2153 rc.subscriptions.setValueMarshaller(KahaSubscriptionCommandMarshaller.INSTANCE); 2154 rc.subscriptions.load(tx); 2155 2156 rc.subscriptionAcks.setKeyMarshaller(StringMarshaller.INSTANCE); 2157 rc.subscriptionAcks.setValueMarshaller(new LastAckMarshaller()); 2158 rc.subscriptionAcks.load(tx); 2159 2160 rc.ackPositions.setKeyMarshaller(StringMarshaller.INSTANCE); 2161 rc.ackPositions.setValueMarshaller(SequenceSet.Marshaller.INSTANCE); 2162 rc.ackPositions.load(tx); 2163 2164 rc.subLocations.setKeyMarshaller(StringMarshaller.INSTANCE); 2165 rc.subLocations.setValueMarshaller(LocationMarshaller.INSTANCE); 2166 rc.subLocations.load(tx); 2167 2168 rc.subscriptionCursors = new HashMap<String, MessageOrderCursor>(); 2169 2170 if (metadata.version < 3) { 2171 2172 // on upgrade need to fill ackLocation with available messages past last ack 2173 for (Iterator<Entry<String, LastAck>> iterator = rc.subscriptionAcks.iterator(tx); iterator.hasNext(); ) { 2174 Entry<String, LastAck> entry = iterator.next(); 2175 for (Iterator<Entry<Long, MessageKeys>> orderIterator = 2176 rc.orderIndex.iterator(tx, new MessageOrderCursor(entry.getValue().lastAckedSequence)); orderIterator.hasNext(); ) { 2177 Long sequence = orderIterator.next().getKey(); 2178 addAckLocation(tx, rc, sequence, entry.getKey()); 2179 } 2180 // modify so it is upgraded 2181 rc.subscriptionAcks.put(tx, entry.getKey(), entry.getValue()); 2182 } 2183 } 2184 2185 // Configure the message references index 2186 Iterator<Entry<String, SequenceSet>> subscriptions = rc.ackPositions.iterator(tx); 2187 while (subscriptions.hasNext()) { 2188 Entry<String, SequenceSet> subscription = subscriptions.next(); 2189 SequenceSet pendingAcks = subscription.getValue(); 2190 if (pendingAcks != null && !pendingAcks.isEmpty()) { 2191 Long lastPendingAck = pendingAcks.getTail().getLast(); 2192 for (Long sequenceId : pendingAcks) { 2193 Long current = rc.messageReferences.get(sequenceId); 2194 if (current == null) { 2195 current = new Long(0); 2196 } 2197 2198 // We always add a trailing empty entry for the next position to start from 2199 // so we need to ensure we don't count that as a message reference on reload. 2200 if (!sequenceId.equals(lastPendingAck)) { 2201 current = current.longValue() + 1; 2202 } else { 2203 current = Long.valueOf(0L); 2204 } 2205 2206 rc.messageReferences.put(sequenceId, current); 2207 } 2208 } 2209 } 2210 2211 // Configure the subscription cache 2212 for (Iterator<Entry<String, LastAck>> iterator = rc.subscriptionAcks.iterator(tx); iterator.hasNext(); ) { 2213 Entry<String, LastAck> entry = iterator.next(); 2214 rc.subscriptionCache.add(entry.getKey()); 2215 } 2216 2217 if (rc.orderIndex.nextMessageId == 0) { 2218 // check for existing durable sub all acked out - pull next seq from acks as messages are gone 2219 if (!rc.subscriptionAcks.isEmpty(tx)) { 2220 for (Iterator<Entry<String, LastAck>> iterator = rc.subscriptionAcks.iterator(tx); iterator.hasNext();) { 2221 Entry<String, LastAck> entry = iterator.next(); 2222 rc.orderIndex.nextMessageId = 2223 Math.max(rc.orderIndex.nextMessageId, entry.getValue().lastAckedSequence +1); 2224 } 2225 } 2226 } else { 2227 // update based on ackPositions for unmatched, last entry is always the next 2228 if (!rc.messageReferences.isEmpty()) { 2229 Long nextMessageId = (Long) rc.messageReferences.keySet().toArray()[rc.messageReferences.size() - 1]; 2230 rc.orderIndex.nextMessageId = 2231 Math.max(rc.orderIndex.nextMessageId, nextMessageId); 2232 } 2233 } 2234 } 2235 2236 if (metadata.version < VERSION) { 2237 // store again after upgrade 2238 metadata.destinations.put(tx, key, rc); 2239 } 2240 return rc; 2241 } 2242 2243 /** 2244 * Clear the counter for the destination, if one exists. 2245 * 2246 * @param kahaDestination 2247 */ 2248 protected void clearStoreStats(KahaDestination kahaDestination) { 2249 MessageStoreStatistics storeStats = getStoreStats(key(kahaDestination)); 2250 if (storeStats != null) { 2251 storeStats.reset(); 2252 } 2253 } 2254 2255 /** 2256 * Update MessageStoreStatistics 2257 * 2258 * @param kahaDestination 2259 * @param size 2260 */ 2261 protected void incrementAndAddSizeToStoreStat(KahaDestination kahaDestination, long size) { 2262 incrementAndAddSizeToStoreStat(key(kahaDestination), size); 2263 } 2264 2265 protected void incrementAndAddSizeToStoreStat(String kahaDestKey, long size) { 2266 MessageStoreStatistics storeStats = getStoreStats(kahaDestKey); 2267 if (storeStats != null) { 2268 storeStats.getMessageCount().increment(); 2269 if (size > 0) { 2270 storeStats.getMessageSize().addSize(size); 2271 } 2272 } 2273 } 2274 2275 protected void decrementAndSubSizeToStoreStat(KahaDestination kahaDestination, long size) { 2276 decrementAndSubSizeToStoreStat(key(kahaDestination), size); 2277 } 2278 2279 protected void decrementAndSubSizeToStoreStat(String kahaDestKey, long size) { 2280 MessageStoreStatistics storeStats = getStoreStats(kahaDestKey); 2281 if (storeStats != null) { 2282 storeStats.getMessageCount().decrement(); 2283 if (size > 0) { 2284 storeStats.getMessageSize().addSize(-size); 2285 } 2286 } 2287 } 2288 2289 /** 2290 * This is a map to cache DestinationStatistics for a specific 2291 * KahaDestination key 2292 */ 2293 protected final ConcurrentMap<String, MessageStore> storeCache = 2294 new ConcurrentHashMap<String, MessageStore>(); 2295 2296 /** 2297 * Locate the storeMessageSize counter for this KahaDestination 2298 * @param kahaDestination 2299 * @return 2300 */ 2301 protected MessageStoreStatistics getStoreStats(String kahaDestKey) { 2302 MessageStoreStatistics storeStats = null; 2303 try { 2304 MessageStore messageStore = storeCache.get(kahaDestKey); 2305 if (messageStore != null) { 2306 storeStats = messageStore.getMessageStoreStatistics(); 2307 } 2308 } catch (Exception e1) { 2309 LOG.error("Getting size counter of destination failed", e1); 2310 } 2311 2312 return storeStats; 2313 } 2314 2315 /** 2316 * Determine whether this Destination matches the DestinationType 2317 * 2318 * @param destination 2319 * @param type 2320 * @return 2321 */ 2322 protected boolean matchType(Destination destination, 2323 KahaDestination.DestinationType type) { 2324 if (destination instanceof Topic 2325 && type.equals(KahaDestination.DestinationType.TOPIC)) { 2326 return true; 2327 } else if (destination instanceof Queue 2328 && type.equals(KahaDestination.DestinationType.QUEUE)) { 2329 return true; 2330 } 2331 return false; 2332 } 2333 2334 class LocationSizeMarshaller implements Marshaller<Location> { 2335 2336 public LocationSizeMarshaller() { 2337 2338 } 2339 2340 @Override 2341 public Location readPayload(DataInput dataIn) throws IOException { 2342 Location rc = new Location(); 2343 rc.setDataFileId(dataIn.readInt()); 2344 rc.setOffset(dataIn.readInt()); 2345 if (metadata.version >= 6) { 2346 rc.setSize(dataIn.readInt()); 2347 } 2348 return rc; 2349 } 2350 2351 @Override 2352 public void writePayload(Location object, DataOutput dataOut) 2353 throws IOException { 2354 dataOut.writeInt(object.getDataFileId()); 2355 dataOut.writeInt(object.getOffset()); 2356 dataOut.writeInt(object.getSize()); 2357 } 2358 2359 @Override 2360 public int getFixedSize() { 2361 return 12; 2362 } 2363 2364 @Override 2365 public Location deepCopy(Location source) { 2366 return new Location(source); 2367 } 2368 2369 @Override 2370 public boolean isDeepCopySupported() { 2371 return true; 2372 } 2373 } 2374 2375 private void addAckLocation(Transaction tx, StoredDestination sd, Long messageSequence, String subscriptionKey) throws IOException { 2376 SequenceSet sequences = sd.ackPositions.get(tx, subscriptionKey); 2377 if (sequences == null) { 2378 sequences = new SequenceSet(); 2379 sequences.add(messageSequence); 2380 sd.ackPositions.add(tx, subscriptionKey, sequences); 2381 } else { 2382 sequences.add(messageSequence); 2383 sd.ackPositions.put(tx, subscriptionKey, sequences); 2384 } 2385 2386 Long count = sd.messageReferences.get(messageSequence); 2387 if (count == null) { 2388 count = Long.valueOf(0L); 2389 } 2390 count = count.longValue() + 1; 2391 sd.messageReferences.put(messageSequence, count); 2392 } 2393 2394 // new sub is interested in potentially all existing messages 2395 private void addAckLocationForRetroactiveSub(Transaction tx, StoredDestination sd, String subscriptionKey) throws IOException { 2396 SequenceSet allOutstanding = new SequenceSet(); 2397 Iterator<Map.Entry<String, SequenceSet>> iterator = sd.ackPositions.iterator(tx); 2398 while (iterator.hasNext()) { 2399 SequenceSet set = iterator.next().getValue(); 2400 for (Long entry : set) { 2401 allOutstanding.add(entry); 2402 } 2403 } 2404 sd.ackPositions.put(tx, subscriptionKey, allOutstanding); 2405 2406 for (Long ackPosition : allOutstanding) { 2407 Long count = sd.messageReferences.get(ackPosition); 2408 2409 // There might not be a reference if the ackLocation was the last 2410 // one which is a placeholder for the next incoming message and 2411 // no value was added to the message references table. 2412 if (count != null) { 2413 count = count.longValue() + 1; 2414 sd.messageReferences.put(ackPosition, count); 2415 } 2416 } 2417 } 2418 2419 // on a new message add, all existing subs are interested in this message 2420 private void addAckLocationForNewMessage(Transaction tx, StoredDestination sd, Long messageSequence) throws IOException { 2421 for(String subscriptionKey : sd.subscriptionCache) { 2422 SequenceSet sequences = sd.ackPositions.get(tx, subscriptionKey); 2423 if (sequences == null) { 2424 sequences = new SequenceSet(); 2425 sequences.add(new Sequence(messageSequence, messageSequence + 1)); 2426 sd.ackPositions.add(tx, subscriptionKey, sequences); 2427 } else { 2428 sequences.add(new Sequence(messageSequence, messageSequence + 1)); 2429 sd.ackPositions.put(tx, subscriptionKey, sequences); 2430 } 2431 2432 Long count = sd.messageReferences.get(messageSequence); 2433 if (count == null) { 2434 count = Long.valueOf(0L); 2435 } 2436 count = count.longValue() + 1; 2437 sd.messageReferences.put(messageSequence, count); 2438 sd.messageReferences.put(messageSequence + 1, Long.valueOf(0L)); 2439 } 2440 } 2441 2442 private void removeAckLocationsForSub(KahaSubscriptionCommand command, 2443 Transaction tx, StoredDestination sd, String subscriptionKey) throws IOException { 2444 if (!sd.ackPositions.isEmpty(tx)) { 2445 SequenceSet sequences = sd.ackPositions.remove(tx, subscriptionKey); 2446 if (sequences == null || sequences.isEmpty()) { 2447 return; 2448 } 2449 2450 ArrayList<Long> unreferenced = new ArrayList<Long>(); 2451 2452 for(Long sequenceId : sequences) { 2453 Long references = sd.messageReferences.get(sequenceId); 2454 if (references != null) { 2455 references = references.longValue() - 1; 2456 2457 if (references.longValue() > 0) { 2458 sd.messageReferences.put(sequenceId, references); 2459 } else { 2460 sd.messageReferences.remove(sequenceId); 2461 unreferenced.add(sequenceId); 2462 } 2463 } 2464 } 2465 2466 for(Long sequenceId : unreferenced) { 2467 // Find all the entries that need to get deleted. 2468 ArrayList<Entry<Long, MessageKeys>> deletes = new ArrayList<Entry<Long, MessageKeys>>(); 2469 sd.orderIndex.getDeleteList(tx, deletes, sequenceId); 2470 2471 // Do the actual deletes. 2472 for (Entry<Long, MessageKeys> entry : deletes) { 2473 sd.locationIndex.remove(tx, entry.getValue().location); 2474 sd.messageIdIndex.remove(tx, entry.getValue().messageId); 2475 sd.orderIndex.remove(tx, entry.getKey()); 2476 decrementAndSubSizeToStoreStat(command.getDestination(), entry.getValue().location.getSize()); 2477 } 2478 } 2479 } 2480 } 2481 2482 /** 2483 * @param tx 2484 * @param sd 2485 * @param subscriptionKey 2486 * @param messageSequence 2487 * @throws IOException 2488 */ 2489 private void removeAckLocation(KahaRemoveMessageCommand command, 2490 Transaction tx, StoredDestination sd, String subscriptionKey, 2491 Long messageSequence) throws IOException { 2492 // Remove the sub from the previous location set.. 2493 if (messageSequence != null) { 2494 SequenceSet range = sd.ackPositions.get(tx, subscriptionKey); 2495 if (range != null && !range.isEmpty()) { 2496 range.remove(messageSequence); 2497 if (!range.isEmpty()) { 2498 sd.ackPositions.put(tx, subscriptionKey, range); 2499 } else { 2500 sd.ackPositions.remove(tx, subscriptionKey); 2501 } 2502 2503 // Check if the message is reference by any other subscription. 2504 Long count = sd.messageReferences.get(messageSequence); 2505 if (count != null) { 2506 long references = count.longValue() - 1; 2507 if (references > 0) { 2508 sd.messageReferences.put(messageSequence, Long.valueOf(references)); 2509 return; 2510 } else { 2511 sd.messageReferences.remove(messageSequence); 2512 } 2513 } 2514 2515 // Find all the entries that need to get deleted. 2516 ArrayList<Entry<Long, MessageKeys>> deletes = new ArrayList<Entry<Long, MessageKeys>>(); 2517 sd.orderIndex.getDeleteList(tx, deletes, messageSequence); 2518 2519 // Do the actual deletes. 2520 for (Entry<Long, MessageKeys> entry : deletes) { 2521 sd.locationIndex.remove(tx, entry.getValue().location); 2522 sd.messageIdIndex.remove(tx, entry.getValue().messageId); 2523 sd.orderIndex.remove(tx, entry.getKey()); 2524 decrementAndSubSizeToStoreStat(command.getDestination(), entry.getValue().location.getSize()); 2525 } 2526 } 2527 } 2528 } 2529 2530 public LastAck getLastAck(Transaction tx, StoredDestination sd, String subscriptionKey) throws IOException { 2531 return sd.subscriptionAcks.get(tx, subscriptionKey); 2532 } 2533 2534 public long getStoredMessageCount(Transaction tx, StoredDestination sd, String subscriptionKey) throws IOException { 2535 SequenceSet messageSequences = sd.ackPositions.get(tx, subscriptionKey); 2536 if (messageSequences != null) { 2537 long result = messageSequences.rangeSize(); 2538 // if there's anything in the range the last value is always the nextMessage marker, so remove 1. 2539 return result > 0 ? result - 1 : 0; 2540 } 2541 2542 return 0; 2543 } 2544 2545 protected String key(KahaDestination destination) { 2546 return destination.getType().getNumber() + ":" + destination.getName(); 2547 } 2548 2549 // ///////////////////////////////////////////////////////////////// 2550 // Transaction related implementation methods. 2551 // ///////////////////////////////////////////////////////////////// 2552 @SuppressWarnings("rawtypes") 2553 private final LinkedHashMap<TransactionId, List<Operation>> inflightTransactions = new LinkedHashMap<TransactionId, List<Operation>>(); 2554 @SuppressWarnings("rawtypes") 2555 protected final LinkedHashMap<TransactionId, List<Operation>> preparedTransactions = new LinkedHashMap<TransactionId, List<Operation>>(); 2556 protected final Set<String> ackedAndPrepared = new HashSet<String>(); 2557 protected final Set<String> rolledBackAcks = new HashSet<String>(); 2558 2559 // messages that have prepared (pending) acks cannot be re-dispatched unless the outcome is rollback, 2560 // till then they are skipped by the store. 2561 // 'at most once' XA guarantee 2562 public void trackRecoveredAcks(ArrayList<MessageAck> acks) { 2563 this.indexLock.writeLock().lock(); 2564 try { 2565 for (MessageAck ack : acks) { 2566 ackedAndPrepared.add(ack.getLastMessageId().toProducerKey()); 2567 } 2568 } finally { 2569 this.indexLock.writeLock().unlock(); 2570 } 2571 } 2572 2573 public void forgetRecoveredAcks(ArrayList<MessageAck> acks, boolean rollback) throws IOException { 2574 if (acks != null) { 2575 this.indexLock.writeLock().lock(); 2576 try { 2577 for (MessageAck ack : acks) { 2578 final String id = ack.getLastMessageId().toProducerKey(); 2579 ackedAndPrepared.remove(id); 2580 if (rollback) { 2581 rolledBackAcks.add(id); 2582 } 2583 } 2584 } finally { 2585 this.indexLock.writeLock().unlock(); 2586 } 2587 } 2588 } 2589 2590 @SuppressWarnings("rawtypes") 2591 private List<Operation> getInflightTx(KahaTransactionInfo info) { 2592 TransactionId key = TransactionIdConversion.convert(info); 2593 List<Operation> tx; 2594 synchronized (inflightTransactions) { 2595 tx = inflightTransactions.get(key); 2596 if (tx == null) { 2597 tx = Collections.synchronizedList(new ArrayList<Operation>()); 2598 inflightTransactions.put(key, tx); 2599 } 2600 } 2601 return tx; 2602 } 2603 2604 @SuppressWarnings("unused") 2605 private TransactionId key(KahaTransactionInfo transactionInfo) { 2606 return TransactionIdConversion.convert(transactionInfo); 2607 } 2608 2609 abstract class Operation <T extends JournalCommand<T>> { 2610 final T command; 2611 final Location location; 2612 2613 public Operation(T command, Location location) { 2614 this.command = command; 2615 this.location = location; 2616 } 2617 2618 public Location getLocation() { 2619 return location; 2620 } 2621 2622 public T getCommand() { 2623 return command; 2624 } 2625 2626 abstract public void execute(Transaction tx) throws IOException; 2627 } 2628 2629 class AddOperation extends Operation<KahaAddMessageCommand> { 2630 final IndexAware runWithIndexLock; 2631 public AddOperation(KahaAddMessageCommand command, Location location, IndexAware runWithIndexLock) { 2632 super(command, location); 2633 this.runWithIndexLock = runWithIndexLock; 2634 } 2635 2636 @Override 2637 public void execute(Transaction tx) throws IOException { 2638 long seq = updateIndex(tx, command, location); 2639 if (runWithIndexLock != null) { 2640 runWithIndexLock.sequenceAssignedWithIndexLocked(seq); 2641 } 2642 } 2643 2644 } 2645 2646 class RemoveOperation extends Operation<KahaRemoveMessageCommand> { 2647 2648 public RemoveOperation(KahaRemoveMessageCommand command, Location location) { 2649 super(command, location); 2650 } 2651 2652 @Override 2653 public void execute(Transaction tx) throws IOException { 2654 updateIndex(tx, command, location); 2655 } 2656 } 2657 2658 // ///////////////////////////////////////////////////////////////// 2659 // Initialization related implementation methods. 2660 // ///////////////////////////////////////////////////////////////// 2661 2662 private PageFile createPageFile() throws IOException { 2663 if( indexDirectory == null ) { 2664 indexDirectory = directory; 2665 } 2666 IOHelper.mkdirs(indexDirectory); 2667 PageFile index = new PageFile(indexDirectory, "db"); 2668 index.setEnableWriteThread(isEnableIndexWriteAsync()); 2669 index.setWriteBatchSize(getIndexWriteBatchSize()); 2670 index.setPageCacheSize(indexCacheSize); 2671 index.setUseLFRUEviction(isUseIndexLFRUEviction()); 2672 index.setLFUEvictionFactor(getIndexLFUEvictionFactor()); 2673 index.setEnableDiskSyncs(isEnableIndexDiskSyncs()); 2674 index.setEnableRecoveryFile(isEnableIndexRecoveryFile()); 2675 index.setEnablePageCaching(isEnableIndexPageCaching()); 2676 return index; 2677 } 2678 2679 private Journal createJournal() throws IOException { 2680 Journal manager = new Journal(); 2681 manager.setDirectory(directory); 2682 manager.setMaxFileLength(getJournalMaxFileLength()); 2683 manager.setCheckForCorruptionOnStartup(checkForCorruptJournalFiles); 2684 manager.setChecksum(checksumJournalFiles || checkForCorruptJournalFiles); 2685 manager.setWriteBatchSize(getJournalMaxWriteBatchSize()); 2686 manager.setArchiveDataLogs(isArchiveDataLogs()); 2687 manager.setSizeAccumulator(journalSize); 2688 manager.setEnableAsyncDiskSync(isEnableJournalDiskSyncs()); 2689 manager.setPreallocationScope(Journal.PreallocationScope.valueOf(preallocationScope.trim().toUpperCase())); 2690 manager.setPreallocationStrategy( 2691 Journal.PreallocationStrategy.valueOf(preallocationStrategy.trim().toUpperCase())); 2692 if (getDirectoryArchive() != null) { 2693 IOHelper.mkdirs(getDirectoryArchive()); 2694 manager.setDirectoryArchive(getDirectoryArchive()); 2695 } 2696 return manager; 2697 } 2698 2699 private Metadata createMetadata() { 2700 Metadata md = new Metadata(); 2701 md.producerSequenceIdTracker.setAuditDepth(getFailoverProducersAuditDepth()); 2702 md.producerSequenceIdTracker.setMaximumNumberOfProducersToTrack(getMaxFailoverProducersToTrack()); 2703 return md; 2704 } 2705 2706 public int getJournalMaxWriteBatchSize() { 2707 return journalMaxWriteBatchSize; 2708 } 2709 2710 public void setJournalMaxWriteBatchSize(int journalMaxWriteBatchSize) { 2711 this.journalMaxWriteBatchSize = journalMaxWriteBatchSize; 2712 } 2713 2714 public File getDirectory() { 2715 return directory; 2716 } 2717 2718 public void setDirectory(File directory) { 2719 this.directory = directory; 2720 } 2721 2722 public boolean isDeleteAllMessages() { 2723 return deleteAllMessages; 2724 } 2725 2726 public void setDeleteAllMessages(boolean deleteAllMessages) { 2727 this.deleteAllMessages = deleteAllMessages; 2728 } 2729 2730 public void setIndexWriteBatchSize(int setIndexWriteBatchSize) { 2731 this.setIndexWriteBatchSize = setIndexWriteBatchSize; 2732 } 2733 2734 public int getIndexWriteBatchSize() { 2735 return setIndexWriteBatchSize; 2736 } 2737 2738 public void setEnableIndexWriteAsync(boolean enableIndexWriteAsync) { 2739 this.enableIndexWriteAsync = enableIndexWriteAsync; 2740 } 2741 2742 boolean isEnableIndexWriteAsync() { 2743 return enableIndexWriteAsync; 2744 } 2745 2746 public boolean isEnableJournalDiskSyncs() { 2747 return enableJournalDiskSyncs; 2748 } 2749 2750 public void setEnableJournalDiskSyncs(boolean syncWrites) { 2751 this.enableJournalDiskSyncs = syncWrites; 2752 } 2753 2754 public long getCheckpointInterval() { 2755 return checkpointInterval; 2756 } 2757 2758 public void setCheckpointInterval(long checkpointInterval) { 2759 this.checkpointInterval = checkpointInterval; 2760 } 2761 2762 public long getCleanupInterval() { 2763 return cleanupInterval; 2764 } 2765 2766 public void setCleanupInterval(long cleanupInterval) { 2767 this.cleanupInterval = cleanupInterval; 2768 } 2769 2770 public void setJournalMaxFileLength(int journalMaxFileLength) { 2771 this.journalMaxFileLength = journalMaxFileLength; 2772 } 2773 2774 public int getJournalMaxFileLength() { 2775 return journalMaxFileLength; 2776 } 2777 2778 public void setMaxFailoverProducersToTrack(int maxFailoverProducersToTrack) { 2779 this.metadata.producerSequenceIdTracker.setMaximumNumberOfProducersToTrack(maxFailoverProducersToTrack); 2780 } 2781 2782 public int getMaxFailoverProducersToTrack() { 2783 return this.metadata.producerSequenceIdTracker.getMaximumNumberOfProducersToTrack(); 2784 } 2785 2786 public void setFailoverProducersAuditDepth(int failoverProducersAuditDepth) { 2787 this.metadata.producerSequenceIdTracker.setAuditDepth(failoverProducersAuditDepth); 2788 } 2789 2790 public int getFailoverProducersAuditDepth() { 2791 return this.metadata.producerSequenceIdTracker.getAuditDepth(); 2792 } 2793 2794 public PageFile getPageFile() throws IOException { 2795 if (pageFile == null) { 2796 pageFile = createPageFile(); 2797 } 2798 return pageFile; 2799 } 2800 2801 public Journal getJournal() throws IOException { 2802 if (journal == null) { 2803 journal = createJournal(); 2804 } 2805 return journal; 2806 } 2807 2808 public boolean isFailIfDatabaseIsLocked() { 2809 return failIfDatabaseIsLocked; 2810 } 2811 2812 public void setFailIfDatabaseIsLocked(boolean failIfDatabaseIsLocked) { 2813 this.failIfDatabaseIsLocked = failIfDatabaseIsLocked; 2814 } 2815 2816 public boolean isIgnoreMissingJournalfiles() { 2817 return ignoreMissingJournalfiles; 2818 } 2819 2820 public void setIgnoreMissingJournalfiles(boolean ignoreMissingJournalfiles) { 2821 this.ignoreMissingJournalfiles = ignoreMissingJournalfiles; 2822 } 2823 2824 public int getIndexCacheSize() { 2825 return indexCacheSize; 2826 } 2827 2828 public void setIndexCacheSize(int indexCacheSize) { 2829 this.indexCacheSize = indexCacheSize; 2830 } 2831 2832 public boolean isCheckForCorruptJournalFiles() { 2833 return checkForCorruptJournalFiles; 2834 } 2835 2836 public void setCheckForCorruptJournalFiles(boolean checkForCorruptJournalFiles) { 2837 this.checkForCorruptJournalFiles = checkForCorruptJournalFiles; 2838 } 2839 2840 public boolean isChecksumJournalFiles() { 2841 return checksumJournalFiles; 2842 } 2843 2844 public void setChecksumJournalFiles(boolean checksumJournalFiles) { 2845 this.checksumJournalFiles = checksumJournalFiles; 2846 } 2847 2848 @Override 2849 public void setBrokerService(BrokerService brokerService) { 2850 this.brokerService = brokerService; 2851 } 2852 2853 /** 2854 * @return the archiveDataLogs 2855 */ 2856 public boolean isArchiveDataLogs() { 2857 return this.archiveDataLogs; 2858 } 2859 2860 /** 2861 * @param archiveDataLogs the archiveDataLogs to set 2862 */ 2863 public void setArchiveDataLogs(boolean archiveDataLogs) { 2864 this.archiveDataLogs = archiveDataLogs; 2865 } 2866 2867 /** 2868 * @return the directoryArchive 2869 */ 2870 public File getDirectoryArchive() { 2871 return this.directoryArchive; 2872 } 2873 2874 /** 2875 * @param directoryArchive the directoryArchive to set 2876 */ 2877 public void setDirectoryArchive(File directoryArchive) { 2878 this.directoryArchive = directoryArchive; 2879 } 2880 2881 public boolean isArchiveCorruptedIndex() { 2882 return archiveCorruptedIndex; 2883 } 2884 2885 public void setArchiveCorruptedIndex(boolean archiveCorruptedIndex) { 2886 this.archiveCorruptedIndex = archiveCorruptedIndex; 2887 } 2888 2889 public float getIndexLFUEvictionFactor() { 2890 return indexLFUEvictionFactor; 2891 } 2892 2893 public void setIndexLFUEvictionFactor(float indexLFUEvictionFactor) { 2894 this.indexLFUEvictionFactor = indexLFUEvictionFactor; 2895 } 2896 2897 public boolean isUseIndexLFRUEviction() { 2898 return useIndexLFRUEviction; 2899 } 2900 2901 public void setUseIndexLFRUEviction(boolean useIndexLFRUEviction) { 2902 this.useIndexLFRUEviction = useIndexLFRUEviction; 2903 } 2904 2905 public void setEnableIndexDiskSyncs(boolean enableIndexDiskSyncs) { 2906 this.enableIndexDiskSyncs = enableIndexDiskSyncs; 2907 } 2908 2909 public void setEnableIndexRecoveryFile(boolean enableIndexRecoveryFile) { 2910 this.enableIndexRecoveryFile = enableIndexRecoveryFile; 2911 } 2912 2913 public void setEnableIndexPageCaching(boolean enableIndexPageCaching) { 2914 this.enableIndexPageCaching = enableIndexPageCaching; 2915 } 2916 2917 public boolean isEnableIndexDiskSyncs() { 2918 return enableIndexDiskSyncs; 2919 } 2920 2921 public boolean isEnableIndexRecoveryFile() { 2922 return enableIndexRecoveryFile; 2923 } 2924 2925 public boolean isEnableIndexPageCaching() { 2926 return enableIndexPageCaching; 2927 } 2928 2929 // ///////////////////////////////////////////////////////////////// 2930 // Internal conversion methods. 2931 // ///////////////////////////////////////////////////////////////// 2932 2933 class MessageOrderCursor{ 2934 long defaultCursorPosition; 2935 long lowPriorityCursorPosition; 2936 long highPriorityCursorPosition; 2937 MessageOrderCursor(){ 2938 } 2939 2940 MessageOrderCursor(long position){ 2941 this.defaultCursorPosition=position; 2942 this.lowPriorityCursorPosition=position; 2943 this.highPriorityCursorPosition=position; 2944 } 2945 2946 MessageOrderCursor(MessageOrderCursor other){ 2947 this.defaultCursorPosition=other.defaultCursorPosition; 2948 this.lowPriorityCursorPosition=other.lowPriorityCursorPosition; 2949 this.highPriorityCursorPosition=other.highPriorityCursorPosition; 2950 } 2951 2952 MessageOrderCursor copy() { 2953 return new MessageOrderCursor(this); 2954 } 2955 2956 void reset() { 2957 this.defaultCursorPosition=0; 2958 this.highPriorityCursorPosition=0; 2959 this.lowPriorityCursorPosition=0; 2960 } 2961 2962 void increment() { 2963 if (defaultCursorPosition!=0) { 2964 defaultCursorPosition++; 2965 } 2966 if (highPriorityCursorPosition!=0) { 2967 highPriorityCursorPosition++; 2968 } 2969 if (lowPriorityCursorPosition!=0) { 2970 lowPriorityCursorPosition++; 2971 } 2972 } 2973 2974 @Override 2975 public String toString() { 2976 return "MessageOrderCursor:[def:" + defaultCursorPosition 2977 + ", low:" + lowPriorityCursorPosition 2978 + ", high:" + highPriorityCursorPosition + "]"; 2979 } 2980 2981 public void sync(MessageOrderCursor other) { 2982 this.defaultCursorPosition=other.defaultCursorPosition; 2983 this.lowPriorityCursorPosition=other.lowPriorityCursorPosition; 2984 this.highPriorityCursorPosition=other.highPriorityCursorPosition; 2985 } 2986 } 2987 2988 class MessageOrderIndex { 2989 static final byte HI = 9; 2990 static final byte LO = 0; 2991 static final byte DEF = 4; 2992 2993 long nextMessageId; 2994 BTreeIndex<Long, MessageKeys> defaultPriorityIndex; 2995 BTreeIndex<Long, MessageKeys> lowPriorityIndex; 2996 BTreeIndex<Long, MessageKeys> highPriorityIndex; 2997 final MessageOrderCursor cursor = new MessageOrderCursor(); 2998 Long lastDefaultKey; 2999 Long lastHighKey; 3000 Long lastLowKey; 3001 byte lastGetPriority; 3002 final List<Long> pendingAdditions = new LinkedList<Long>(); 3003 final MessageKeysMarshaller messageKeysMarshaller = new MessageKeysMarshaller(); 3004 3005 MessageKeys remove(Transaction tx, Long key) throws IOException { 3006 MessageKeys result = defaultPriorityIndex.remove(tx, key); 3007 if (result == null && highPriorityIndex!=null) { 3008 result = highPriorityIndex.remove(tx, key); 3009 if (result ==null && lowPriorityIndex!=null) { 3010 result = lowPriorityIndex.remove(tx, key); 3011 } 3012 } 3013 return result; 3014 } 3015 3016 void load(Transaction tx) throws IOException { 3017 defaultPriorityIndex.setKeyMarshaller(LongMarshaller.INSTANCE); 3018 defaultPriorityIndex.setValueMarshaller(messageKeysMarshaller); 3019 defaultPriorityIndex.load(tx); 3020 lowPriorityIndex.setKeyMarshaller(LongMarshaller.INSTANCE); 3021 lowPriorityIndex.setValueMarshaller(messageKeysMarshaller); 3022 lowPriorityIndex.load(tx); 3023 highPriorityIndex.setKeyMarshaller(LongMarshaller.INSTANCE); 3024 highPriorityIndex.setValueMarshaller(messageKeysMarshaller); 3025 highPriorityIndex.load(tx); 3026 } 3027 3028 void allocate(Transaction tx) throws IOException { 3029 defaultPriorityIndex = new BTreeIndex<Long, MessageKeys>(pageFile, tx.allocate()); 3030 if (metadata.version >= 2) { 3031 lowPriorityIndex = new BTreeIndex<Long, MessageKeys>(pageFile, tx.allocate()); 3032 highPriorityIndex = new BTreeIndex<Long, MessageKeys>(pageFile, tx.allocate()); 3033 } 3034 } 3035 3036 void configureLast(Transaction tx) throws IOException { 3037 // Figure out the next key using the last entry in the destination. 3038 TreeSet<Long> orderedSet = new TreeSet<Long>(); 3039 3040 addLast(orderedSet, highPriorityIndex, tx); 3041 addLast(orderedSet, defaultPriorityIndex, tx); 3042 addLast(orderedSet, lowPriorityIndex, tx); 3043 3044 if (!orderedSet.isEmpty()) { 3045 nextMessageId = orderedSet.last() + 1; 3046 } 3047 } 3048 3049 private void addLast(TreeSet<Long> orderedSet, BTreeIndex<Long, MessageKeys> index, Transaction tx) throws IOException { 3050 if (index != null) { 3051 Entry<Long, MessageKeys> lastEntry = index.getLast(tx); 3052 if (lastEntry != null) { 3053 orderedSet.add(lastEntry.getKey()); 3054 } 3055 } 3056 } 3057 3058 void clear(Transaction tx) throws IOException { 3059 this.remove(tx); 3060 this.resetCursorPosition(); 3061 this.allocate(tx); 3062 this.load(tx); 3063 this.configureLast(tx); 3064 } 3065 3066 void remove(Transaction tx) throws IOException { 3067 defaultPriorityIndex.clear(tx); 3068 defaultPriorityIndex.unload(tx); 3069 tx.free(defaultPriorityIndex.getPageId()); 3070 if (lowPriorityIndex != null) { 3071 lowPriorityIndex.clear(tx); 3072 lowPriorityIndex.unload(tx); 3073 3074 tx.free(lowPriorityIndex.getPageId()); 3075 } 3076 if (highPriorityIndex != null) { 3077 highPriorityIndex.clear(tx); 3078 highPriorityIndex.unload(tx); 3079 tx.free(highPriorityIndex.getPageId()); 3080 } 3081 } 3082 3083 void resetCursorPosition() { 3084 this.cursor.reset(); 3085 lastDefaultKey = null; 3086 lastHighKey = null; 3087 lastLowKey = null; 3088 } 3089 3090 void setBatch(Transaction tx, Long sequence) throws IOException { 3091 if (sequence != null) { 3092 Long nextPosition = new Long(sequence.longValue() + 1); 3093 lastDefaultKey = sequence; 3094 cursor.defaultCursorPosition = nextPosition.longValue(); 3095 lastHighKey = sequence; 3096 cursor.highPriorityCursorPosition = nextPosition.longValue(); 3097 lastLowKey = sequence; 3098 cursor.lowPriorityCursorPosition = nextPosition.longValue(); 3099 } 3100 } 3101 3102 void setBatch(Transaction tx, LastAck last) throws IOException { 3103 setBatch(tx, last.lastAckedSequence); 3104 if (cursor.defaultCursorPosition == 0 3105 && cursor.highPriorityCursorPosition == 0 3106 && cursor.lowPriorityCursorPosition == 0) { 3107 long next = last.lastAckedSequence + 1; 3108 switch (last.priority) { 3109 case DEF: 3110 cursor.defaultCursorPosition = next; 3111 cursor.highPriorityCursorPosition = next; 3112 break; 3113 case HI: 3114 cursor.highPriorityCursorPosition = next; 3115 break; 3116 case LO: 3117 cursor.lowPriorityCursorPosition = next; 3118 cursor.defaultCursorPosition = next; 3119 cursor.highPriorityCursorPosition = next; 3120 break; 3121 } 3122 } 3123 } 3124 3125 void stoppedIterating() { 3126 if (lastDefaultKey!=null) { 3127 cursor.defaultCursorPosition=lastDefaultKey.longValue()+1; 3128 } 3129 if (lastHighKey!=null) { 3130 cursor.highPriorityCursorPosition=lastHighKey.longValue()+1; 3131 } 3132 if (lastLowKey!=null) { 3133 cursor.lowPriorityCursorPosition=lastLowKey.longValue()+1; 3134 } 3135 lastDefaultKey = null; 3136 lastHighKey = null; 3137 lastLowKey = null; 3138 } 3139 3140 void getDeleteList(Transaction tx, ArrayList<Entry<Long, MessageKeys>> deletes, Long sequenceId) 3141 throws IOException { 3142 if (defaultPriorityIndex.containsKey(tx, sequenceId)) { 3143 getDeleteList(tx, deletes, defaultPriorityIndex, sequenceId); 3144 } else if (highPriorityIndex != null && highPriorityIndex.containsKey(tx, sequenceId)) { 3145 getDeleteList(tx, deletes, highPriorityIndex, sequenceId); 3146 } else if (lowPriorityIndex != null && lowPriorityIndex.containsKey(tx, sequenceId)) { 3147 getDeleteList(tx, deletes, lowPriorityIndex, sequenceId); 3148 } 3149 } 3150 3151 void getDeleteList(Transaction tx, ArrayList<Entry<Long, MessageKeys>> deletes, 3152 BTreeIndex<Long, MessageKeys> index, Long sequenceId) throws IOException { 3153 3154 Iterator<Entry<Long, MessageKeys>> iterator = index.iterator(tx, sequenceId, null); 3155 deletes.add(iterator.next()); 3156 } 3157 3158 long getNextMessageId(int priority) { 3159 return nextMessageId++; 3160 } 3161 3162 MessageKeys get(Transaction tx, Long key) throws IOException { 3163 MessageKeys result = defaultPriorityIndex.get(tx, key); 3164 if (result == null) { 3165 result = highPriorityIndex.get(tx, key); 3166 if (result == null) { 3167 result = lowPriorityIndex.get(tx, key); 3168 lastGetPriority = LO; 3169 } else { 3170 lastGetPriority = HI; 3171 } 3172 } else { 3173 lastGetPriority = DEF; 3174 } 3175 return result; 3176 } 3177 3178 MessageKeys put(Transaction tx, int priority, Long key, MessageKeys value) throws IOException { 3179 if (priority == javax.jms.Message.DEFAULT_PRIORITY) { 3180 return defaultPriorityIndex.put(tx, key, value); 3181 } else if (priority > javax.jms.Message.DEFAULT_PRIORITY) { 3182 return highPriorityIndex.put(tx, key, value); 3183 } else { 3184 return lowPriorityIndex.put(tx, key, value); 3185 } 3186 } 3187 3188 Iterator<Entry<Long, MessageKeys>> iterator(Transaction tx) throws IOException{ 3189 return new MessageOrderIterator(tx,cursor,this); 3190 } 3191 3192 Iterator<Entry<Long, MessageKeys>> iterator(Transaction tx, MessageOrderCursor m) throws IOException{ 3193 return new MessageOrderIterator(tx,m,this); 3194 } 3195 3196 public byte lastGetPriority() { 3197 return lastGetPriority; 3198 } 3199 3200 public boolean alreadyDispatched(Long sequence) { 3201 return (cursor.highPriorityCursorPosition > 0 && cursor.highPriorityCursorPosition >= sequence) || 3202 (cursor.defaultCursorPosition > 0 && cursor.defaultCursorPosition >= sequence) || 3203 (cursor.lowPriorityCursorPosition > 0 && cursor.lowPriorityCursorPosition >= sequence); 3204 } 3205 3206 public void trackPendingAdd(Long seq) { 3207 synchronized (pendingAdditions) { 3208 pendingAdditions.add(seq); 3209 } 3210 } 3211 3212 public void trackPendingAddComplete(Long seq) { 3213 synchronized (pendingAdditions) { 3214 pendingAdditions.remove(seq); 3215 } 3216 } 3217 3218 public Long minPendingAdd() { 3219 synchronized (pendingAdditions) { 3220 if (!pendingAdditions.isEmpty()) { 3221 return pendingAdditions.get(0); 3222 } else { 3223 return null; 3224 } 3225 } 3226 } 3227 3228 class MessageOrderIterator implements Iterator<Entry<Long, MessageKeys>>{ 3229 Iterator<Entry<Long, MessageKeys>>currentIterator; 3230 final Iterator<Entry<Long, MessageKeys>>highIterator; 3231 final Iterator<Entry<Long, MessageKeys>>defaultIterator; 3232 final Iterator<Entry<Long, MessageKeys>>lowIterator; 3233 3234 MessageOrderIterator(Transaction tx, MessageOrderCursor m, MessageOrderIndex messageOrderIndex) throws IOException { 3235 Long pendingAddLimiter = messageOrderIndex.minPendingAdd(); 3236 this.defaultIterator = defaultPriorityIndex.iterator(tx, m.defaultCursorPosition, pendingAddLimiter); 3237 if (highPriorityIndex != null) { 3238 this.highIterator = highPriorityIndex.iterator(tx, m.highPriorityCursorPosition, pendingAddLimiter); 3239 } else { 3240 this.highIterator = null; 3241 } 3242 if (lowPriorityIndex != null) { 3243 this.lowIterator = lowPriorityIndex.iterator(tx, m.lowPriorityCursorPosition, pendingAddLimiter); 3244 } else { 3245 this.lowIterator = null; 3246 } 3247 } 3248 3249 @Override 3250 public boolean hasNext() { 3251 if (currentIterator == null) { 3252 if (highIterator != null) { 3253 if (highIterator.hasNext()) { 3254 currentIterator = highIterator; 3255 return currentIterator.hasNext(); 3256 } 3257 if (defaultIterator.hasNext()) { 3258 currentIterator = defaultIterator; 3259 return currentIterator.hasNext(); 3260 } 3261 if (lowIterator.hasNext()) { 3262 currentIterator = lowIterator; 3263 return currentIterator.hasNext(); 3264 } 3265 return false; 3266 } else { 3267 currentIterator = defaultIterator; 3268 return currentIterator.hasNext(); 3269 } 3270 } 3271 if (highIterator != null) { 3272 if (currentIterator.hasNext()) { 3273 return true; 3274 } 3275 if (currentIterator == highIterator) { 3276 if (defaultIterator.hasNext()) { 3277 currentIterator = defaultIterator; 3278 return currentIterator.hasNext(); 3279 } 3280 if (lowIterator.hasNext()) { 3281 currentIterator = lowIterator; 3282 return currentIterator.hasNext(); 3283 } 3284 return false; 3285 } 3286 3287 if (currentIterator == defaultIterator) { 3288 if (lowIterator.hasNext()) { 3289 currentIterator = lowIterator; 3290 return currentIterator.hasNext(); 3291 } 3292 return false; 3293 } 3294 } 3295 return currentIterator.hasNext(); 3296 } 3297 3298 @Override 3299 public Entry<Long, MessageKeys> next() { 3300 Entry<Long, MessageKeys> result = currentIterator.next(); 3301 if (result != null) { 3302 Long key = result.getKey(); 3303 if (highIterator != null) { 3304 if (currentIterator == defaultIterator) { 3305 lastDefaultKey = key; 3306 } else if (currentIterator == highIterator) { 3307 lastHighKey = key; 3308 } else { 3309 lastLowKey = key; 3310 } 3311 } else { 3312 lastDefaultKey = key; 3313 } 3314 } 3315 return result; 3316 } 3317 3318 @Override 3319 public void remove() { 3320 throw new UnsupportedOperationException(); 3321 } 3322 } 3323 } 3324 3325 private static class HashSetStringMarshaller extends VariableMarshaller<HashSet<String>> { 3326 final static HashSetStringMarshaller INSTANCE = new HashSetStringMarshaller(); 3327 3328 @Override 3329 public void writePayload(HashSet<String> object, DataOutput dataOut) throws IOException { 3330 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 3331 ObjectOutputStream oout = new ObjectOutputStream(baos); 3332 oout.writeObject(object); 3333 oout.flush(); 3334 oout.close(); 3335 byte[] data = baos.toByteArray(); 3336 dataOut.writeInt(data.length); 3337 dataOut.write(data); 3338 } 3339 3340 @Override 3341 @SuppressWarnings("unchecked") 3342 public HashSet<String> readPayload(DataInput dataIn) throws IOException { 3343 int dataLen = dataIn.readInt(); 3344 byte[] data = new byte[dataLen]; 3345 dataIn.readFully(data); 3346 ByteArrayInputStream bais = new ByteArrayInputStream(data); 3347 ObjectInputStream oin = new ObjectInputStream(bais); 3348 try { 3349 return (HashSet<String>) oin.readObject(); 3350 } catch (ClassNotFoundException cfe) { 3351 IOException ioe = new IOException("Failed to read HashSet<String>: " + cfe); 3352 ioe.initCause(cfe); 3353 throw ioe; 3354 } 3355 } 3356 } 3357 3358 public File getIndexDirectory() { 3359 return indexDirectory; 3360 } 3361 3362 public void setIndexDirectory(File indexDirectory) { 3363 this.indexDirectory = indexDirectory; 3364 } 3365 3366 interface IndexAware { 3367 public void sequenceAssignedWithIndexLocked(long index); 3368 } 3369 3370 public String getPreallocationScope() { 3371 return preallocationScope; 3372 } 3373 3374 public void setPreallocationScope(String preallocationScope) { 3375 this.preallocationScope = preallocationScope; 3376 } 3377 3378 public String getPreallocationStrategy() { 3379 return preallocationStrategy; 3380 } 3381 3382 public void setPreallocationStrategy(String preallocationStrategy) { 3383 this.preallocationStrategy = preallocationStrategy; 3384 } 3385}