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