1 /*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002, 2014 Oracle and/or its affiliates. All rights reserved. 5 * 6 */ 7 8 package com.sleepycat.je.txn; 9 10 import static com.sleepycat.je.txn.LockStatDefinition.LOCK_READ_LOCKS; 11 import static com.sleepycat.je.txn.LockStatDefinition.LOCK_TOTAL; 12 import static com.sleepycat.je.txn.LockStatDefinition.LOCK_WRITE_LOCKS; 13 import static com.sleepycat.je.utilint.DbLsn.NULL_LSN; 14 15 import java.nio.ByteBuffer; 16 import java.util.Collection; 17 import java.util.Collections; 18 import java.util.HashMap; 19 import java.util.HashSet; 20 import java.util.Iterator; 21 import java.util.Map; 22 import java.util.Set; 23 import java.util.TreeSet; 24 import java.util.concurrent.atomic.AtomicInteger; 25 import java.util.logging.Level; 26 import java.util.logging.Logger; 27 28 import javax.transaction.xa.XAResource; 29 import javax.transaction.xa.Xid; 30 31 import com.sleepycat.je.CommitToken; 32 import com.sleepycat.je.Database; 33 import com.sleepycat.je.DatabaseException; 34 import com.sleepycat.je.DbInternal; 35 import com.sleepycat.je.Durability; 36 import com.sleepycat.je.Durability.SyncPolicy; 37 import com.sleepycat.je.EnvironmentFailureException; 38 import com.sleepycat.je.OperationFailureException; 39 import com.sleepycat.je.ThreadInterruptedException; 40 import com.sleepycat.je.Transaction; 41 import com.sleepycat.je.TransactionConfig; 42 import com.sleepycat.je.dbi.CursorImpl; 43 import com.sleepycat.je.dbi.DatabaseId; 44 import com.sleepycat.je.dbi.DatabaseImpl; 45 import com.sleepycat.je.dbi.EnvironmentFailureReason; 46 import com.sleepycat.je.dbi.EnvironmentImpl; 47 import com.sleepycat.je.dbi.MemoryBudget; 48 import com.sleepycat.je.dbi.TriggerManager; 49 import com.sleepycat.je.log.BasicVersionedWriteLoggable; 50 import com.sleepycat.je.log.FileManager; 51 import com.sleepycat.je.log.LogContext; 52 import com.sleepycat.je.log.LogEntryType; 53 import com.sleepycat.je.log.LogItem; 54 import com.sleepycat.je.log.LogManager; 55 import com.sleepycat.je.log.LogUtils; 56 import com.sleepycat.je.log.Loggable; 57 import com.sleepycat.je.log.Provisional; 58 import com.sleepycat.je.log.ReplicationContext; 59 import com.sleepycat.je.log.VersionedWriteLoggable; 60 import com.sleepycat.je.log.entry.AbortLogEntry; 61 import com.sleepycat.je.log.entry.CommitLogEntry; 62 import com.sleepycat.je.log.entry.LNLogEntry; 63 import com.sleepycat.je.log.entry.SingleItemEntry; 64 import com.sleepycat.je.recovery.RecoveryManager; 65 import com.sleepycat.je.tree.TreeLocation; 66 import com.sleepycat.je.txn.TxnChain.CompareSlot; 67 import com.sleepycat.je.utilint.DbLsn; 68 import com.sleepycat.je.utilint.IntStat; 69 import com.sleepycat.je.utilint.LoggerUtils; 70 import com.sleepycat.je.utilint.StatGroup; 71 import com.sleepycat.je.utilint.TinyHashSet; 72 73 /** 74 * A Txn is the internal representation of a transaction created by a call to 75 * Environment.txnBegin. This class must support multi-threaded use. 76 */ 77 public class Txn extends Locker implements VersionedWriteLoggable { 78 79 /** 80 * The log version of the most recent format change for this loggable. 81 * 82 * @see #getLastFormatChange 83 */ 84 public static final int LAST_FORMAT_CHANGE = 8; 85 86 /* Use an AtomicInteger to record cursors opened under this txn. */ 87 private final AtomicInteger cursors = new AtomicInteger(); 88 89 /* Internal txn flags. */ 90 private byte txnFlags; 91 /* Set if prepare() has been called on this transaction. */ 92 private static final byte IS_PREPARED = 1; 93 /* Set if xa_end(TMSUSPEND) has been called on this transaction. */ 94 private static final byte XA_SUSPENDED = 2; 95 /* Set if this rollback() has been called on this transaction. */ 96 private static final byte PAST_ROLLBACK = 4; 97 98 /* 99 * Set if this transaction may abort other transactions holding a needed 100 * lock. Note that this bit flag and the setImportunate method could be 101 * removed in favor of overriding getImportunate in ReplayTxn. This was 102 * not done, for now, to avoid changing importunate tests that use a Txn 103 * and call setImportunate. [#16513] 104 */ 105 private static final byte IMPORTUNATE = 8; 106 107 /* Holds the public Transaction state. */ 108 private Transaction.State txnState; 109 110 /* Information about why a Txn was made only abortable. */ 111 private OperationFailureException onlyAbortableCause; 112 113 /* 114 * A Txn can be used by multiple threads. Modification to the read and 115 * write lock collections is done by synchronizing on the txn. 116 */ 117 private Set<Long> readLocks; // key is LSN 118 private Map<Long, WriteLockInfo> writeInfo; // key is LSN 119 120 /* 121 * A set of BuddyLockers that have this locker as their buddy. Currently 122 * this set is only maintained (non-null) in a replicated environment 123 * because it is only needed for determining when to throw 124 * LockPreemptedException. If null, it can be assumed that no other 125 * thread will change it. If non-null, access should be synchronized on 126 * the buddyLockers object. TinyHashSet is used because it is optimized 127 * for 0 to 2 entries, and normally a Txn will have at most two buddy 128 * lockers (for read-committed mode). 129 */ 130 private TinyHashSet<BuddyLocker> buddyLockers; 131 132 private static final int READ_LOCK_OVERHEAD = 133 MemoryBudget.HASHSET_ENTRY_OVERHEAD; 134 private static final int WRITE_LOCK_OVERHEAD = 135 MemoryBudget.HASHMAP_ENTRY_OVERHEAD + 136 MemoryBudget.WRITE_LOCKINFO_OVERHEAD; 137 138 /* 139 * We have to keep a set of DatabaseCleanupInfo objects so after commit or 140 * abort of Environment.truncateDatabase() or Environment.removeDatabase(), 141 * we can appropriately purge the unneeded MapLN and DatabaseImpl. 142 * Synchronize access to this set on this object. 143 */ 144 protected Set<DatabaseCleanupInfo> deletedDatabases; 145 146 /* 147 * We need a map of the latest databaseImpl objects to drive the undo 148 * during an abort, because it's too hard to look up the database object in 149 * the mapping tree. (The normal code paths want to take locks, add 150 * cursors, etc. 151 */ 152 protected Map<DatabaseId, DatabaseImpl> undoDatabases; 153 154 /** 155 * @see #addOpenedDatabase 156 * @see HandleLocker 157 */ 158 protected Set<Database> openedDatabaseHandles; 159 160 /* 161 * First LSN logged for this transaction -- used for keeping track of the 162 * first active LSN point, for checkpointing. This field is not persistent. 163 * 164 * [#16861] This field is volatile to avoid making getFirstActiveLsn 165 * synchronized, which causes a deadlock in HA. 166 */ 167 protected volatile long firstLoggedLsn = NULL_LSN; 168 169 /* 170 * Last LSN logged for this transaction. Serves as the handle onto the 171 * chained log entries belonging to this transaction. Is persistent. 172 */ 173 protected long lastLoggedLsn = NULL_LSN; 174 175 /* 176 * The LSN used to commit the transaction. One of commitLSN or abortLSN 177 * must be set after a commit() or abort() operation. Note that a commit() 178 * may set abortLSN, if the commit failed, and the transaction had to be 179 * aborted. 180 */ 181 protected long commitLsn = NULL_LSN; 182 183 /* The LSN used to record the abort of the transaction. */ 184 long abortLsn = NULL_LSN; 185 186 /* The configured durability at the time the transaction was created. */ 187 private Durability defaultDurability; 188 189 /* The durability used for the actual commit. */ 190 private Durability commitDurability; 191 192 /* Whether to use Serializable isolation (prevent phantoms). */ 193 private boolean serializableIsolation; 194 195 /* Whether to use Read-Committed isolation. */ 196 private boolean readCommittedIsolation; 197 198 /* 199 * In-memory size, in bytes. A Txn tracks the memory needed for itself and 200 * the readlock, writeInfo, undoDatabases, and deletedDatabases 201 * collections, including the cost of each collection entry. However, the 202 * actual Lock object memory cost is maintained within the Lock class. 203 */ 204 private int inMemorySize; 205 206 /* 207 * Accumulated memory budget delta. Once this exceeds ACCUMULATED_LIMIT we 208 * inform the MemoryBudget that a change has occurred. 209 */ 210 private int accumulatedDelta = 0; 211 212 /* 213 * The set of databases for which triggers were invoked during the 214 * course of this transaction. It's null if no triggers were invoked. 215 */ 216 private Set<DatabaseImpl> triggerDbs = null; 217 218 /* 219 * The user Transaction handle associated with this Txn. It's null if there 220 * isn't one, e.g. it's an internal transaction. 221 */ 222 private Transaction transaction; 223 224 /* 225 * Max allowable accumulation of memory budget changes before MemoryBudget 226 * should be updated. This allows for consolidating multiple calls to 227 * updateXXXMemoryBudget() into one call. Not declared final so that unit 228 * tests can modify this. See SR 12273. 229 */ 230 public static int ACCUMULATED_LIMIT = 10000; 231 232 /* 233 * Each Txn instance has a handle on a ReplicationContext instance for use 234 * in logging a TxnCommit or TxnAbort log entries. 235 */ 236 protected ReplicationContext repContext; 237 238 /* 239 * Used to track mixed mode (sync/durability) transaction API usage. When 240 * the sync based api is removed, these tracking ivs can be as well. 241 */ 242 private boolean explicitSyncConfigured = false; 243 private boolean explicitDurabilityConfigured = false; 244 245 /* Determines whether the transaction is auto-commit */ 246 private boolean isAutoCommit = false; 247 248 private boolean readOnly; 249 250 /** 251 * Constructor for reading from log. 252 */ Txn()253 public Txn() { 254 lastLoggedLsn = NULL_LSN; 255 } 256 Txn(EnvironmentImpl envImpl, TransactionConfig config, ReplicationContext repContext)257 protected Txn(EnvironmentImpl envImpl, 258 TransactionConfig config, 259 ReplicationContext repContext) { 260 this(envImpl, config, repContext, 0L /*mandatedId */ ); 261 } 262 263 /** 264 * A non-zero mandatedId is specified only by subtypes which arbitrarily 265 * impose a transaction id value onto the transaction. This is done by 266 * implementing a version of Locker.generateId() which uses the proposed 267 * id. 268 */ Txn(EnvironmentImpl envImpl, TransactionConfig config, ReplicationContext repContext, long mandatedId)269 protected Txn(EnvironmentImpl envImpl, 270 TransactionConfig config, 271 ReplicationContext repContext, 272 long mandatedId) 273 throws DatabaseException { 274 275 /* 276 * Initialize using the config but don't hold a reference to it, since 277 * it has not been cloned. 278 */ 279 super(envImpl, config.getReadUncommitted(), config.getNoWait(), 280 mandatedId); 281 initTxn(config); 282 this.repContext = repContext; 283 } 284 createLocalTxn(EnvironmentImpl envImpl, TransactionConfig config)285 public static Txn createLocalTxn(EnvironmentImpl envImpl, 286 TransactionConfig config) { 287 return new Txn(envImpl, config, ReplicationContext.NO_REPLICATE); 288 } 289 createLocalAutoTxn(EnvironmentImpl envImpl, TransactionConfig config)290 public static Txn createLocalAutoTxn(EnvironmentImpl envImpl, 291 TransactionConfig config) { 292 Txn txn = createLocalTxn(envImpl, config); 293 txn.isAutoCommit = true; 294 return txn; 295 } 296 297 /* 298 * Make a transaction for a user instigated transaction. Whether the 299 * environment is replicated or not determines whether a MasterTxn or 300 * a plain local Txn is returned. 301 */ createUserTxn(EnvironmentImpl envImpl, TransactionConfig config)302 static Txn createUserTxn(EnvironmentImpl envImpl, 303 TransactionConfig config) { 304 305 Txn ret = null; 306 try { 307 ret = envImpl.isReplicated() ? 308 envImpl.createRepUserTxn(config) : 309 createLocalTxn(envImpl, config); 310 } catch (DatabaseException DE) { 311 if (ret != null) { 312 ret.close(false); 313 } 314 throw DE; 315 } 316 return ret; 317 } 318 createAutoTxn(EnvironmentImpl envImpl, TransactionConfig config, ReplicationContext repContext)319 static Txn createAutoTxn(EnvironmentImpl envImpl, 320 TransactionConfig config, 321 ReplicationContext repContext) 322 throws DatabaseException { 323 324 Txn ret = null; 325 try { 326 if (envImpl.isReplicated() && repContext.inReplicationStream()) { 327 ret = envImpl.createRepUserTxn(config); 328 } else { 329 ret = new Txn(envImpl, config, repContext); 330 } 331 332 ret.isAutoCommit = true; 333 } catch (DatabaseException DE) { 334 if (ret != null) { 335 ret.close(false); 336 } 337 throw DE; 338 } 339 return ret; 340 } 341 342 @SuppressWarnings("deprecation") initTxn(TransactionConfig config)343 private void initTxn(TransactionConfig config) 344 throws DatabaseException { 345 346 serializableIsolation = config.getSerializableIsolation(); 347 readCommittedIsolation = config.getReadCommitted(); 348 defaultDurability = config.getDurability(); 349 if (defaultDurability == null) { 350 explicitDurabilityConfigured = false; 351 defaultDurability = config.getDurabilityFromSync(envImpl); 352 } else { 353 explicitDurabilityConfigured = true; 354 } 355 explicitSyncConfigured = 356 config.getSync() || config.getNoSync() || config.getWriteNoSync(); 357 358 assert (!(explicitDurabilityConfigured && explicitSyncConfigured)); 359 360 readOnly = config.getReadOnly(); 361 362 lastLoggedLsn = NULL_LSN; 363 firstLoggedLsn = NULL_LSN; 364 365 txnFlags = 0; 366 setState(Transaction.State.OPEN); 367 368 if (envImpl.isReplicated()) { 369 buddyLockers = new TinyHashSet<BuddyLocker>(); 370 } 371 372 txnBeginHook(config); 373 374 /* 375 * Note: readLocks, writeInfo, undoDatabases, deleteDatabases are 376 * initialized lazily in order to conserve memory. WriteInfo and 377 * undoDatabases are treated as a package deal, because they are both 378 * only needed if a transaction does writes. 379 * 380 * When a lock is added to this transaction, we add the collection 381 * entry overhead to the memory cost, but don't add the lock 382 * itself. That's taken care of by the Lock class. 383 */ 384 updateMemoryUsage(MemoryBudget.TXN_OVERHEAD); 385 386 if (registerImmediately()) { 387 this.envImpl.getTxnManager().registerTxn(this); 388 } 389 } 390 391 /** 392 * True if this transaction should be registered with the transaction 393 * manager immediately at startup. True for all transactions except for 394 * those ReplayTxns which were created as transformed master transactions. 395 */ registerImmediately()396 protected boolean registerImmediately() { 397 return true; 398 } 399 400 @Override addBuddy(BuddyLocker buddy)401 void addBuddy(BuddyLocker buddy) { 402 if (buddyLockers != null) { 403 synchronized (buddyLockers) { 404 buddyLockers.add(buddy); 405 } 406 } 407 } 408 409 @Override removeBuddy(BuddyLocker buddy)410 void removeBuddy(BuddyLocker buddy) { 411 if (buddyLockers != null) { 412 synchronized (buddyLockers) { 413 buddyLockers.remove(buddy); 414 } 415 } 416 } 417 418 /** 419 * UserTxns get a new unique id for each instance. 420 */ 421 @Override 422 @SuppressWarnings("unused") generateId(TxnManager txnManager, long ignore )423 protected long generateId(TxnManager txnManager, 424 long ignore /* mandatedId */) { 425 return txnManager.getNextTxnId(); 426 } 427 428 /** 429 * Access to last LSN. 430 */ getLastLsn()431 public long getLastLsn() { 432 return lastLoggedLsn; 433 } 434 435 /** 436 * 437 * Returns the durability used for the commit operation. It's only 438 * available after a commit operation has been initiated. 439 * 440 * @return the durability associated with the commit, or null if the 441 * commit has not yet been initiated. 442 */ getCommitDurability()443 public Durability getCommitDurability() { 444 return commitDurability; 445 } 446 447 /** 448 * Returns the durability associated the transaction at the time it's first 449 * created. 450 * 451 * @return the durability associated with the transaction at creation. 452 */ getDefaultDurability()453 public Durability getDefaultDurability() { 454 return defaultDurability; 455 } 456 getPrepared()457 public boolean getPrepared() { 458 return (txnFlags & IS_PREPARED) != 0; 459 } 460 setPrepared(boolean prepared)461 public void setPrepared(boolean prepared) { 462 if (prepared) { 463 txnFlags |= IS_PREPARED; 464 } else { 465 txnFlags &= ~IS_PREPARED; 466 } 467 } 468 setSuspended(boolean suspended)469 public void setSuspended(boolean suspended) { 470 if (suspended) { 471 txnFlags |= XA_SUSPENDED; 472 } else { 473 txnFlags &= ~XA_SUSPENDED; 474 } 475 } 476 isSuspended()477 public boolean isSuspended() { 478 return (txnFlags & XA_SUSPENDED) != 0; 479 } 480 setRollback()481 protected void setRollback() { 482 txnFlags |= PAST_ROLLBACK; 483 } 484 485 /** 486 * @return if this transaction has ever executed a rollback. 487 * A Rollback is an undo of the transaction that can return either to the 488 * original pre-txn state, or to an intermediate intra-txn state. An abort 489 * always returns the txn to the pre-txn state. 490 */ 491 @Override isRolledBack()492 public boolean isRolledBack() { 493 return (txnFlags & PAST_ROLLBACK) != 0; 494 } 495 496 /** 497 * Gets a lock on this LSN and, if it is a write lock, saves an abort 498 * LSN. Caller will set the abortLsn later, after the write lock has been 499 * obtained. 500 * 501 * @throws IllegalStateException via API read/write methods if the txn is 502 * closed, in theory. However, this should not occur from a user API call, 503 * because the API methods first call Transaction.getLocker, which will 504 * throw IllegalStateException if the txn is closed. It might occur, 505 * however, if the transaction ends in the window between the call to 506 * getLocker and the lock attempt. 507 * 508 * @throws OperationFailureException via API read/write methods if an 509 * OperationFailureException occurred earlier and set the txn to 510 * abort-only. 511 * 512 * @see Locker#lockInternal 513 * @Override 514 */ 515 @Override lockInternal(long lsn, LockType lockType, boolean noWait, boolean jumpAheadOfWaiters, DatabaseImpl database)516 protected LockResult lockInternal(long lsn, 517 LockType lockType, 518 boolean noWait, 519 boolean jumpAheadOfWaiters, 520 DatabaseImpl database) 521 throws DatabaseException { 522 523 long timeout = 0; 524 boolean useNoWait = noWait || defaultNoWait; 525 synchronized (this) { 526 checkState(false); 527 if (!useNoWait) { 528 timeout = getLockTimeout(); 529 } 530 } 531 532 /* Ask for the lock. */ 533 LockGrantType grant = lockManager.lock 534 (lsn, this, lockType, timeout, useNoWait, jumpAheadOfWaiters, 535 database); 536 537 WriteLockInfo info = null; 538 if (writeInfo != null) { 539 if (grant != LockGrantType.DENIED && lockType.isWriteLock()) { 540 synchronized (this) { 541 info = writeInfo.get(Long.valueOf(lsn)); 542 /* Save the latest version of this database for undoing. */ 543 undoDatabases.put(database.getId(), database); 544 } 545 } 546 } 547 548 return new LockResult(grant, info); 549 } 550 551 /** 552 * Prepare to undo in the (very unlikely) event that logging succeeds but 553 * locking fails. Subclasses should call super.preLogWithoutLock. [#22875] 554 */ 555 @Override preLogWithoutLock(DatabaseImpl database)556 public synchronized void preLogWithoutLock(DatabaseImpl database) { 557 ensureWriteInfo(); 558 undoDatabases.put(database.getId(), database); 559 } 560 561 /** 562 * @throws IllegalStateException via XAResource 563 */ prepare(Xid xid)564 public synchronized int prepare(Xid xid) 565 throws DatabaseException { 566 567 if ((txnFlags & IS_PREPARED) != 0) { 568 throw new IllegalStateException 569 ("prepare() has already been called for Transaction " + 570 id + "."); 571 } 572 573 checkState(false); 574 if (checkCursorsForClose()) { 575 throw new IllegalStateException 576 ("Transaction " + id + 577 " prepare failed because there were open cursors."); 578 } 579 580 setPrepared(true); 581 envImpl.getTxnManager().notePrepare(); 582 if (writeInfo == null) { 583 return XAResource.XA_RDONLY; 584 } 585 586 SingleItemEntry<TxnPrepare> prepareEntry = 587 SingleItemEntry.create(LogEntryType.LOG_TXN_PREPARE, 588 new TxnPrepare(id,xid)); 589 /* Flush required. */ 590 LogManager logManager = envImpl.getLogManager(); 591 logManager.logForceFlush(prepareEntry, 592 true, // fsyncrequired 593 ReplicationContext.NO_REPLICATE); 594 595 return XAResource.XA_OK; 596 } 597 commit(Xid xid)598 public void commit(Xid xid) 599 throws DatabaseException { 600 601 commit(Durability.COMMIT_SYNC); 602 envImpl.getTxnManager().unRegisterXATxn(xid, true); 603 return; 604 } 605 abort(Xid xid)606 public void abort(Xid xid) 607 throws DatabaseException { 608 609 abort(true /* forceFlush */); 610 envImpl.getTxnManager().unRegisterXATxn(xid, false); 611 return; 612 } 613 614 /** 615 * Call commit() with the default sync configuration property. 616 */ commit()617 public long commit() 618 throws DatabaseException { 619 620 return commit(defaultDurability); 621 } 622 623 /** 624 * Commit this transaction; it involves the following logical steps: 625 * 626 * 1. Run pre-commit hook. 627 * 628 * 2. Release read locks. 629 * 630 * 3. Log a txn commit record and flush the log as indicated by the 631 * durability policy. 632 * 633 * 4. Run the post-commit hook. 634 * 635 * 5. Add deleted LN info to IN compressor queue. 636 * 637 * 6. Release all write locks 638 * 639 * If this transaction has not made any changes to the database, that is, 640 * it is a read-only transaction, no entry is made to the log. Otherwise, 641 * a concerted effort is made to log a commit entry, or an abort entry, 642 * but NOT both. If exceptions are encountered and neither entry can be 643 * logged, a EnvironmentFailureException is thrown. 644 * 645 * Error conditions (in contrast to Exceptions) always result in the 646 * environment being invalidated and the Error being propagated back to the 647 * application. In addition, if the environment is made invalid in another 648 * thread, or the transaction is closed by another thread, then we 649 * propagate the exception and we do not attempt to abort. This special 650 * handling is prior to the pre-commit stage. 651 * 652 * From an exception handling viewpoint the commit goes through two stages: 653 * a pre-commit stage spanning steps 1-3, and a post-commit stage 654 * spanning steps 4-5. The post-commit stage is entered only after a commit 655 * entry has been successfully logged. 656 * 657 * Any exceptions detected during the pre-commit stage results in an 658 * attempt to log an abort entry. A NULL commitLsn (and abortLsn) 659 * indicates that we are in the pre-commit stage. Note in particular, that 660 * if the log of the commit entry (step 3) fails due to an IOException, 661 * then the lower levels are responsible for wrapping it in a 662 * EnvironmentFailureException which is propagated directly to the 663 * application. 664 * 665 * Exceptions thrown in the post-commit stage are examined to see if they 666 * are expected and must be propagated back to the caller after completing 667 * any pending cleanup; some replication exceptions fall into this 668 * category. If the exception was unexpected, the environment is 669 * invalidated and a EnvironmentFailureException is thrown instead. The 670 * current implementation only allows propagation of exceptions from the 671 * post-commit hook, since we do not expect exceptions from any of the 672 * other post-commit operations. 673 * 674 * When there are multiple failures in commit(), we want the caller to 675 * receive the first exception, to make the problem manifest. So an effort 676 * is made to preserve that primary exception and propagate it instead of 677 * any following, secondary exceptions. The secondary exception is always 678 * logged in such a circumstance. 679 * 680 * @throws IllegalStateException via Transaction.commit if cursors are 681 * open. 682 * 683 * @throws OperationFailureException via Transaction.commit if an 684 * OperationFailureException occurred earlier and set the txn to 685 * abort-only. 686 * 687 * Note that IllegalStateException should never be thrown by 688 * Transaction.commit because of a closed txn, since Transaction.commit and 689 * abort set the Transaction.txn to null and disallow subsequent method 690 * calls (other than abort). So in a sense the call to checkState(true) in 691 * this method is unnecessary, although perhaps a good safeguard. 692 */ commit(Durability durability)693 public long commit(Durability durability) 694 throws DatabaseException { 695 696 /* 697 * If frozen, throw the appropriate exception, but don't attempt to 698 * make any changes to cleanup the exception. 699 */ 700 checkIfFrozen(true /* isCommit */); 701 702 /* 703 * A post commit exception that needs to be propagated back to the 704 * caller. Its throw is delayed until the post commit cleanup has been 705 * completed. 706 */ 707 DatabaseException queuedPostCommitException = null; 708 709 this.commitDurability = durability; 710 711 try { 712 713 synchronized (this) { 714 checkState(false); 715 if (checkCursorsForClose()) { 716 throw new IllegalStateException 717 ("Transaction " + id + 718 " commit failed because there were open cursors."); 719 } 720 721 /* 722 * Do the pre-commit hook before executing any commit related 723 * actions like releasing locks. 724 */ 725 if (updateLoggedForTxn()) { 726 preLogCommitHook(); 727 } 728 729 /* 730 * Release all read locks, clear lock collection. Optimize for 731 * the case where there are no read locks. 732 */ 733 int numReadLocks = clearReadLocks(); 734 735 /* 736 * Log the commit if we ever logged any modifications for this 737 * txn. Refraining from logging empty commits is more efficient 738 * and makes for fewer edge cases for HA. Note that this is not 739 * the same as the question of whether we have held any write 740 * locks. Various scenarios, like RMW txns and 741 * Cursor.putNoOverwrite can take write locks without having 742 * actually made any modifications. 743 * 744 * If we have outstanding write locks, we must release them 745 * even if we won't log a commit. TODO: This may have been 746 * true in the past because of dbhandle write locks that were 747 * transferred away, but is probably no longer true. 748 */ 749 int numWriteLocks = 0; 750 Collection<WriteLockInfo> obsoleteLsns = null; 751 if (writeInfo != null) { 752 numWriteLocks = writeInfo.size(); 753 obsoleteLsns = getObsoleteLsnInfo(); 754 } 755 756 /* 757 * If nothing was written to log for this txn, no need to log a 758 * commit. 759 */ 760 if (updateLoggedForTxn()) { 761 final LogItem commitItem = 762 logCommitEntry(durability.getLocalSync(), 763 obsoleteLsns); 764 commitLsn = commitItem.getNewLsn(); 765 766 try { 767 postLogCommitHook(commitItem); 768 } catch (DatabaseException hookException) { 769 if (txnState == Transaction.State.MUST_ABORT) { 770 throw EnvironmentFailureException. 771 unexpectedException 772 ("postLogCommitHook may not set MUST_ABORT", 773 hookException); 774 } 775 if (!propagatePostCommitException(hookException)) { 776 throw hookException; 777 } 778 queuedPostCommitException = hookException; 779 } 780 } 781 782 /* 783 * Set database state for deletes before releasing any write 784 * locks. 785 */ 786 setDeletedDatabaseState(true); 787 788 /* Release all write locks, clear lock collection. */ 789 if (numWriteLocks > 0) { 790 releaseWriteLocks(); 791 } 792 writeInfo = null; 793 794 /* Unload delete info, but don't wake up the compressor. */ 795 if ((deleteInfo != null) && deleteInfo.size() > 0) { 796 envImpl.addToCompressorQueue(deleteInfo.values(), 797 false); // don't wakeup 798 deleteInfo.clear(); 799 } 800 traceCommit(numWriteLocks, numReadLocks); 801 } 802 803 /* 804 * Purge any databaseImpls not needed as a result of the commit. Be 805 * sure to do this outside the synchronization block, to avoid 806 * conflict w/ checkpointer. 807 */ 808 cleanupDatabaseImpls(true); 809 810 /* 811 * Unregister this txn. Be sure to do this outside the 812 * synchronization block, to avoid conflict w/ checkpointer. 813 */ 814 close(true); 815 816 if (queuedPostCommitException == null) { 817 TriggerManager.runCommitTriggers(this); 818 return commitLsn; 819 } 820 } catch (Error e) { 821 envImpl.invalidate(e); 822 throw e; 823 } catch (RuntimeException commitException) { 824 if (!envImpl.isValid()) { 825 /* Env is invalid, propagate exception. */ 826 throw commitException; 827 } 828 if (commitLsn != NULL_LSN) { 829 /* An unfiltered post commit exception */ 830 throw new EnvironmentFailureException 831 (envImpl, 832 EnvironmentFailureReason.LOG_INCOMPLETE, 833 "Failed after commiting transaction " + 834 id + 835 " during post transaction cleanup." + 836 "Original exception = " + 837 commitException.getMessage(), 838 commitException); 839 } 840 841 /* 842 * If this transaction is frozen, just bail out, and don't try 843 * to clean up with an abort. 844 */ 845 checkIfFrozen(true); 846 throwPreCommitException(durability, commitException); 847 } finally { 848 849 /* 850 * Final catch-all to ensure state is set, in case close(boolean) 851 * is not called. 852 */ 853 if (txnState == Transaction.State.OPEN) { 854 setState(Transaction.State.COMMITTED); 855 } 856 } 857 throw queuedPostCommitException; 858 } 859 860 /** 861 * Releases all write locks, nulls the lock collection. 862 */ releaseWriteLocks()863 protected void releaseWriteLocks() throws DatabaseException { 864 if (writeInfo == null) { 865 return; 866 } 867 for (Long lsn : writeInfo.keySet()) { 868 lockManager.release(lsn, this); 869 } 870 writeInfo = null; 871 } 872 873 /** 874 * Aborts the current transaction and throws the pre-commit Exception, 875 * wrapped in a Database exception if it isn't already a DatabaseException. 876 * 877 * If the attempt at writing the abort entry fails, that is, if neither an 878 * abort entry, nor a commit entry was successfully written to the log, the 879 * environment is invalidated and a EnvironmentFailureException is thrown. 880 * Note that for HA, it's necessary that either a commit or abort entry be 881 * made in the log, so that it can be replayed to the replicas and the 882 * transaction is not left in limbo at the other nodes. 883 * 884 * @param durability used to determine whether the abort record should be 885 * flushed to the log. 886 * @param preCommitException the exception being handled. 887 * @throws DatabaseException this is the normal return for the method. 888 */ throwPreCommitException(Durability durability, RuntimeException preCommitException)889 private void throwPreCommitException(Durability durability, 890 RuntimeException preCommitException) { 891 892 try { 893 abortInternal(durability.getLocalSync() == SyncPolicy.SYNC); 894 LoggerUtils.traceAndLogException(envImpl, "Txn", "commit", 895 "Commit of transaction " + id + 896 " failed", preCommitException); 897 } catch (Error e) { 898 envImpl.invalidate(e); 899 throw e; 900 } catch (RuntimeException abortT2) { 901 if (!envImpl.isValid()) { 902 /* Env already invalid, propagate exception. */ 903 throw abortT2; 904 } 905 String message = "Failed while attempting to commit transaction " + 906 id + ". The attempt to abort also failed. " + 907 "The original exception seen from commit = " + 908 preCommitException.getMessage() + 909 " The exception from the cleanup = " + 910 abortT2.getMessage(); 911 if ((writeInfo != null) && (abortLsn == NULL_LSN)) { 912 /* Failed to log an abort or commit entry */ 913 throw new EnvironmentFailureException 914 (envImpl, 915 EnvironmentFailureReason.LOG_INCOMPLETE, 916 message, preCommitException); 917 } 918 919 /* 920 * An abort entry has been written, so we can proceed. Log the 921 * secondary exception, but throw the more meaningful original 922 * exception. 923 */ 924 LoggerUtils.envLogMsg(Level.WARNING, envImpl, message); 925 /* The preCommitException exception will be thrown below. */ 926 } 927 postLogCommitAbortHook(); 928 929 /* 930 * Abort entry was written, wrap the exception if necessary and throw 931 * it. An IllegalStateException is thrown by commit() when cursors are 932 * open. 933 */ 934 if (preCommitException instanceof DatabaseException || 935 preCommitException instanceof IllegalStateException) { 936 throw preCommitException; 937 } 938 939 /* Now throw an exception that shows the commit problem. */ 940 throw EnvironmentFailureException.unexpectedException 941 ("Failed while attempting to commit transaction " + 942 id + ", aborted instead. Original exception = " + 943 preCommitException.getMessage(), 944 preCommitException); 945 } 946 947 /** 948 * Creates and logs the txn commit entry, enforcing the flush/Sync 949 * behavior. 950 * 951 * @param flushSyncBehavior the local durability requirements 952 * 953 * @return the committed log item 954 * 955 * @throws DatabaseException 956 */ logCommitEntry(SyncPolicy flushSyncBehavior, Collection<WriteLockInfo> obsoleteLsns)957 private LogItem logCommitEntry(SyncPolicy flushSyncBehavior, 958 Collection<WriteLockInfo> obsoleteLsns) 959 throws DatabaseException { 960 961 LogManager logManager = envImpl.getLogManager(); 962 assert checkForValidReplicatorNodeId(); 963 964 final CommitLogEntry commitEntry = 965 new CommitLogEntry(new TxnCommit(id, 966 lastLoggedLsn, 967 getReplicatorNodeId())); 968 969 LogItem item = new LogItem(); 970 item.entry = commitEntry; 971 item.provisional = Provisional.NO; 972 item.repContext = repContext; 973 974 LogContext context = new LogContext(); 975 context.obsoleteWriteLockInfo = obsoleteLsns; 976 977 switch (flushSyncBehavior) { 978 979 case SYNC: 980 context.flushRequired = true; 981 context.fsyncRequired = true; 982 break; 983 984 case WRITE_NO_SYNC: 985 context.flushRequired = true; 986 context.fsyncRequired = false; 987 break; 988 989 default: 990 context.flushRequired = false; 991 context.fsyncRequired = false; 992 break; 993 } 994 995 /* 996 * Do a final pre-log check just before the logging call, to minimize 997 * the window where the POSSIBLY_COMMITTED state may be set. [#21264] 998 */ 999 preLogCommitCheck(); 1000 1001 /* Log the commit with requested durability. */ 1002 boolean logSuccess = false; 1003 try { 1004 logManager.log(item, context); 1005 logSuccess = true; 1006 } catch (RuntimeException e) { 1007 1008 /* 1009 * Exceptions thrown during logging are expected to be fatal. 1010 * Ensure that the environment is invalidated when a non-fatal 1011 * exception is unexpectedly thrown, since the commit durability is 1012 * unknown [#21264]. 1013 * 1014 * However, we allow the environment to remain valid when testing 1015 * IOExceptions. In IOExceptionTest, we test a possible future 1016 * feature where the environment is not invalidated by disk-full. 1017 */ 1018 if (envImpl.isValid() && 1019 !FileManager.continueAfterWriteException()) { 1020 throw EnvironmentFailureException.unexpectedException 1021 (envImpl, 1022 "Unexpected non-fatal exception while logging commit", 1023 e); 1024 } 1025 throw e; 1026 } catch (Error e) { 1027 /* Ensure that the environment is invalidated. [#21264] */ 1028 envImpl.invalidate(e); 1029 throw e; 1030 } finally { 1031 1032 /* 1033 * If logging fails, there is still a possibility that the commit 1034 * is durable. [#21264] 1035 */ 1036 if (!logSuccess) { 1037 setState(Transaction.State.POSSIBLY_COMMITTED); 1038 } 1039 } 1040 1041 return item; 1042 } 1043 1044 /** 1045 * Pre-log check for an invalid environment or interrupted thread (this 1046 * thread may have been interrupted but we haven't found out yet, because 1047 * we haven't done a wait or an I/O) to narrow the time window where a 1048 * commit could become partially durable. See getPartialDurability. 1049 * [#21264] 1050 */ preLogCommitCheck()1051 private void preLogCommitCheck() { 1052 if (Thread.interrupted()) { 1053 throw new ThreadInterruptedException 1054 (envImpl, "Thread interrupted prior to logging the commit"); 1055 } 1056 envImpl.checkIfInvalid(); 1057 } 1058 1059 /* 1060 * A replicated txn must know the node of the master which issued it. 1061 */ checkForValidReplicatorNodeId()1062 private boolean checkForValidReplicatorNodeId() { 1063 if (isReplicated()) { 1064 if (getReplicatorNodeId() == 0) { 1065 return false; 1066 } 1067 1068 /* 1069 return (repContext.getClientVLSN() != null) && 1070 (!repContext.getClientVLSN().isNull()); 1071 */ 1072 } 1073 return true; 1074 } 1075 1076 /** 1077 * Extract obsolete LSN info from writeInfo. Do not add a WriteInfo if a 1078 * slot with a deleted LN was reused (abortKnownDeleted), to avoid double 1079 * counting. And count each abortLSN only once. 1080 */ getObsoleteLsnInfo()1081 private Collection<WriteLockInfo> getObsoleteLsnInfo() { 1082 1083 /* 1084 * A Map is used to prevent double counting abortLNS if there is more 1085 * then one node with the same abortLSN in this txn. Two nodes with 1086 * the same abortLSN occur when a deleted slot is reused in the same 1087 * txn. 1088 */ 1089 Map<Long, WriteLockInfo> map = new HashMap<Long, WriteLockInfo>(); 1090 1091 for (WriteLockInfo info : writeInfo.values()) { 1092 maybeAddWriteLockInfo(map, info); 1093 } 1094 1095 return map.values(); 1096 } 1097 maybeAddWriteLockInfo(Map<Long, WriteLockInfo> obsoleteLsnSet, WriteLockInfo info)1098 private void maybeAddWriteLockInfo(Map<Long, WriteLockInfo> obsoleteLsnSet, 1099 WriteLockInfo info) { 1100 if (info.getAbortLsn() == DbLsn.NULL_LSN || 1101 info.getAbortKnownDeleted()) { 1102 return; 1103 } 1104 if ((info.getAbortDb() != null) && 1105 info.getAbortDb().isLNImmediatelyObsolete()) { 1106 /* Was already counted obsolete during logging. */ 1107 return; 1108 } 1109 1110 final Long longLsn = Long.valueOf(info.getAbortLsn()); 1111 if (!obsoleteLsnSet.containsKey(longLsn)) { 1112 obsoleteLsnSet.put(longLsn, info); 1113 } 1114 } 1115 1116 /** 1117 * Abort this transaction. This flavor does not return an LSN, nor does it 1118 * require the logging of a durable abort record. 1119 */ abort()1120 public void abort() 1121 throws DatabaseException { 1122 1123 if (isClosed()) { 1124 return; 1125 } 1126 abort(false /* forceFlush */); 1127 } 1128 1129 /** 1130 * Abort this transaction. Steps are: 1131 * 1. Release LN read locks. 1132 * 2. Write a txn abort entry to the log. This is used for log file 1133 * cleaning optimization and replication, and there's no need to 1134 * guarantee a flush to disk. 1135 * 3. Find the last LN log entry written for this txn, and use that 1136 * to traverse the log looking for nodes to undo. For each node, 1137 * use the same undo logic as recovery to undo the transaction. Note 1138 * that we walk the log in order to undo in reverse order of the 1139 * actual operations. For example, suppose the txn did this: 1140 * delete K1/D1 (in LN 10) 1141 * create K1/D1 (in LN 20) 1142 * If we process LN10 before LN 20, we'd inadvertently create a 1143 * duplicate tree of "K1", which would be fatal for the mapping tree. 1144 * 4. Release the write lock for this LN. 1145 * 1146 * An abort differs from a rollback in that the former always undoes every 1147 * operation, and returns it to the pre-txn state. A rollback may return 1148 * the txn to an intermediate state, or to the pre-txn state. 1149 */ abort(boolean forceFlush)1150 public long abort(boolean forceFlush) 1151 throws DatabaseException { 1152 1153 return abortInternal(forceFlush); 1154 } 1155 1156 /** 1157 * @throws IllegalStateException via Transaction.abort if cursors are open. 1158 * 1159 * Note that IllegalStateException should never be thrown by 1160 * Transaction.abort because of a closed txn, since Transaction.commit and 1161 * abort set the Transaction.txn to null and disallow subsequent method 1162 * calls (other than abort). So in a sense the call to checkState(true) in 1163 * this method is unnecessary, although perhaps a good safeguard. 1164 */ abortInternal(boolean forceFlush)1165 private long abortInternal(boolean forceFlush) 1166 throws DatabaseException { 1167 1168 /* 1169 * If frozen, throw the appropriate exception, but don't attempt to 1170 * make any changes to cleanup the exception. 1171 */ 1172 boolean hooked = false; 1173 checkIfFrozen(false); 1174 1175 try { 1176 try { 1177 synchronized (this) { 1178 checkState(true); 1179 1180 /* 1181 * State is set to ABORTED before undo, so that other 1182 * threads cannot access this txn in the middle of undo. 1183 * [#19321] 1184 */ 1185 setState(Transaction.State.ABORTED); 1186 1187 /* Log the abort. */ 1188 if (updateLoggedForTxn()) { 1189 preLogAbortHook(); 1190 hooked = true; 1191 assert checkForValidReplicatorNodeId(); 1192 assert (commitLsn == NULL_LSN) && 1193 (abortLsn == NULL_LSN); 1194 final AbortLogEntry abortEntry = 1195 new AbortLogEntry( 1196 new TxnAbort(id, lastLoggedLsn, 1197 getReplicatorNodeId())); 1198 abortLsn = forceFlush ? 1199 envImpl.getLogManager(). 1200 logForceFlush(abortEntry, 1201 true /* fsyncRequired */, 1202 repContext) : 1203 envImpl.getLogManager().log(abortEntry, 1204 repContext); 1205 } 1206 } 1207 } finally { 1208 if (hooked) { 1209 postLogAbortHook(); 1210 hooked = false; 1211 } 1212 1213 /* 1214 * undo must be called outside the synchronization block to 1215 * preserve locking order: For non-blocking locks, the BIN 1216 * is latched before synchronizing on the Txn. If we were 1217 * to synchronize while calling undo, this order would be 1218 * reversed. 1219 */ 1220 undo(); 1221 } 1222 1223 /* 1224 * Purge any databaseImpls not needed as a result of the abort. Be 1225 * sure to do this outside the synchronization block, to avoid 1226 * conflict w/ checkpointer. 1227 */ 1228 cleanupDatabaseImpls(false); 1229 1230 synchronized (this) { 1231 boolean openCursors = checkCursorsForClose(); 1232 Logger logger = envImpl.getLogger(); 1233 if (logger.isLoggable(Level.FINE)) { 1234 LoggerUtils.fine(logger, envImpl, 1235 "Abort: id = " + id + " openCursors= " + 1236 openCursors); 1237 } 1238 1239 /* Invalidate any Db handles protected by this txn. */ 1240 if (openedDatabaseHandles != null) { 1241 for (Database handle : openedDatabaseHandles) { 1242 DbInternal.invalidate(handle); 1243 } 1244 } 1245 /* Delay the exception until cleanup is complete. */ 1246 if (openCursors) { 1247 envImpl.checkIfInvalid(); 1248 throw new IllegalStateException 1249 ("Transaction " + id + 1250 " detected open cursors while aborting"); 1251 } 1252 } 1253 } finally { 1254 1255 /* 1256 * The close method, which unregisters the txn, and must be called 1257 * after undo and cleanupDatabaseImpls. A transaction must remain 1258 * registered until all actions that modify/dirty INs are complete; 1259 * see Checkpointer class comments for details. [#19321] 1260 * 1261 * close must be called, even though the state has already been set 1262 * to ABORTED above, for two reasons: 1) To unregister the txn, and 1263 * 2) to allow subclasses to override the close method. 1264 * 1265 * close must be called outside the synchronization block to avoid 1266 * conflict w/ checkpointer. 1267 */ 1268 close(false); 1269 1270 if (abortLsn != NULL_LSN) { 1271 TriggerManager.runAbortTriggers(this); 1272 } 1273 } 1274 1275 return abortLsn; 1276 } 1277 1278 /** 1279 * Undo write operations and release all resources held by the transaction. 1280 */ undo()1281 protected void undo() 1282 throws DatabaseException { 1283 1284 /* 1285 * We need to undo, or reverse the effect of any applied operations on 1286 * the in-memory btree. We also need to make the latest version of any 1287 * record modified by the transaction obsolete. 1288 */ 1289 Set<Long> alreadyUndoneLsns = new HashSet<Long>(); 1290 Set<CompareSlot> alreadyUndoneSlots = new TreeSet<CompareSlot>(); 1291 TreeLocation location = new TreeLocation(); 1292 long undoLsn = lastLoggedLsn; 1293 try { 1294 while (undoLsn != NULL_LSN) { 1295 UndoReader undo = 1296 UndoReader.create(envImpl, undoLsn, undoDatabases); 1297 /* 1298 * Only undo the first instance we see of any node. All log 1299 * entries for a given node have the same abortLsn, so we don't 1300 * need to undo it multiple times. 1301 */ 1302 if (firstInstance(alreadyUndoneLsns, alreadyUndoneSlots, 1303 undo)) { 1304 RecoveryManager.abortUndo 1305 (envImpl.getLogger(), 1306 Level.FINER, 1307 undo.db, 1308 location, 1309 undo.logEntry, 1310 undoLsn); 1311 1312 countObsoleteExact(undoLsn, undo, isRolledBack()); 1313 } 1314 1315 /* Move on to the previous log entry for this txn. */ 1316 undoLsn = undo.logEntry.getUserTxn().getLastLsn(); 1317 } 1318 } catch (DatabaseException e) { 1319 String lsnMsg = "LSN=" + DbLsn.getNoFormatString(undoLsn); 1320 LoggerUtils.traceAndLogException(envImpl, "Txn", "undo", 1321 lsnMsg, e); 1322 e.addErrorMessage(lsnMsg); 1323 throw e; 1324 } catch (RuntimeException e) { 1325 throw EnvironmentFailureException.unexpectedException 1326 ("Txn undo for LSN=" + DbLsn.getNoFormatString(undoLsn), e); 1327 } 1328 1329 /* 1330 * Release all read locks after the undo (since the undo may need to 1331 * read in mapLNs). 1332 */ 1333 if (readLocks != null) { 1334 clearReadLocks(); 1335 } 1336 1337 /* Set database state for deletes before releasing any write locks. */ 1338 setDeletedDatabaseState(false); 1339 1340 /* Throw away write lock collection, don't retain any locks. */ 1341 Set<Long> empty = Collections.emptySet(); 1342 clearWriteLocks(empty); 1343 1344 /* 1345 * Let the delete related info (binreferences and dbs) get gc'ed. Don't 1346 * explicitly iterate and clear -- that's far less efficient, gives GC 1347 * wrong input. 1348 */ 1349 deleteInfo = null; 1350 } 1351 1352 /** 1353 * For an explanation of obsoleteDupsAllowed, see ReplayTxn.rollback. 1354 */ countObsoleteExact(long undoLsn, UndoReader undo, boolean obsoleteDupsAllowed)1355 private void countObsoleteExact(long undoLsn, UndoReader undo, 1356 boolean obsoleteDupsAllowed) { 1357 /* 1358 * "Immediately obsolete" LNs are counted as obsolete when they are 1359 * logged, so no need to repeat here. 1360 */ 1361 if (undo.logEntry.isImmediatelyObsolete(undo.db)) { 1362 return; 1363 } 1364 1365 LogManager logManager = envImpl.getLogManager(); 1366 1367 if (obsoleteDupsAllowed) { 1368 logManager.countObsoleteNodeDupsAllowed 1369 (undoLsn, 1370 null, // type 1371 undo.logEntrySize, 1372 undo.db); 1373 } else { 1374 logManager.countObsoleteNode(undoLsn, 1375 null, // type 1376 undo.logEntrySize, 1377 undo.db, 1378 true); // countExact 1379 } 1380 } 1381 1382 /** 1383 * Release any write locks that are not in the retainedNodes set. 1384 */ clearWriteLocks(Set<Long> retainedNodes)1385 protected void clearWriteLocks(Set<Long> retainedNodes) 1386 throws DatabaseException { 1387 1388 if (writeInfo == null) { 1389 return; 1390 } 1391 1392 /* Release all write locks, clear lock collection. */ 1393 Iterator<Map.Entry<Long, WriteLockInfo>> iter = 1394 writeInfo.entrySet().iterator(); 1395 while (iter.hasNext()) { 1396 Map.Entry<Long, WriteLockInfo> entry = iter.next(); 1397 Long lsn = entry.getKey(); 1398 1399 /* Release any write locks not in the retained set. */ 1400 if (!retainedNodes.contains(lsn)) { 1401 lockManager.release(lsn, this); 1402 iter.remove(); 1403 } 1404 } 1405 1406 if (writeInfo.size() == 0) { 1407 writeInfo = null; 1408 } 1409 } 1410 clearReadLocks()1411 protected int clearReadLocks() 1412 throws DatabaseException { 1413 1414 int numReadLocks = 0; 1415 if (readLocks != null) { 1416 numReadLocks = readLocks.size(); 1417 Iterator<Long> iter = readLocks.iterator(); 1418 while (iter.hasNext()) { 1419 Long rLockNid = iter.next(); 1420 lockManager.release(rLockNid, this); 1421 } 1422 readLocks = null; 1423 } 1424 return numReadLocks; 1425 } 1426 1427 /** 1428 * Called by the recovery manager when logging a transaction aware object. 1429 * This method is synchronized by the caller, by being called within the 1430 * log latch. Record the last LSN for this transaction, to create the 1431 * transaction chain, and also record the LSN in the write info for abort 1432 * logic. 1433 */ addLogInfo(long lastLsn)1434 public synchronized void addLogInfo(long lastLsn) { 1435 /* Save the last LSN for maintaining the transaction LSN chain. */ 1436 lastLoggedLsn = lastLsn; 1437 1438 /* 1439 * Save handle to LSN for aborts. 1440 * 1441 * If this is the first LSN, save it for calculating the first LSN 1442 * of any active txn, for checkpointing. 1443 */ 1444 if (firstLoggedLsn == NULL_LSN) { 1445 firstLoggedLsn = lastLsn; 1446 } 1447 } 1448 1449 /** 1450 * [#16861] The firstLoggedLsn field is volatile to avoid making 1451 * getFirstActiveLsn synchronized, which causes a deadlock in HA. 1452 * 1453 * @return first logged LSN, to aid recovery undo 1454 */ getFirstActiveLsn()1455 public long getFirstActiveLsn() { 1456 return firstLoggedLsn; 1457 } 1458 1459 /** 1460 * @return true if this txn has logged any log entries. 1461 */ updateLoggedForTxn()1462 protected boolean updateLoggedForTxn() { 1463 return (lastLoggedLsn != DbLsn.NULL_LSN); 1464 } 1465 1466 /** 1467 * @param dbImpl databaseImpl to remove 1468 * @param deleteAtCommit true if this databaseImpl should be cleaned on 1469 * commit, false if it should be cleaned on abort. 1470 */ 1471 @Override markDeleteAtTxnEnd(DatabaseImpl dbImpl, boolean deleteAtCommit)1472 public synchronized void markDeleteAtTxnEnd(DatabaseImpl dbImpl, 1473 boolean deleteAtCommit) { 1474 int delta = 0; 1475 if (deletedDatabases == null) { 1476 deletedDatabases = new HashSet<DatabaseCleanupInfo>(); 1477 delta += MemoryBudget.HASHSET_OVERHEAD; 1478 } 1479 1480 deletedDatabases.add(new DatabaseCleanupInfo(dbImpl, 1481 deleteAtCommit)); 1482 delta += MemoryBudget.HASHSET_ENTRY_OVERHEAD + 1483 MemoryBudget.OBJECT_OVERHEAD; 1484 updateMemoryUsage(delta); 1485 1486 /* releaseDb will be called by cleanupDatabaseImpls. */ 1487 } 1488 getDeletedDatabases()1489 public Set<DatabaseCleanupInfo> getDeletedDatabases() { 1490 return deletedDatabases; 1491 } 1492 1493 /* 1494 * Leftover databaseImpls that are a by-product of database operations like 1495 * removeDatabase(), truncateDatabase() will be deleted after the write 1496 * locks are released. However, do set the database state appropriately 1497 * before the locks are released. 1498 */ setDeletedDatabaseState(boolean isCommit)1499 protected void setDeletedDatabaseState(boolean isCommit) { 1500 if (deletedDatabases != null) { 1501 Iterator<DatabaseCleanupInfo> iter = deletedDatabases.iterator(); 1502 while (iter.hasNext()) { 1503 DatabaseCleanupInfo info = iter.next(); 1504 if (info.deleteAtCommit == isCommit) { 1505 info.dbImpl.startDeleteProcessing(); 1506 } 1507 } 1508 } 1509 } 1510 1511 /** 1512 * Cleanup leftover databaseImpls that are a by-product of database 1513 * operations like removeDatabase(), truncateDatabase(). 1514 * 1515 * This method must be called outside the synchronization on this txn, 1516 * because it calls finishDeleteProcessing, which gets the TxnManager's 1517 * allTxns latch. The checkpointer also gets the allTxns latch, and within 1518 * that latch, needs to synchronize on individual txns, so we must avoid a 1519 * latching hiearchy conflict. 1520 * 1521 * [#16861] FUTURE: Perhaps this special handling is no longer needed, now 1522 * that firstLoggedLsn is volatile and getFirstActiveLsn is not 1523 * synchronized. 1524 */ cleanupDatabaseImpls(boolean isCommit)1525 protected void cleanupDatabaseImpls(boolean isCommit) 1526 throws DatabaseException { 1527 1528 if (deletedDatabases != null) { 1529 /* Make a copy of the deleted databases while synchronized. */ 1530 DatabaseCleanupInfo[] infoArray; 1531 synchronized (this) { 1532 infoArray = new DatabaseCleanupInfo[deletedDatabases.size()]; 1533 deletedDatabases.toArray(infoArray); 1534 } 1535 for (DatabaseCleanupInfo info : infoArray) { 1536 if (info.deleteAtCommit == isCommit) { 1537 1538 /* 1539 * If deletedDatabases contains same databases with 1540 * different deleteAtCommit, firstly release the database, 1541 * then delete it. [#19636] 1542 */ 1543 if (checkRepeatedDeletedDB(infoArray, info)) { 1544 envImpl.getDbTree().releaseDb(info.dbImpl); 1545 } 1546 /* releaseDb will be called by finishDeleteProcessing. */ 1547 info.dbImpl.finishDeleteProcessing(); 1548 } else if(!checkRepeatedDeletedDB(infoArray, info)){ 1549 1550 /* 1551 * If deletedDatabases contains same databases with 1552 * different deleteAtCommit, do nothing. [#19636] 1553 */ 1554 envImpl.getDbTree().releaseDb(info.dbImpl); 1555 } 1556 } 1557 deletedDatabases = null; 1558 } 1559 } 1560 checkRepeatedDeletedDB(DatabaseCleanupInfo[] infoArray, DatabaseCleanupInfo info)1561 private boolean checkRepeatedDeletedDB(DatabaseCleanupInfo[] infoArray, 1562 DatabaseCleanupInfo info) { 1563 for (DatabaseCleanupInfo element : infoArray) { 1564 if (element.dbImpl.getId().equals(info.dbImpl.getId()) && 1565 element.deleteAtCommit != info.deleteAtCommit){ 1566 return true; 1567 } 1568 } 1569 return false; 1570 } 1571 ensureWriteInfo()1572 private synchronized void ensureWriteInfo() { 1573 if (writeInfo == null) { 1574 writeInfo = new HashMap<Long, WriteLockInfo>(); 1575 undoDatabases = new HashMap<DatabaseId, DatabaseImpl>(); 1576 updateMemoryUsage(MemoryBudget.TWOHASHMAPS_OVERHEAD); 1577 } 1578 } 1579 1580 /** 1581 * Add lock to the appropriate queue. 1582 */ 1583 @Override addLock(Long lsn, LockType type, LockGrantType grantStatus)1584 protected synchronized void addLock(Long lsn, 1585 LockType type, 1586 LockGrantType grantStatus) { 1587 if (type.isWriteLock()) { 1588 1589 ensureWriteInfo(); 1590 writeInfo.put(lsn, new WriteLockInfo()); 1591 1592 int delta = WRITE_LOCK_OVERHEAD; 1593 1594 if ((grantStatus == LockGrantType.PROMOTION) || 1595 (grantStatus == LockGrantType.WAIT_PROMOTION)) { 1596 readLocks.remove(lsn); 1597 delta -= READ_LOCK_OVERHEAD; 1598 } 1599 updateMemoryUsage(delta); 1600 } else { 1601 addReadLock(lsn); 1602 } 1603 } 1604 addReadLock(Long lsn)1605 private void addReadLock(Long lsn) { 1606 int delta = 0; 1607 if (readLocks == null) { 1608 readLocks = new HashSet<Long>(); 1609 delta = MemoryBudget.HASHSET_OVERHEAD; 1610 } 1611 1612 readLocks.add(lsn); 1613 delta += READ_LOCK_OVERHEAD; 1614 updateMemoryUsage(delta); 1615 } 1616 1617 /** 1618 * Remove the lock from the set owned by this transaction. If specified to 1619 * LockManager.release, the lock manager will call this when its releasing 1620 * a lock. Usually done because the transaction doesn't need to really keep 1621 * the lock, i.e for a deleted record. 1622 */ 1623 @Override 1624 protected removeLock(long lsn)1625 synchronized void removeLock(long lsn) { 1626 1627 /* 1628 * We could optimize by passing the lock type so we know which 1629 * collection to look in. Be careful of demoted locks, which have 1630 * shifted collection. 1631 * 1632 * Don't bother updating memory utilization here -- we'll update at 1633 * transaction end. 1634 */ 1635 if ((readLocks != null) && 1636 readLocks.remove(lsn)) { 1637 updateMemoryUsage(0 - READ_LOCK_OVERHEAD); 1638 } else if ((writeInfo != null) && 1639 (writeInfo.remove(lsn) != null)) { 1640 updateMemoryUsage(0 - WRITE_LOCK_OVERHEAD); 1641 } 1642 } 1643 1644 /** 1645 * A lock is being demoted. Move it from the write collection into the read 1646 * collection. 1647 */ 1648 @Override 1649 @SuppressWarnings("unused") moveWriteToReadLock(long lsn, Lock lock)1650 synchronized void moveWriteToReadLock(long lsn, Lock lock) { 1651 1652 boolean found = false; 1653 if ((writeInfo != null) && 1654 (writeInfo.remove(lsn) != null)) { 1655 found = true; 1656 updateMemoryUsage(0 - WRITE_LOCK_OVERHEAD); 1657 } 1658 1659 assert found : "Couldn't find lock for Node " + lsn + 1660 " in writeInfo Map."; 1661 addReadLock(lsn); 1662 } 1663 updateMemoryUsage(int delta)1664 private void updateMemoryUsage(int delta) { 1665 inMemorySize += delta; 1666 accumulatedDelta += delta; 1667 if (accumulatedDelta > ACCUMULATED_LIMIT || 1668 accumulatedDelta < -ACCUMULATED_LIMIT) { 1669 envImpl.getMemoryBudget().updateTxnMemoryUsage(accumulatedDelta); 1670 accumulatedDelta = 0; 1671 } 1672 } 1673 1674 /** 1675 * Returns the amount of memory currently budgeted for this transaction. 1676 */ getBudgetedMemorySize()1677 int getBudgetedMemorySize() { 1678 return inMemorySize - accumulatedDelta; 1679 } 1680 1681 /** 1682 * @return the WriteLockInfo for this node. 1683 */ 1684 @Override getWriteLockInfo(long lsn)1685 public WriteLockInfo getWriteLockInfo(long lsn) { 1686 WriteLockInfo wli = null; 1687 synchronized (this) { 1688 if (writeInfo != null) { 1689 wli = writeInfo.get(lsn); 1690 } 1691 } 1692 1693 if (wli == null) { 1694 throw EnvironmentFailureException.unexpectedState 1695 ("writeInfo is null in Txn.getWriteLockInfo"); 1696 } 1697 return wli; 1698 } 1699 1700 /** 1701 * Is always transactional. 1702 */ 1703 @Override isTransactional()1704 public boolean isTransactional() { 1705 return true; 1706 } 1707 1708 /** 1709 * Determines whether this is an auto transaction. 1710 */ isAutoTxn()1711 public boolean isAutoTxn() { 1712 return isAutoCommit; 1713 } 1714 1715 @Override isReadOnly()1716 public boolean isReadOnly() { 1717 return readOnly; 1718 } 1719 1720 /** 1721 * Is serializable isolation if so configured. 1722 */ 1723 @Override isSerializableIsolation()1724 public boolean isSerializableIsolation() { 1725 return serializableIsolation; 1726 } 1727 1728 /** 1729 * Is read-committed isolation if so configured. 1730 */ 1731 @Override isReadCommittedIsolation()1732 public boolean isReadCommittedIsolation() { 1733 return readCommittedIsolation; 1734 } 1735 1736 /** 1737 * Returns true if the sync api was used for configuration 1738 */ getExplicitSyncConfigured()1739 public boolean getExplicitSyncConfigured() { 1740 return explicitSyncConfigured; 1741 } 1742 1743 /** 1744 * Returns true if the durability api was used for configuration. 1745 */ getExplicitDurabilityConfigured()1746 public boolean getExplicitDurabilityConfigured() { 1747 return explicitDurabilityConfigured; 1748 } 1749 1750 /** 1751 * This is a transactional locker. 1752 */ 1753 @Override getTxnLocker()1754 public Txn getTxnLocker() { 1755 return this; 1756 } 1757 1758 /** 1759 * Returns 'this', since this locker holds no non-transactional locks. 1760 * Since this is returned, sharing of locks is obviously supported. 1761 */ 1762 @Override newNonTxnLocker()1763 public Locker newNonTxnLocker() { 1764 return this; 1765 } 1766 1767 /** 1768 * This locker holds no non-transactional locks. 1769 */ 1770 @Override releaseNonTxnLocks()1771 public void releaseNonTxnLocks() { 1772 } 1773 1774 /** 1775 * Created transactions do nothing at the end of the operation. 1776 */ 1777 @Override nonTxnOperationEnd()1778 public void nonTxnOperationEnd() { 1779 } 1780 1781 /* 1782 * @see com.sleepycat.je.txn.Locker#operationEnd(boolean) 1783 */ 1784 @Override operationEnd(boolean operationOK)1785 public void operationEnd(boolean operationOK) 1786 throws DatabaseException { 1787 1788 if (!isAutoCommit) { 1789 /* Created transactions do nothing at the end of the operation. */ 1790 return; 1791 } 1792 1793 if (operationOK) { 1794 commit(); 1795 } else { 1796 abort(false); // no sync required 1797 } 1798 } 1799 1800 /** 1801 * Called at the end of a database open operation to add the database 1802 * handle to a user txn. When a user txn aborts, handles opened using that 1803 * txn are invalidated. 1804 * 1805 * A non-txnal locker or auto-commit txn does not retain the handle, 1806 * because the open database operation will succeed or fail atomically and 1807 * no database invalidation is needed at a later time. 1808 * 1809 * @see HandleLocker 1810 */ 1811 @Override addOpenedDatabase(Database dbHandle)1812 public synchronized void addOpenedDatabase(Database dbHandle) { 1813 if (isAutoCommit) { 1814 return; 1815 } 1816 if (openedDatabaseHandles == null) { 1817 openedDatabaseHandles = new HashSet<Database>(); 1818 } 1819 openedDatabaseHandles.add(dbHandle); 1820 } 1821 1822 /** 1823 * Increase the counter if a new Cursor is opened under this transaction. 1824 */ 1825 @Override 1826 @SuppressWarnings("unused") registerCursor(CursorImpl cursor)1827 public void registerCursor(CursorImpl cursor) { 1828 cursors.getAndIncrement(); 1829 } 1830 1831 /** 1832 * Decrease the counter if a Cursor is closed under this transaction. 1833 */ 1834 @Override 1835 @SuppressWarnings("unused") unRegisterCursor(CursorImpl cursor)1836 public void unRegisterCursor(CursorImpl cursor) { 1837 cursors.getAndDecrement(); 1838 } 1839 1840 /* 1841 * Txns always require locking. 1842 */ 1843 @Override lockingRequired()1844 public boolean lockingRequired() { 1845 return true; 1846 } 1847 1848 /** 1849 * Check if all cursors associated with the txn are closed. If not, those 1850 * open cursors will be forcibly closed. 1851 * @return true if open cursors exist 1852 */ checkCursorsForClose()1853 private boolean checkCursorsForClose() { 1854 return (cursors.get() != 0); 1855 } 1856 1857 /** 1858 * stats 1859 */ 1860 @Override collectStats()1861 public StatGroup collectStats() { 1862 StatGroup stats = 1863 new StatGroup("Transaction lock counts" , 1864 "Read and write locks held by transaction " + id); 1865 1866 IntStat statReadLocks = new IntStat(stats, LOCK_READ_LOCKS); 1867 IntStat statWriteLocks = new IntStat(stats, LOCK_WRITE_LOCKS); 1868 IntStat statTotalLocks = new IntStat(stats, LOCK_TOTAL); 1869 1870 synchronized (this) { 1871 int nReadLocks = (readLocks == null) ? 0 : readLocks.size(); 1872 statReadLocks.add(nReadLocks); 1873 int nWriteLocks = (writeInfo == null) ? 0 : writeInfo.size(); 1874 statWriteLocks.add(nWriteLocks); 1875 statTotalLocks.add(nReadLocks + nWriteLocks); 1876 } 1877 1878 return stats; 1879 } 1880 1881 /** 1882 * Set the state of a transaction to abort-only. Should ONLY be called 1883 * by OperationFailureException. 1884 */ 1885 @Override setOnlyAbortable(OperationFailureException cause)1886 public void setOnlyAbortable(OperationFailureException cause) { 1887 assert cause != null; 1888 setState(Transaction.State.MUST_ABORT); 1889 onlyAbortableCause = cause; 1890 } 1891 1892 /** 1893 * Set the state of a transaction's IMPORTUNATE bit. 1894 */ 1895 @Override setImportunate(boolean importunate)1896 public void setImportunate(boolean importunate) { 1897 if (importunate) { 1898 txnFlags |= IMPORTUNATE; 1899 } else { 1900 txnFlags &= ~IMPORTUNATE; 1901 } 1902 } 1903 1904 /** 1905 * Get the state of a transaction's IMPORTUNATE bit. 1906 */ 1907 @Override getImportunate()1908 public boolean getImportunate() { 1909 return (txnFlags & IMPORTUNATE) != 0; 1910 } 1911 1912 /** 1913 * Checks for preemption in this locker and all its child buddies. Does 1914 * NOT call checkPreempted on its child buddies, since this would cause an 1915 * infinite recursion. 1916 */ 1917 @Override checkPreempted(final Locker allowPreemptedLocker)1918 public void checkPreempted(final Locker allowPreemptedLocker) 1919 throws OperationFailureException { 1920 1921 /* First check this locker. */ 1922 throwIfPreempted(allowPreemptedLocker); 1923 1924 /* 1925 * Then check our buddy lockers. It's OK to call throwIfPreempted while 1926 * synchronized on buddyLockers, since it takes no locks. 1927 */ 1928 if (buddyLockers != null) { 1929 synchronized (buddyLockers) { 1930 for (BuddyLocker buddy : buddyLockers) { 1931 buddy.throwIfPreempted(allowPreemptedLocker); 1932 } 1933 } 1934 } 1935 } 1936 1937 /** 1938 * Throw an exception if the transaction is not open. 1939 * 1940 * If calledByAbort is true, it means we're being called from abort(). But 1941 * once closed, a Transaction never calls abort(). See comment at the top 1942 * of abortInternal. 1943 * 1944 * Caller must invoke with "this" synchronized. 1945 */ 1946 @Override checkState(boolean calledByAbort)1947 public void checkState(boolean calledByAbort) 1948 throws DatabaseException { 1949 1950 switch (txnState) { 1951 1952 case OPEN: 1953 return; 1954 1955 case MUST_ABORT: 1956 1957 /* Don't complain if the user is doing what we asked. */ 1958 if (calledByAbort) { 1959 return; 1960 } 1961 1962 /* 1963 * Throw the original exception that caused the txn to be set 1964 * to abort-only, wrapped in a new exception of the same class. 1965 * That way, both stack traces are available and the user can 1966 * specify a meaningful class in their catch statement. 1967 * 1968 * It's ok for FindBugs to whine about id not being 1969 * synchronized. 1970 */ 1971 throw onlyAbortableCause.wrapSelf 1972 ("Transaction " + id + 1973 " must be aborted, caused by: " + onlyAbortableCause); 1974 1975 default: 1976 /* All other states are equivalent to closed. */ 1977 1978 /* 1979 * It's ok for FindBugs to whine about id not being 1980 * synchronized. 1981 */ 1982 throw new IllegalStateException 1983 ("Transaction " + id + " has been closed."); 1984 } 1985 } 1986 1987 /** 1988 * Close and unregister this txn. 1989 */ close(boolean isCommit)1990 public void close(boolean isCommit) { 1991 1992 if (isCommit) { 1993 /* Set final state to COMMITTED, if not set earlier. */ 1994 if (txnState == Transaction.State.OPEN) { 1995 setState(Transaction.State.COMMITTED); 1996 } 1997 } else { 1998 /* This was set earlier by abort, but here also for safety. */ 1999 setState(Transaction.State.ABORTED); 2000 } 2001 2002 /* 2003 * UnregisterTxn must be called outside the synchronization on this 2004 * txn, because it gets the TxnManager's allTxns latch. The 2005 * checkpointer also gets the allTxns latch, and within that latch, 2006 * needs to synchronize on individual txns, so we must avoid a latching 2007 * hierarchy conflict. 2008 * 2009 * [#16861] FUTURE: Perhaps this special handling is no longer needed, 2010 * now that firstLoggedLsn is volatile and getFirstActiveLsn is not 2011 * synchronized. 2012 */ 2013 envImpl.getTxnManager().unRegisterTxn(this, isCommit); 2014 2015 /* Set the superclass Locker state to closed. */ 2016 close(); 2017 } 2018 setState(Transaction.State state)2019 private synchronized void setState(Transaction.State state) { 2020 txnState = state; 2021 } 2022 getState()2023 public Transaction.State getState() { 2024 return txnState; 2025 } 2026 2027 @Override isValid()2028 public boolean isValid() { 2029 return txnState == Transaction.State.OPEN; 2030 } 2031 isClosed()2032 public boolean isClosed() { 2033 return txnState != Transaction.State.OPEN && 2034 txnState != Transaction.State.MUST_ABORT; 2035 } 2036 isOnlyAbortable()2037 public boolean isOnlyAbortable() { 2038 return txnState == Transaction.State.MUST_ABORT; 2039 } 2040 2041 /* Non replicated txns don't use a node ID. */ getReplicatorNodeId()2042 protected int getReplicatorNodeId() { 2043 return 0; 2044 } 2045 2046 /* 2047 * Log support 2048 */ 2049 2050 /** 2051 * @see VersionedWriteLoggable#getLastFormatChange 2052 */ 2053 @Override getLastFormatChange()2054 public int getLastFormatChange() { 2055 return LAST_FORMAT_CHANGE; 2056 } 2057 2058 /** 2059 * @see Loggable#getLogSize 2060 */ 2061 @Override getLogSize()2062 public int getLogSize() { 2063 return LogUtils.getPackedLongLogSize(id) + 2064 LogUtils.getPackedLongLogSize(lastLoggedLsn); 2065 } 2066 2067 /** 2068 * @see VersionedWriteLoggable#getLogSize(int) 2069 */ 2070 @Override getLogSize(final int logVersion)2071 public int getLogSize(final int logVersion) { 2072 return BasicVersionedWriteLoggable.getLogSize(this, logVersion); 2073 } 2074 2075 /** 2076 * @see Loggable#writeToLog 2077 * 2078 * It's ok for FindBugs to whine about id not being synchronized. 2079 */ 2080 @Override writeToLog(ByteBuffer logBuffer)2081 public void writeToLog(ByteBuffer logBuffer) { 2082 LogUtils.writePackedLong(logBuffer, id); 2083 LogUtils.writePackedLong(logBuffer, lastLoggedLsn); 2084 } 2085 2086 /** 2087 * @see VersionedWriteLoggable#writeToLog(ByteBuffer, int) 2088 */ 2089 @Override writeToLog(final ByteBuffer logBuffer, final int logVersion)2090 public void writeToLog(final ByteBuffer logBuffer, final int logVersion) { 2091 BasicVersionedWriteLoggable.writeToLog(this, logBuffer, logVersion); 2092 } 2093 2094 /** 2095 * @see Loggable#readFromLog 2096 * 2097 * It's ok for FindBugs to whine about id not being synchronized. 2098 */ 2099 @Override readFromLog(ByteBuffer logBuffer, int entryVersion)2100 public void readFromLog(ByteBuffer logBuffer, int entryVersion) { 2101 id = LogUtils.readLong(logBuffer, (entryVersion < 6)); 2102 lastLoggedLsn = LogUtils.readLong(logBuffer, (entryVersion < 6)); 2103 } 2104 2105 /** 2106 * @see Loggable#dumpLog 2107 */ 2108 @Override 2109 @SuppressWarnings("unused") dumpLog(StringBuilder sb, boolean verbose)2110 public void dumpLog(StringBuilder sb, boolean verbose) { 2111 sb.append("<txn id=\""); 2112 sb.append(getId()); 2113 sb.append("\">"); 2114 sb.append(DbLsn.toString(lastLoggedLsn)); 2115 sb.append("</txn>"); 2116 } 2117 2118 /** 2119 * @see Loggable#getTransactionId 2120 */ 2121 @Override getTransactionId()2122 public long getTransactionId() { 2123 return getId(); 2124 } 2125 2126 /** 2127 * @see Loggable#logicalEquals 2128 */ 2129 @Override logicalEquals(Loggable other)2130 public boolean logicalEquals(Loggable other) { 2131 2132 if (!(other instanceof Txn)) { 2133 return false; 2134 } 2135 2136 return id == ((Txn) other).id; 2137 } 2138 2139 /** 2140 * Send trace messages to the java.util.logger. Don't rely on the logger 2141 * alone to conditionalize whether we send this message, we don't even want 2142 * to construct the message if the level is not enabled. The string 2143 * construction can be numerous enough to show up on a performance profile. 2144 */ traceCommit(int numWriteLocks, int numReadLocks)2145 private void traceCommit(int numWriteLocks, int numReadLocks) { 2146 Logger logger = envImpl.getLogger(); 2147 if (logger.isLoggable(Level.FINE)) { 2148 StringBuilder sb = new StringBuilder(); 2149 sb.append(" Commit: id = ").append(id); 2150 sb.append(" numWriteLocks=").append(numWriteLocks); 2151 sb.append(" numReadLocks = ").append(numReadLocks); 2152 LoggerUtils.fine(logger, envImpl, sb.toString()); 2153 } 2154 } 2155 2156 /** 2157 * Store information about a DatabaseImpl that will have to be 2158 * purged at transaction commit or abort. This handles cleanup after 2159 * operations like Environment.truncateDatabase, 2160 * Environment.removeDatabase. Cleanup like this is done outside the 2161 * usual transaction commit or node undo processing, because 2162 * the mapping tree is always auto Txn'ed to avoid deadlock and is 2163 * essentially non-transactional. 2164 */ 2165 public static class DatabaseCleanupInfo { 2166 DatabaseImpl dbImpl; 2167 2168 /* if true, clean on commit. If false, clean on abort. */ 2169 boolean deleteAtCommit; 2170 DatabaseCleanupInfo(DatabaseImpl dbImpl, boolean deleteAtCommit)2171 DatabaseCleanupInfo(DatabaseImpl dbImpl, 2172 boolean deleteAtCommit) { 2173 this.dbImpl = dbImpl; 2174 this.deleteAtCommit = deleteAtCommit; 2175 } 2176 2177 /** 2178 * Make sure that a set of DatabaseCleanupInfo only has one entry 2179 * per databaseImpl/deleteAtCommit tuple. 2180 */ 2181 @Override equals(Object obj)2182 public boolean equals(Object obj) { 2183 if (!(obj instanceof DatabaseCleanupInfo)) { 2184 return false; 2185 } 2186 2187 DatabaseCleanupInfo other = (DatabaseCleanupInfo) obj; 2188 return (dbImpl.equals(other.dbImpl)) && 2189 (deleteAtCommit == other.deleteAtCommit); 2190 } 2191 2192 @Override hashCode()2193 public int hashCode() { 2194 return dbImpl.hashCode(); 2195 } 2196 } 2197 2198 /* Transaction hooks used for replication support. */ 2199 2200 /** 2201 * A replicated environment introduces some new considerations when 2202 * entering a transaction scope via an Environment.transactionBegin() 2203 * operation. 2204 * 2205 * On a Replica, the transactionBegin() operation must wait until the 2206 * Replica has synched up to where it satisfies the ConsistencyPolicy that 2207 * is in effect. 2208 * 2209 * On a Master, the transactionBegin() must wait until the Feeder has 2210 * sufficient connections to ensure that it can satisfy the 2211 * ReplicaAckPolicy, since if it does not, it will fail at commit() and the 2212 * work done in the transaction will need to be undone. 2213 * 2214 * This hook provides the mechanism for implementing the above support for 2215 * replicated transactions. It ignores all non-replicated transactions. 2216 * 2217 * The hook throws ReplicaStateException, if a Master switches to a Replica 2218 * state while waiting for its Replicas connections. Changes from a Replica 2219 * to a Master are handled transparently to the application. Exceptions 2220 * manifest themselves as DatabaseException at the interface to minimize 2221 * use of Replication based exceptions in core JE. 2222 * 2223 * @param config the transaction config that applies to the txn 2224 * 2225 * @throws DatabaseException if there is a failure 2226 */ txnBeginHook(TransactionConfig config)2227 protected void txnBeginHook(TransactionConfig config) 2228 throws DatabaseException { 2229 2230 /* Overridden by Txn subclasses when appropriate */ 2231 } 2232 2233 /** 2234 * This hook is invoked before the commit of a transaction that made 2235 * changes to a replicated environment. It's invoked for transactions 2236 * executed on the master or replica, but is only relevant to transactions 2237 * being done on the master. When invoked for a transaction on a replica 2238 * the implementation just returns. 2239 * 2240 * The hook is invoked at a very specific point in the normal commit 2241 * sequence: immediately before the commit log entry is written to the log. 2242 * It represents the last chance to abort the transaction and provides an 2243 * opportunity to make some final checks before allowing the commit can go 2244 * ahead. Note that it should be possible to abort the transaction at the 2245 * time the hook is invoked. 2246 * 2247 * After invocation of the "pre" hook one of the "post" hooks: 2248 * postLogCommitHook or postLogAbortHook must always be invoked. 2249 * 2250 * Exceptions thrown by this hook result in the transaction being aborted 2251 * and the exception being propagated back to the application. 2252 * 2253 * @throws DatabaseException if there was a problem and that the 2254 * transaction should be aborted. 2255 */ preLogCommitHook()2256 protected void preLogCommitHook() 2257 throws DatabaseException { 2258 2259 /* Overridden by Txn subclasses when appropriate */ 2260 } 2261 2262 /** 2263 * This hook is invoked after the commit record has been written to the 2264 * log, but before write locks have been released, so that other 2265 * application cannot see the changes made by the transaction. At this 2266 * point the transaction has been committed by the Master. 2267 * 2268 * Exceptions thrown by this hook result in the transaction being completed 2269 * on the Master, that is, locks are released, etc. and the exception is 2270 * propagated back to the application. 2271 * 2272 * @param commitItem the commit item that was just logged 2273 * 2274 * @throws DatabaseException to indicate that there was a replication 2275 * related problem that needs to be communicated back to the application. 2276 */ postLogCommitHook(LogItem commitItem)2277 protected void postLogCommitHook(LogItem commitItem) 2278 throws DatabaseException { 2279 2280 /* Overridden by Txn subclasses when appropriate */ 2281 } 2282 preLogAbortHook()2283 protected void preLogAbortHook() 2284 throws DatabaseException { 2285 2286 /* Override by Txn subclasses when appropriate */ 2287 } 2288 2289 /** 2290 * Invoked if the transaction associated with the preLogCommitHook was 2291 * subsequently aborted, for example due to a lack of disk space. This 2292 * method is responsible for any cleanup that may need to be done as a 2293 * result of the abort. 2294 * 2295 * Note that only one of the "post" hooks (commit or abort) is invoked 2296 * following the invocation of the "pre" hook. 2297 */ postLogCommitAbortHook()2298 protected void postLogCommitAbortHook() { 2299 /* Overridden by Txn subclasses when appropriate */ 2300 } 2301 postLogAbortHook()2302 protected void postLogAbortHook() { 2303 /* Overridden by Txn subclasses when appropriate */ 2304 } 2305 2306 /** 2307 * Returns the CommitToken associated with a successful replicated commit. 2308 * 2309 * @see com.sleepycat.je.Transaction#getCommitToken 2310 */ getCommitToken()2311 public CommitToken getCommitToken() { 2312 return null; 2313 } 2314 2315 /** 2316 * Identifies exceptions that may be propagated back to the caller during 2317 * the postCommit phase of a transaction commit. 2318 * 2319 * @param postCommitException the exception being evaluated 2320 * 2321 * @return true if the exception must be propagated back to the caller, 2322 * false if the exception indicates there is a serious problem with the 2323 * commit operation and the environment should be invalidated. 2324 */ 2325 protected boolean propagatePostCommitException(DatabaseException postCommitException)2326 propagatePostCommitException(DatabaseException postCommitException) { 2327 return false; 2328 } 2329 2330 /** 2331 * Use the marker Sets to record whether this is the first time we've see 2332 * this logical node. 2333 */ firstInstance(Set<Long> seenLsns, Set<CompareSlot> seenSlots, UndoReader undo)2334 private boolean firstInstance(Set<Long> seenLsns, 2335 Set<CompareSlot> seenSlots, 2336 UndoReader undo) { 2337 final LNLogEntry<?> undoEntry = undo.logEntry; 2338 final long abortLsn1 = undoEntry.getAbortLsn(); 2339 if (abortLsn1 != DbLsn.NULL_LSN) { 2340 return seenLsns.add(abortLsn1); 2341 } 2342 final CompareSlot slot = new CompareSlot(undo.db, undoEntry); 2343 return seenSlots.add(slot); 2344 } 2345 2346 /** 2347 * Accumulates the set of databases for which transaction commit/abort 2348 * triggers must be run. 2349 * 2350 * @param dbImpl the database that associated with the trigger 2351 */ noteTriggerDb(DatabaseImpl dbImpl)2352 public void noteTriggerDb(DatabaseImpl dbImpl) { 2353 if (triggerDbs == null) { 2354 triggerDbs = 2355 Collections.synchronizedSet(new HashSet<DatabaseImpl>()); 2356 } 2357 triggerDbs.add(dbImpl); 2358 } 2359 2360 /** 2361 * Returns the set of databases for which transaction commit/abort 2362 * triggers must be run. Returns Null if no triggers need to be run. 2363 */ getTriggerDbs()2364 public Set<DatabaseImpl> getTriggerDbs() { 2365 return triggerDbs; 2366 } 2367 2368 /** Get the set of lock ids owned by this transaction */ getWriteLockIds()2369 public Set<Long> getWriteLockIds() { 2370 if (writeInfo == null) { 2371 Set<Long> empty = Collections.emptySet(); 2372 return empty; 2373 } 2374 2375 return writeInfo.keySet(); 2376 } 2377 2378 /* For unit tests. */ getReadLockIds()2379 public Set<Long> getReadLockIds() { 2380 if (readLocks == null) { 2381 return new HashSet<Long>(); 2382 } 2383 return new HashSet<Long>(readLocks); 2384 } 2385 getEnvironmentImpl()2386 public EnvironmentImpl getEnvironmentImpl() { 2387 return envImpl; 2388 } 2389 setTransaction(Transaction transaction)2390 public void setTransaction(Transaction transaction) { 2391 this.transaction = transaction; 2392 } 2393 2394 @Override getTransaction()2395 public Transaction getTransaction() { 2396 return (transaction != null) ? 2397 transaction : 2398 (transaction = new AutoTransaction(this)); 2399 } 2400 2401 private static class AutoTransaction extends Transaction { 2402 AutoTransaction(Txn txn)2403 protected AutoTransaction(Txn txn) { 2404 /* AutoTransactions do not have a convenient environment handle. */ 2405 super(txn.getEnvironmentImpl().getInternalEnvHandle(), txn); 2406 } 2407 2408 @Override commit()2409 public synchronized void commit() 2410 throws DatabaseException { 2411 2412 EnvironmentFailureException.unexpectedState 2413 ("commit() not permitted on an auto transaction"); 2414 } 2415 2416 @Override commit(@uppressWarningsR) Durability durability)2417 public synchronized void commit 2418 (@SuppressWarnings("unused") Durability durability) { 2419 EnvironmentFailureException.unexpectedState 2420 ("commit() not permitted on an auto transaction"); 2421 } 2422 2423 @Override commitNoSync()2424 public synchronized void commitNoSync() 2425 throws DatabaseException { 2426 2427 EnvironmentFailureException.unexpectedState 2428 ("commit() not permitted on an auto transaction"); 2429 } 2430 2431 @Override commitWriteNoSync()2432 public synchronized void commitWriteNoSync() 2433 throws DatabaseException { 2434 2435 EnvironmentFailureException.unexpectedState 2436 ("commit() not permitted on an auto transaction"); 2437 } 2438 2439 @Override abort()2440 public synchronized void abort() 2441 throws DatabaseException { 2442 2443 EnvironmentFailureException.unexpectedState 2444 ("abort() not permitted on an auto transaction"); 2445 } 2446 } 2447 getUndoDatabases()2448 public Map<DatabaseId, DatabaseImpl> getUndoDatabases() { 2449 return undoDatabases; 2450 } 2451 2452 /** 2453 * Txn freezing is used to prevent changes to transaction lock contents. A 2454 * frozen transaction should ignore any transaction commit/abort 2455 * requests. This is used only by MasterTxns, as a way of holding a 2456 * transaction stable while cloning it to serve as a ReplayTxn during 2457 * master->replica transitions. 2458 * @param isCommit true if called by commit. 2459 */ checkIfFrozen(boolean isCommit)2460 protected void checkIfFrozen(boolean isCommit) 2461 throws DatabaseException { 2462 return; 2463 } 2464 2465 /* 2466 * Used when creating a subset of MasterTxns. Using an explicit method 2467 * like this rather than checking class types insulates us from any 2468 * assumptions about the class hierarchy. 2469 */ isMasterTxn()2470 public boolean isMasterTxn() { 2471 return false; 2472 } 2473 } 2474