0001: /*-
0002: * See the file LICENSE for redistribution information.
0003: *
0004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
0005: *
0006: * $Id: Txn.java,v 1.148.2.9 2008/01/07 15:14:17 cwl Exp $
0007: */
0008:
0009: package com.sleepycat.je.txn;
0010:
0011: import java.nio.ByteBuffer;
0012: import java.util.ArrayList;
0013: import java.util.HashMap;
0014: import java.util.HashSet;
0015: import java.util.Iterator;
0016: import java.util.List;
0017: import java.util.Map;
0018: import java.util.Set;
0019: import java.util.logging.Level;
0020: import java.util.logging.Logger;
0021:
0022: import javax.transaction.xa.XAResource;
0023: import javax.transaction.xa.Xid;
0024:
0025: import com.sleepycat.je.Database;
0026: import com.sleepycat.je.DatabaseException;
0027: import com.sleepycat.je.DbInternal;
0028: import com.sleepycat.je.LockStats;
0029: import com.sleepycat.je.RunRecoveryException;
0030: import com.sleepycat.je.TransactionConfig;
0031: import com.sleepycat.je.dbi.CursorImpl;
0032: import com.sleepycat.je.dbi.DatabaseId;
0033: import com.sleepycat.je.dbi.DatabaseImpl;
0034: import com.sleepycat.je.dbi.EnvironmentImpl;
0035: import com.sleepycat.je.dbi.MemoryBudget;
0036: import com.sleepycat.je.log.LogEntryType;
0037: import com.sleepycat.je.log.LogManager;
0038: import com.sleepycat.je.log.LogUtils;
0039: import com.sleepycat.je.log.Loggable;
0040: import com.sleepycat.je.log.entry.LNLogEntry;
0041: import com.sleepycat.je.log.entry.SingleItemEntry;
0042: import com.sleepycat.je.recovery.RecoveryManager;
0043: import com.sleepycat.je.tree.LN;
0044: import com.sleepycat.je.tree.TreeLocation;
0045: import com.sleepycat.je.utilint.DbLsn;
0046: import com.sleepycat.je.utilint.Tracer;
0047:
0048: /**
0049: * A Txn is one that's created by a call to Environment.txnBegin. This class
0050: * must support multithreaded use.
0051: */
0052: public class Txn extends Locker implements Loggable {
0053: public static final byte TXN_NOSYNC = 0;
0054: public static final byte TXN_WRITE_NOSYNC = 1;
0055: public static final byte TXN_SYNC = 2;
0056:
0057: private static final String DEBUG_NAME = Txn.class.getName();
0058:
0059: private byte txnState;
0060:
0061: /*
0062: * Cursors opened under this txn. Implemented as a simple linked list to
0063: * conserve on memory.
0064: */
0065: private CursorImpl cursorSet;
0066:
0067: /* txnState bits. */
0068: private static final byte USABLE = 0;
0069: private static final byte CLOSED = 1;
0070: private static final byte ONLY_ABORTABLE = 2;
0071: private static final byte STATE_BITS = 3;
0072: /* Set if prepare() has been called on this transaction. */
0073: private static final byte IS_PREPARED = 4;
0074: /* Set if xa_end(TMSUSPEND) has been called on this transaction. */
0075: private static final byte XA_SUSPENDED = 8;
0076:
0077: /*
0078: * A Txn can be used by multiple threads. Modification to the read and
0079: * write lock collections is done by synchronizing on the txn.
0080: */
0081: private Set readLocks; // Set<Long> (nodeIds)
0082: private Map writeInfo; // key=nodeid, data = WriteLockInfo
0083:
0084: private final int READ_LOCK_OVERHEAD = MemoryBudget.HASHSET_ENTRY_OVERHEAD;
0085: private final int WRITE_LOCK_OVERHEAD = MemoryBudget.HASHMAP_ENTRY_OVERHEAD
0086: + MemoryBudget.WRITE_LOCKINFO_OVERHEAD;
0087:
0088: /*
0089: * We have to keep a set of DatabaseCleanupInfo objects so after
0090: * commit or abort of Environment.truncateDatabase() or
0091: * Environment.removeDatabase(), we can appropriately purge the
0092: * unneeded MapLN and DatabaseImpl.
0093: * Synchronize access to this set on this object.
0094: */
0095: private Set deletedDatabases;
0096:
0097: /*
0098: * We need a map of the latest databaseImpl objects to drive the undo
0099: * during an abort, because it's too hard to look up the database object in
0100: * the mapping tree. (The normal code paths want to take locks, add
0101: * cursors, etc.
0102: */
0103: private Map undoDatabases;
0104:
0105: /* Last LSN logged for this transaction. */
0106: private long lastLoggedLsn = DbLsn.NULL_LSN;
0107:
0108: /*
0109: * First LSN logged for this transaction -- used for keeping track of the
0110: * first active LSN point, for checkpointing. This field is not persistent.
0111: */
0112: private long firstLoggedLsn = DbLsn.NULL_LSN;
0113:
0114: /* Whether to flush and sync on commit by default. */
0115: private byte defaultFlushSyncBehavior;
0116:
0117: /* Whether to use Serializable isolation (prevent phantoms). */
0118: private boolean serializableIsolation;
0119:
0120: /* Whether to use Read-Committed isolation. */
0121: private boolean readCommittedIsolation;
0122:
0123: /*
0124: * In-memory size, in bytes. A Txn tracks the memory needed for itself and
0125: * the readlock, writeInfo, undoDatabases, and deletedDatabases
0126: * collections, including the cost of each collection entry. However, the
0127: * actual Lock object memory cost is maintained within the Lock class.
0128: */
0129: private int inMemorySize;
0130:
0131: /*
0132: * accumluted memory budget delta. Once this exceeds
0133: * ACCUMULATED_LIMIT we inform the MemoryBudget that a change
0134: * has occurred.
0135: */
0136: private int accumulatedDelta = 0;
0137:
0138: /*
0139: * Max allowable accumulation of memory budget changes before MemoryBudget
0140: * should be updated. This allows for consolidating multiple calls to
0141: * updateXXXMemoryBudget() into one call. Not declared final so that unit
0142: * tests can modify this. See SR 12273.
0143: */
0144: public static int ACCUMULATED_LIMIT = 10000;
0145:
0146: /**
0147: * Create a transaction from Environment.txnBegin.
0148: */
0149: public Txn(EnvironmentImpl envImpl, TransactionConfig config)
0150: throws DatabaseException {
0151:
0152: /*
0153: * Initialize using the config but don't hold a reference to it, since
0154: * it has not been cloned.
0155: */
0156: super (envImpl, config.getReadUncommitted(), config.getNoWait());
0157: init(envImpl, config);
0158: }
0159:
0160: public Txn(EnvironmentImpl envImpl, TransactionConfig config,
0161: long id) throws DatabaseException {
0162:
0163: /*
0164: * Initialize using the config but don't hold a reference to it, since
0165: * it has not been cloned.
0166: */
0167: super (envImpl, config.getReadUncommitted(), config.getNoWait());
0168: init(envImpl, config);
0169:
0170: this .id = id;
0171: }
0172:
0173: private void init(EnvironmentImpl envImpl, TransactionConfig config)
0174: throws DatabaseException {
0175:
0176: serializableIsolation = config.getSerializableIsolation();
0177: readCommittedIsolation = config.getReadCommitted();
0178:
0179: /*
0180: * Figure out what we should do on commit. TransactionConfig could be
0181: * set with conflicting values; take the most stringent ones first.
0182: * All environment level defaults were applied by the caller.
0183: *
0184: * ConfigSync ConfigWriteNoSync ConfigNoSync default
0185: * 0 0 0 sync
0186: * 0 0 1 nosync
0187: * 0 1 0 write nosync
0188: * 0 1 1 write nosync
0189: * 1 0 0 sync
0190: * 1 0 1 sync
0191: * 1 1 0 sync
0192: * 1 1 1 sync
0193: */
0194: if (config.getSync()) {
0195: defaultFlushSyncBehavior = TXN_SYNC;
0196: } else if (config.getWriteNoSync()) {
0197: defaultFlushSyncBehavior = TXN_WRITE_NOSYNC;
0198: } else if (config.getNoSync()) {
0199: defaultFlushSyncBehavior = TXN_NOSYNC;
0200: } else {
0201: defaultFlushSyncBehavior = TXN_SYNC;
0202: }
0203:
0204: lastLoggedLsn = DbLsn.NULL_LSN;
0205: firstLoggedLsn = DbLsn.NULL_LSN;
0206:
0207: txnState = USABLE;
0208:
0209: /*
0210: * Note: readLocks, writeInfo, undoDatabases, deleteDatabases are
0211: * initialized lazily in order to conserve memory. WriteInfo and
0212: * undoDatabases are treated as a package deal, because they are both
0213: * only needed if a transaction does writes.
0214: *
0215: * When a lock is added to this transaction, we add the collection
0216: * entry overhead to the memory cost, but don't add the lock
0217: * itself. That's taken care of by the Lock class.
0218: */
0219: updateMemoryUsage(MemoryBudget.TXN_OVERHEAD);
0220:
0221: this .envImpl.getTxnManager().registerTxn(this );
0222: }
0223:
0224: /**
0225: * Constructor for reading from log.
0226: */
0227: public Txn() {
0228: lastLoggedLsn = DbLsn.NULL_LSN;
0229: }
0230:
0231: /**
0232: * UserTxns get a new unique id for each instance.
0233: */
0234: protected long generateId(TxnManager txnManager) {
0235: return txnManager.incTxnId();
0236: }
0237:
0238: /**
0239: * Access to last LSN.
0240: */
0241: long getLastLsn() {
0242: return lastLoggedLsn;
0243: }
0244:
0245: public boolean getPrepared() {
0246: return (txnState & IS_PREPARED) != 0;
0247: }
0248:
0249: public void setPrepared(boolean prepared) {
0250: if (prepared) {
0251: txnState |= IS_PREPARED;
0252: } else {
0253: txnState &= ~IS_PREPARED;
0254: }
0255: }
0256:
0257: public void setSuspended(boolean suspended) {
0258: if (suspended) {
0259: txnState |= XA_SUSPENDED;
0260: } else {
0261: txnState &= ~XA_SUSPENDED;
0262: }
0263: }
0264:
0265: public boolean isSuspended() {
0266: return (txnState & XA_SUSPENDED) != 0;
0267: }
0268:
0269: /**
0270: * Gets a lock on this nodeId and, if it is a write lock, saves an abort
0271: * LSN. Caller will set the abortLsn later, after the write lock has been
0272: * obtained.
0273: *
0274: * @see Locker#lockInternal
0275: * @Override
0276: */
0277: LockResult lockInternal(long nodeId, LockType lockType,
0278: boolean noWait, DatabaseImpl database)
0279: throws DatabaseException {
0280:
0281: long timeout = 0;
0282: boolean useNoWait = noWait || defaultNoWait;
0283: synchronized (this ) {
0284: checkState(false);
0285: if (!useNoWait) {
0286: timeout = lockTimeOutMillis;
0287: }
0288: }
0289:
0290: /* Ask for the lock. */
0291: LockGrantType grant = lockManager.lock(nodeId, this , lockType,
0292: timeout, useNoWait, database);
0293:
0294: WriteLockInfo info = null;
0295: if (writeInfo != null) {
0296: if (grant != LockGrantType.DENIED && lockType.isWriteLock()) {
0297: synchronized (this ) {
0298: info = (WriteLockInfo) writeInfo.get(new Long(
0299: nodeId));
0300: /* Save the latest version of this database for undoing. */
0301: undoDatabases.put(database.getId(), database);
0302: }
0303: }
0304: }
0305:
0306: return new LockResult(grant, info);
0307: }
0308:
0309: public int prepare(Xid xid) throws DatabaseException {
0310:
0311: if ((txnState & IS_PREPARED) != 0) {
0312: throw new DatabaseException(
0313: "prepare() has already been called for Transaction "
0314: + id + ".");
0315: }
0316: synchronized (this ) {
0317: checkState(false);
0318: if (checkCursorsForClose()) {
0319: throw new DatabaseException(
0320: "Transaction "
0321: + id
0322: + " prepare failed because there were open cursors.");
0323: }
0324:
0325: setPrepared(true);
0326: if (writeInfo == null) {
0327: return XAResource.XA_RDONLY;
0328: }
0329:
0330: SingleItemEntry prepareEntry = new SingleItemEntry(
0331: LogEntryType.LOG_TXN_PREPARE, new TxnPrepare(id,
0332: xid));
0333: /* Flush required. */
0334: LogManager logManager = envImpl.getLogManager();
0335: logManager.logForceFlush(prepareEntry, true); // sync required
0336: }
0337: return XAResource.XA_OK;
0338: }
0339:
0340: public void commit(Xid xid) throws DatabaseException {
0341:
0342: commit(TXN_SYNC);
0343: envImpl.getTxnManager().unRegisterXATxn(xid, true);
0344: return;
0345: }
0346:
0347: public void abort(Xid xid) throws DatabaseException {
0348:
0349: abort(true);
0350: envImpl.getTxnManager().unRegisterXATxn(xid, false);
0351: return;
0352: }
0353:
0354: /**
0355: * Call commit() with the default sync configuration property.
0356: */
0357: public long commit() throws DatabaseException {
0358:
0359: return commit(defaultFlushSyncBehavior);
0360: }
0361:
0362: /**
0363: * Commit this transaction
0364: * 1. Releases read locks
0365: * 2. Writes a txn commit record into the log
0366: * 3. Flushes the log to disk.
0367: * 4. Add deleted LN info to IN compressor queue
0368: * 5. Release all write locks
0369: *
0370: * If any step of this fails, we must convert this transaction to an abort.
0371: */
0372: public long commit(byte flushSyncBehavior) throws DatabaseException {
0373:
0374: try {
0375: long commitLsn = DbLsn.NULL_LSN;
0376: synchronized (this ) {
0377: checkState(false);
0378: if (checkCursorsForClose()) {
0379: throw new DatabaseException(
0380: "Transaction "
0381: + id
0382: + " commit failed because there were open cursors.");
0383: }
0384:
0385: /*
0386: * Save transferred write locks, if any. Their abort LSNs are
0387: * counted as obsolete further below. Create the list lazily
0388: * to avoid creating it in the normal case (no handle locks).
0389: */
0390: List transferredWriteLockInfo = null;
0391:
0392: /* Transfer handle locks to their owning handles. */
0393: if (handleLockToHandleMap != null) {
0394: Iterator handleLockIter = handleLockToHandleMap
0395: .entrySet().iterator();
0396: while (handleLockIter.hasNext()) {
0397: Map.Entry entry = (Map.Entry) handleLockIter
0398: .next();
0399: Long nodeId = (Long) entry.getKey();
0400: if (writeInfo != null) {
0401: WriteLockInfo info = (WriteLockInfo) writeInfo
0402: .get(nodeId);
0403: if (info != null) {
0404: if (transferredWriteLockInfo == null) {
0405: transferredWriteLockInfo = new ArrayList();
0406: }
0407: transferredWriteLockInfo.add(info);
0408: }
0409: }
0410: transferHandleLockToHandleSet(nodeId,
0411: (Set) entry.getValue());
0412: }
0413: }
0414:
0415: LogManager logManager = envImpl.getLogManager();
0416:
0417: /*
0418: * Release all read locks, clear lock collection. Optimize for
0419: * the case where there are no read locks.
0420: */
0421: int numReadLocks = clearReadLocks();
0422:
0423: /*
0424: * Log the commit if we ever held any write locks. Note that
0425: * with dbhandle write locks, we may have held the write lock
0426: * but then had it transferred away.
0427: */
0428: int numWriteLocks = 0;
0429: if (writeInfo != null) {
0430: numWriteLocks = writeInfo.size();
0431: SingleItemEntry commitEntry = new SingleItemEntry(
0432: LogEntryType.LOG_TXN_COMMIT, new TxnCommit(
0433: id, lastLoggedLsn));
0434: if (flushSyncBehavior == TXN_SYNC) {
0435: /* Flush and sync required. */
0436: commitLsn = logManager.logForceFlush(
0437: commitEntry, true);
0438: } else if (flushSyncBehavior == TXN_WRITE_NOSYNC) {
0439: /* Flush but no sync required. */
0440: commitLsn = logManager.logForceFlush(
0441: commitEntry, false);
0442: } else {
0443: /* No flush, no sync required. */
0444: commitLsn = logManager.log(commitEntry);
0445: }
0446:
0447: /*
0448: * Set database state for deletes before releasing any
0449: * write locks.
0450: */
0451: setDeletedDatabaseState(true);
0452:
0453: /*
0454: * Used to prevent double counting abortLNS if there is
0455: * more then one node with the same abortLSN in this txn.
0456: * Two nodes with the same abortLSN occur when a deleted
0457: * slot is reused in the same txn.
0458: */
0459: Set alreadyCountedLsnSet = new HashSet();
0460:
0461: /* Release all write locks, clear lock collection. */
0462: Iterator iter = writeInfo.entrySet().iterator();
0463: while (iter.hasNext()) {
0464: Map.Entry entry = (Map.Entry) iter.next();
0465: Long nodeId = (Long) entry.getKey();
0466: lockManager.release(nodeId.longValue(), this );
0467: countWriteAbortLSN((WriteLockInfo) entry
0468: .getValue(), alreadyCountedLsnSet);
0469: }
0470: writeInfo = null;
0471:
0472: /* Count obsolete LSNs for transferred write locks. */
0473: if (transferredWriteLockInfo != null) {
0474: for (int i = 0; i < transferredWriteLockInfo
0475: .size(); i += 1) {
0476: WriteLockInfo info = (WriteLockInfo) transferredWriteLockInfo
0477: .get(i);
0478: countWriteAbortLSN(info,
0479: alreadyCountedLsnSet);
0480: }
0481: }
0482:
0483: /* Unload delete info, but don't wake up the compressor. */
0484: if ((deleteInfo != null) && deleteInfo.size() > 0) {
0485: envImpl.addToCompressorQueue(deleteInfo
0486: .values(), false); // don't wakeup
0487: deleteInfo.clear();
0488: }
0489: }
0490:
0491: traceCommit(numWriteLocks, numReadLocks);
0492: }
0493:
0494: /*
0495: * Purge any databaseImpls not needed as a result of the commit.
0496: * Be sure to do this outside the synchronization block, to avoid
0497: * conflict w/checkpointer.
0498: */
0499: cleanupDatabaseImpls(true);
0500:
0501: /*
0502: * Unregister this txn. Be sure to do this outside the
0503: * synchronization block, to avoid conflict w/checkpointer.
0504: */
0505: close(true);
0506: return commitLsn;
0507: } catch (RunRecoveryException e) {
0508:
0509: /* May have received a thread interrupt. */
0510: throw e;
0511: } catch (Error e) {
0512: envImpl.invalidate(e);
0513: throw e;
0514: } catch (Exception t) {
0515:
0516: try {
0517:
0518: /*
0519: * If the exception thrown is a DatabaseException it indicates
0520: * that the write() call hit an IOException, probably out of
0521: * disk space, and attempted to rewrite all commit records as
0522: * abort records. Since the abort records are already
0523: * rewritten (or at least attempted to be rewritten), there is
0524: * no reason to have abort attempt to write an abort record
0525: * again. See [11271].
0526: */
0527: abortInternal(flushSyncBehavior == TXN_SYNC,
0528: !(t instanceof DatabaseException));
0529: Tracer.trace(envImpl, "Txn", "commit",
0530: "Commit of transaction " + id + " failed", t);
0531: } catch (Error e) {
0532: envImpl.invalidate(e);
0533: throw e;
0534: } catch (Exception abortT2) {
0535: throw new DatabaseException(
0536: "Failed while attempting to commit transaction "
0537: + id
0538: + ". The attempt to abort and clean up also failed. "
0539: + "The original exception seen from commit = "
0540: + t.getMessage()
0541: + " The exception from the cleanup = "
0542: + abortT2.getMessage(), t);
0543: }
0544:
0545: /* Now throw an exception that shows the commit problem. */
0546: throw new DatabaseException(
0547: "Failed while attempting to commit transaction "
0548: + id
0549: + ", aborted instead. Original exception = "
0550: + t.getMessage(), t);
0551: }
0552: }
0553:
0554: /**
0555: * Count the abortLSN as obsolete. Do not count if a slot with a deleted
0556: * LN was reused (abortKnownDeleted), to avoid double counting. And count
0557: * each abortLSN only once.
0558: */
0559: private void countWriteAbortLSN(WriteLockInfo info,
0560: Set alreadyCountedLsnSet) throws DatabaseException {
0561:
0562: if (info.abortLsn != DbLsn.NULL_LSN && !info.abortKnownDeleted) {
0563: Long longLsn = new Long(info.abortLsn);
0564: if (!alreadyCountedLsnSet.contains(longLsn)) {
0565: envImpl.getLogManager().countObsoleteNode(
0566: info.abortLsn, null, info.abortLogSize);
0567: alreadyCountedLsnSet.add(longLsn);
0568: }
0569: }
0570: }
0571:
0572: /**
0573: * Abort this transaction. Steps are:
0574: * 1. Release LN read locks.
0575: * 2. Write a txn abort entry to the log. This is only for log
0576: * file cleaning optimization and there's no need to guarantee a
0577: * flush to disk.
0578: * 3. Find the last LN log entry written for this txn, and use that
0579: * to traverse the log looking for nodes to undo. For each node,
0580: * use the same undo logic as recovery to rollback the transaction. Note
0581: * that we walk the log in order to undo in reverse order of the
0582: * actual operations. For example, suppose the txn did this:
0583: * delete K1/D1 (in LN 10)
0584: * create K1/D1 (in LN 20)
0585: * If we process LN10 before LN 20, we'd inadvertently create a
0586: * duplicate tree of "K1", which would be fatal for the mapping tree.
0587: * 4. Release the write lock for this LN.
0588: */
0589: public long abort(boolean forceFlush) throws DatabaseException {
0590:
0591: return abortInternal(forceFlush, true);
0592: }
0593:
0594: private long abortInternal(boolean forceFlush,
0595: boolean writeAbortRecord) throws DatabaseException {
0596:
0597: try {
0598: int numReadLocks;
0599: int numWriteLocks;
0600: long abortLsn;
0601:
0602: synchronized (this ) {
0603: checkState(true);
0604:
0605: /* Log the abort. */
0606: SingleItemEntry abortEntry = new SingleItemEntry(
0607: LogEntryType.LOG_TXN_ABORT, new TxnAbort(id,
0608: lastLoggedLsn));
0609: abortLsn = DbLsn.NULL_LSN;
0610: if (writeInfo != null) {
0611: if (writeAbortRecord) {
0612: if (forceFlush) {
0613: abortLsn = envImpl.getLogManager()
0614: .logForceFlush(abortEntry, true);
0615: } else {
0616: abortLsn = envImpl.getLogManager().log(
0617: abortEntry);
0618: }
0619: }
0620: }
0621:
0622: /* Undo the changes. */
0623: undo();
0624:
0625: /*
0626: * Release all read locks after the undo (since the undo may
0627: * need to read in mapLNs).
0628: */
0629: numReadLocks = (readLocks == null) ? 0
0630: : clearReadLocks();
0631:
0632: /*
0633: * Set database state for deletes before releasing any write
0634: * locks.
0635: */
0636: setDeletedDatabaseState(false);
0637:
0638: /* Throw away write lock collection. */
0639: numWriteLocks = (writeInfo == null) ? 0
0640: : clearWriteLocks();
0641:
0642: /*
0643: * Let the delete related info (binreferences and dbs) get
0644: * gc'ed. Don't explicitly iterate and clear -- that's far less
0645: * efficient, gives GC wrong input.
0646: */
0647: deleteInfo = null;
0648: }
0649:
0650: /*
0651: * Purge any databaseImpls not needed as a result of the abort. Be
0652: * sure to do this outside the synchronization block, to avoid
0653: * conflict w/checkpointer.
0654: */
0655: cleanupDatabaseImpls(false);
0656:
0657: synchronized (this ) {
0658: boolean openCursors = checkCursorsForClose();
0659: Tracer.trace(Level.FINE, envImpl, "Abort:id = " + id
0660: + " numWriteLocks= " + numWriteLocks
0661: + " numReadLocks= " + numReadLocks
0662: + " openCursors= " + openCursors);
0663: if (openCursors) {
0664: throw new DatabaseException("Transaction " + id
0665: + " detected open cursors while aborting");
0666: }
0667: /* Unload any db handles protected by this txn. */
0668: if (handleToHandleLockMap != null) {
0669: Iterator handleIter = handleToHandleLockMap
0670: .keySet().iterator();
0671: while (handleIter.hasNext()) {
0672: Database handle = (Database) handleIter.next();
0673: DbInternal.dbInvalidate(handle);
0674: }
0675: }
0676:
0677: return abortLsn;
0678: }
0679: } finally {
0680:
0681: /*
0682: * Unregister this txn, must be done outside synchronization block
0683: * to avoid conflict w/checkpointer.
0684: */
0685: close(false);
0686: }
0687: }
0688:
0689: /**
0690: * Rollback the changes to this txn's write locked nodes.
0691: */
0692: private void undo() throws DatabaseException {
0693:
0694: Long nodeId = null;
0695: long undoLsn = lastLoggedLsn;
0696: LogManager logManager = envImpl.getLogManager();
0697:
0698: try {
0699: Set alreadyUndone = new HashSet();
0700: TreeLocation location = new TreeLocation();
0701: while (undoLsn != DbLsn.NULL_LSN) {
0702:
0703: LNLogEntry undoEntry = (LNLogEntry) logManager
0704: .getLogEntry(undoLsn);
0705: LN undoLN = undoEntry.getLN();
0706: nodeId = new Long(undoLN.getNodeId());
0707:
0708: /*
0709: * Only process this if this is the first time we've seen this
0710: * node. All log entries for a given node have the same
0711: * abortLsn, so we don't need to undo it multiple times.
0712: */
0713: if (!alreadyUndone.contains(nodeId)) {
0714: alreadyUndone.add(nodeId);
0715: DatabaseId dbId = undoEntry.getDbId();
0716: DatabaseImpl db = (DatabaseImpl) undoDatabases
0717: .get(dbId);
0718: undoLN.postFetchInit(db, undoLsn);
0719: long abortLsn = undoEntry.getAbortLsn();
0720: boolean abortKnownDeleted = undoEntry
0721: .getAbortKnownDeleted();
0722: try {
0723: RecoveryManager.undo(Level.FINER, db, location,
0724: undoLN, undoEntry.getKey(), undoEntry
0725: .getDupKey(), undoLsn,
0726: abortLsn, abortKnownDeleted, null,
0727: false);
0728: } finally {
0729: if (location.bin != null) {
0730: location.bin.releaseLatchIfOwner();
0731: }
0732: }
0733:
0734: /*
0735: * The LN undone is counted as obsolete if it is not a
0736: * deleted LN. Deleted LNs are counted as obsolete when
0737: * they are logged.
0738: */
0739: if (!undoLN.isDeleted()) {
0740: logManager.countObsoleteNode(undoLsn, null, // type
0741: undoLN.getLastLoggedSize());
0742: }
0743: }
0744:
0745: /* Move on to the previous log entry for this txn. */
0746: undoLsn = undoEntry.getUserTxn().getLastLsn();
0747: }
0748: } catch (RuntimeException e) {
0749: throw new DatabaseException("Txn undo for node=" + nodeId
0750: + " LSN=" + DbLsn.getNoFormatString(undoLsn), e);
0751: } catch (DatabaseException e) {
0752: Tracer.trace(envImpl, "Txn", "undo", "for node=" + nodeId
0753: + " LSN=" + DbLsn.getNoFormatString(undoLsn), e);
0754: throw e;
0755: }
0756: }
0757:
0758: private int clearWriteLocks() throws DatabaseException {
0759:
0760: int numWriteLocks = writeInfo.size();
0761:
0762: /* Release all write locks, clear lock collection. */
0763: Iterator iter = writeInfo.entrySet().iterator();
0764: while (iter.hasNext()) {
0765: Map.Entry entry = (Map.Entry) iter.next();
0766: Long nodeId = (Long) entry.getKey();
0767: lockManager.release(nodeId.longValue(), this );
0768: }
0769: writeInfo = null;
0770: return numWriteLocks;
0771: }
0772:
0773: private int clearReadLocks() throws DatabaseException {
0774:
0775: int numReadLocks = 0;
0776: if (readLocks != null) {
0777: numReadLocks = readLocks.size();
0778: Iterator iter = readLocks.iterator();
0779: while (iter.hasNext()) {
0780: Long rLock = (Long) iter.next();
0781: lockManager.release(rLock.longValue(), this );
0782: }
0783: readLocks = null;
0784: }
0785: return numReadLocks;
0786: }
0787:
0788: /**
0789: * Called by the recovery manager when logging a transaction aware object.
0790: * This method is synchronized by the caller, by being called within the
0791: * log latch. Record the last LSN for this transaction, to create the
0792: * transaction chain, and also record the LSN in the write info for abort
0793: * logic.
0794: */
0795: public void addLogInfo(long lastLsn) throws DatabaseException {
0796:
0797: /* Save the last LSN for maintaining the transaction LSN chain. */
0798: lastLoggedLsn = lastLsn;
0799:
0800: /* Save handle to LSN for aborts. */
0801: synchronized (this ) {
0802:
0803: /*
0804: * If this is the first LSN, save it for calculating the first LSN
0805: * of any active txn, for checkpointing.
0806: */
0807: if (firstLoggedLsn == DbLsn.NULL_LSN) {
0808: firstLoggedLsn = lastLsn;
0809: }
0810: }
0811: }
0812:
0813: /**
0814: * @return first logged LSN, to aid recovery rollback.
0815: */
0816: long getFirstActiveLsn() throws DatabaseException {
0817:
0818: synchronized (this ) {
0819: return firstLoggedLsn;
0820: }
0821: }
0822:
0823: /**
0824: * @param dbImpl databaseImpl to remove
0825: * @param deleteAtCommit true if this databaseImpl should be cleaned on
0826: * commit, false if it should be cleaned on abort.
0827: * @param mb environment memory budget.
0828: */
0829: public void markDeleteAtTxnEnd(DatabaseImpl dbImpl,
0830: boolean deleteAtCommit) throws DatabaseException {
0831:
0832: synchronized (this ) {
0833: int delta = 0;
0834: if (deletedDatabases == null) {
0835: deletedDatabases = new HashSet();
0836: delta += MemoryBudget.HASHSET_OVERHEAD;
0837: }
0838:
0839: deletedDatabases.add(new DatabaseCleanupInfo(dbImpl,
0840: deleteAtCommit));
0841: delta += MemoryBudget.HASHSET_ENTRY_OVERHEAD
0842: + MemoryBudget.OBJECT_OVERHEAD;
0843: updateMemoryUsage(delta);
0844:
0845: /* releaseDb will be called by cleanupDatabaseImpls. */
0846: }
0847: }
0848:
0849: /*
0850: * Leftover databaseImpls that are a by-product of database operations like
0851: * removeDatabase(), truncateDatabase() will be deleted after the write
0852: * locks are released. However, do set the database state appropriately
0853: * before the locks are released.
0854: */
0855: private void setDeletedDatabaseState(boolean isCommit)
0856: throws DatabaseException {
0857:
0858: if (deletedDatabases != null) {
0859: Iterator iter = deletedDatabases.iterator();
0860: while (iter.hasNext()) {
0861: DatabaseCleanupInfo info = (DatabaseCleanupInfo) iter
0862: .next();
0863: if (info.deleteAtCommit == isCommit) {
0864: info.dbImpl.startDeleteProcessing();
0865: }
0866: }
0867: }
0868: }
0869:
0870: /**
0871: * Cleanup leftover databaseImpls that are a by-product of database
0872: * operations like removeDatabase(), truncateDatabase().
0873: *
0874: * This method must be called outside the synchronization on this txn,
0875: * because it calls releaseDeletedINs, which gets the TxnManager's
0876: * allTxns latch. The checkpointer also gets the allTxns latch, and within
0877: * that latch, needs to synchronize on individual txns, so we must avoid a
0878: * latching hiearchy conflict.
0879: */
0880: private void cleanupDatabaseImpls(boolean isCommit)
0881: throws DatabaseException {
0882:
0883: if (deletedDatabases != null) {
0884: /* Make a copy of the deleted databases while synchronized. */
0885: DatabaseCleanupInfo[] infoArray;
0886: synchronized (this ) {
0887: infoArray = new DatabaseCleanupInfo[deletedDatabases
0888: .size()];
0889: deletedDatabases.toArray(infoArray);
0890: }
0891: for (int i = 0; i < infoArray.length; i += 1) {
0892: DatabaseCleanupInfo info = infoArray[i];
0893: if (info.deleteAtCommit == isCommit) {
0894: /* releaseDb will be called by releaseDeletedINs. */
0895: info.dbImpl.releaseDeletedINs();
0896: } else {
0897: envImpl.releaseDb(info.dbImpl);
0898: }
0899: }
0900: deletedDatabases = null;
0901: }
0902: }
0903:
0904: /**
0905: * Add lock to the appropriate queue.
0906: */
0907: void addLock(Long nodeId, LockType type, LockGrantType grantStatus)
0908: throws DatabaseException {
0909:
0910: synchronized (this ) {
0911: int delta = 0;
0912: if (type.isWriteLock()) {
0913: if (writeInfo == null) {
0914: writeInfo = new HashMap();
0915: undoDatabases = new HashMap();
0916: delta += MemoryBudget.TWOHASHMAPS_OVERHEAD;
0917: }
0918:
0919: writeInfo.put(nodeId, new WriteLockInfo());
0920: delta += WRITE_LOCK_OVERHEAD;
0921:
0922: if ((grantStatus == LockGrantType.PROMOTION)
0923: || (grantStatus == LockGrantType.WAIT_PROMOTION)) {
0924: readLocks.remove(nodeId);
0925: delta -= READ_LOCK_OVERHEAD;
0926: }
0927: updateMemoryUsage(delta);
0928: } else {
0929: addReadLock(nodeId);
0930: }
0931: }
0932: }
0933:
0934: private void addReadLock(Long nodeId) {
0935: int delta = 0;
0936: if (readLocks == null) {
0937: readLocks = new HashSet();
0938: delta = MemoryBudget.HASHSET_OVERHEAD;
0939: }
0940:
0941: readLocks.add(nodeId);
0942: delta += READ_LOCK_OVERHEAD;
0943: updateMemoryUsage(delta);
0944: }
0945:
0946: /**
0947: * Remove the lock from the set owned by this transaction. If specified to
0948: * LockManager.release, the lock manager will call this when its releasing
0949: * a lock. Usually done because the transaction doesn't need to really keep
0950: * the lock, i.e for a deleted record.
0951: */
0952: void removeLock(long nodeId) throws DatabaseException {
0953:
0954: /*
0955: * We could optimize by passing the lock type so we know which
0956: * collection to look in. Be careful of demoted locks, which have
0957: * shifted collection.
0958: *
0959: * Don't bother updating memory utilization here -- we'll update at
0960: * transaction end.
0961: */
0962: synchronized (this ) {
0963: if ((readLocks != null)
0964: && readLocks.remove(new Long(nodeId))) {
0965: updateMemoryUsage(0 - READ_LOCK_OVERHEAD);
0966: } else if ((writeInfo != null)
0967: && (writeInfo.remove(new Long(nodeId)) != null)) {
0968: updateMemoryUsage(0 - WRITE_LOCK_OVERHEAD);
0969: }
0970: }
0971: }
0972:
0973: /**
0974: * A lock is being demoted. Move it from the write collection into the read
0975: * collection.
0976: */
0977: void moveWriteToReadLock(long nodeId, Lock lock) {
0978:
0979: boolean found = false;
0980: synchronized (this ) {
0981: if ((writeInfo != null)
0982: && (writeInfo.remove(new Long(nodeId)) != null)) {
0983: found = true;
0984: updateMemoryUsage(0 - WRITE_LOCK_OVERHEAD);
0985: }
0986:
0987: assert found : "Couldn't find lock for Node " + nodeId
0988: + " in writeInfo Map.";
0989: addReadLock(new Long(nodeId));
0990: }
0991: }
0992:
0993: private void updateMemoryUsage(int delta) {
0994: inMemorySize += delta;
0995: accumulatedDelta += delta;
0996: if (accumulatedDelta > ACCUMULATED_LIMIT
0997: || accumulatedDelta < -ACCUMULATED_LIMIT) {
0998: envImpl.getMemoryBudget().updateMiscMemoryUsage(
0999: accumulatedDelta);
1000: accumulatedDelta = 0;
1001: }
1002: }
1003:
1004: int getAccumulatedDelta() {
1005: return accumulatedDelta;
1006: }
1007:
1008: /**
1009: * @return true if this transaction created this node. We know that this
1010: * is true if the node is write locked and has a null abort LSN.
1011: */
1012: public boolean createdNode(long nodeId) throws DatabaseException {
1013:
1014: boolean created = false;
1015: synchronized (this ) {
1016: if (writeInfo != null) {
1017: WriteLockInfo info = (WriteLockInfo) writeInfo
1018: .get(new Long(nodeId));
1019: if (info != null) {
1020: created = info.createdThisTxn;
1021: }
1022: }
1023: }
1024: return created;
1025: }
1026:
1027: /**
1028: * @return the abortLsn for this node.
1029: */
1030: public long getAbortLsn(long nodeId) throws DatabaseException {
1031:
1032: WriteLockInfo info = null;
1033: synchronized (this ) {
1034: if (writeInfo != null) {
1035: info = (WriteLockInfo) writeInfo.get(new Long(nodeId));
1036: }
1037: }
1038:
1039: if (info == null) {
1040: return DbLsn.NULL_LSN;
1041: } else {
1042: return info.abortLsn;
1043: }
1044: }
1045:
1046: /**
1047: * @return the WriteLockInfo for this node.
1048: */
1049: public WriteLockInfo getWriteLockInfo(long nodeId)
1050: throws DatabaseException {
1051:
1052: WriteLockInfo info = WriteLockInfo.basicWriteLockInfo;
1053: synchronized (this ) {
1054: if (writeInfo != null) {
1055: info = (WriteLockInfo) writeInfo.get(new Long(nodeId));
1056: }
1057: }
1058:
1059: return info;
1060: }
1061:
1062: /**
1063: * Is always transactional.
1064: */
1065: public boolean isTransactional() {
1066: return true;
1067: }
1068:
1069: /**
1070: * Is serializable isolation if so configured.
1071: */
1072: public boolean isSerializableIsolation() {
1073: return serializableIsolation;
1074: }
1075:
1076: /**
1077: * Is read-committed isolation if so configured.
1078: */
1079: public boolean isReadCommittedIsolation() {
1080: return readCommittedIsolation;
1081: }
1082:
1083: /**
1084: * This is a transactional locker.
1085: */
1086: public Txn getTxnLocker() {
1087: return this ;
1088: }
1089:
1090: /**
1091: * Returns 'this', since this locker holds no non-transactional locks.
1092: */
1093: public Locker newNonTxnLocker() throws DatabaseException {
1094:
1095: return this ;
1096: }
1097:
1098: /**
1099: * This locker holds no non-transactional locks.
1100: */
1101: public void releaseNonTxnLocks() throws DatabaseException {
1102: }
1103:
1104: /**
1105: * Created transactions do nothing at the end of the operation.
1106: */
1107: public void operationEnd() throws DatabaseException {
1108: }
1109:
1110: /**
1111: * Created transactions do nothing at the end of the operation.
1112: */
1113: public void operationEnd(boolean operationOK)
1114: throws DatabaseException {
1115: }
1116:
1117: /**
1118: * Created transactions don't transfer locks until commit.
1119: */
1120: public void setHandleLockOwner(boolean ignore /*operationOK*/,
1121: Database dbHandle, boolean dbIsClosing)
1122: throws DatabaseException {
1123:
1124: if (dbIsClosing) {
1125:
1126: /*
1127: * If the Database handle is closing, take it out of the both the
1128: * handle lock map and the handle map. We don't need to do any
1129: * transfers at commit time, and we don't need to do any
1130: * invalidations at abort time.
1131: */
1132: Long handleLockId = (Long) handleToHandleLockMap
1133: .get(dbHandle);
1134: if (handleLockId != null) {
1135: Set dbHandleSet = (Set) handleLockToHandleMap
1136: .get(handleLockId);
1137: boolean removed = dbHandleSet.remove(dbHandle);
1138: assert removed : "Can't find " + dbHandle
1139: + " from dbHandleSet";
1140: if (dbHandleSet.size() == 0) {
1141: Object foo = handleLockToHandleMap
1142: .remove(handleLockId);
1143: assert (foo != null) : "Can't find " + handleLockId
1144: + " from handleLockIdtoHandleMap.";
1145: }
1146: }
1147:
1148: unregisterHandle(dbHandle);
1149:
1150: } else {
1151:
1152: /*
1153: * If the db is still open, make sure the db knows this txn is its
1154: * handle lock protector and that this txn knows it owns this db
1155: * handle.
1156: */
1157: if (dbHandle != null) {
1158: DbInternal.dbSetHandleLocker(dbHandle, this );
1159: }
1160: }
1161: }
1162:
1163: /**
1164: * Cursors operating under this transaction are added to the collection.
1165: */
1166: public void registerCursor(CursorImpl cursor)
1167: throws DatabaseException {
1168:
1169: synchronized (this ) {
1170: /* Add to the head of the list. */
1171: cursor.setLockerNext(cursorSet);
1172: if (cursorSet != null) {
1173: cursorSet.setLockerPrev(cursor);
1174: }
1175: cursorSet = cursor;
1176: }
1177: }
1178:
1179: /**
1180: * Remove a cursor from the collection.
1181: */
1182: public void unRegisterCursor(CursorImpl cursor)
1183: throws DatabaseException {
1184:
1185: synchronized (this ) {
1186: CursorImpl prev = cursor.getLockerPrev();
1187: CursorImpl next = cursor.getLockerNext();
1188: if (prev == null) {
1189: cursorSet = next;
1190: } else {
1191: prev.setLockerNext(next);
1192: }
1193:
1194: if (next != null) {
1195: next.setLockerPrev(prev);
1196: }
1197: cursor.setLockerPrev(null);
1198: cursor.setLockerNext(null);
1199: }
1200: }
1201:
1202: /**
1203: * @return true if this txn is willing to give up the handle lock to
1204: * another txn before this txn ends.
1205: */
1206: public boolean isHandleLockTransferrable() {
1207: return false;
1208: }
1209:
1210: /**
1211: * Check if all cursors associated with the txn are closed. If not, those
1212: * open cursors will be forcibly closed.
1213: * @return true if open cursors exist
1214: */
1215: private boolean checkCursorsForClose() throws DatabaseException {
1216:
1217: CursorImpl c = cursorSet;
1218: while (c != null) {
1219: if (!c.isClosed()) {
1220: return true;
1221: }
1222: c = c.getLockerNext();
1223: }
1224:
1225: return false;
1226: }
1227:
1228: /**
1229: * stats
1230: */
1231: public LockStats collectStats(LockStats stats)
1232: throws DatabaseException {
1233:
1234: synchronized (this ) {
1235: int nReadLocks = (readLocks == null) ? 0 : readLocks.size();
1236: stats.setNReadLocks(stats.getNReadLocks() + nReadLocks);
1237: int nWriteLocks = (writeInfo == null) ? 0 : writeInfo
1238: .size();
1239: stats.setNWriteLocks(stats.getNWriteLocks() + nWriteLocks);
1240: }
1241:
1242: return stats;
1243: }
1244:
1245: /**
1246: * Set the state of a transaction to ONLY_ABORTABLE.
1247: */
1248: public void setOnlyAbortable() {
1249: txnState &= ~STATE_BITS;
1250: txnState |= ONLY_ABORTABLE;
1251: }
1252:
1253: /**
1254: * Get the state of a transaction's ONLY_ABORTABLE.
1255: */
1256: public boolean getOnlyAbortable() {
1257: return (txnState & ONLY_ABORTABLE) != 0;
1258: }
1259:
1260: /**
1261: * Throw an exception if the transaction is not open.
1262: *
1263: * If calledByAbort is true, it means we're being called
1264: * from abort().
1265: *
1266: * Caller must invoke with "this" synchronized.
1267: */
1268: protected void checkState(boolean calledByAbort)
1269: throws DatabaseException {
1270:
1271: boolean ok = false;
1272: boolean onlyAbortable = false;
1273: byte state = (byte) (txnState & STATE_BITS);
1274: ok = (state == USABLE);
1275: onlyAbortable = (state == ONLY_ABORTABLE);
1276:
1277: if (!calledByAbort && onlyAbortable) {
1278:
1279: /*
1280: * It's ok for FindBugs to whine about id not being synchronized.
1281: */
1282: throw new DatabaseException("Transaction " + id
1283: + " must be aborted.");
1284: }
1285:
1286: if (ok || (calledByAbort && onlyAbortable)) {
1287: return;
1288: }
1289:
1290: /*
1291: * It's ok for FindBugs to whine about id not being synchronized.
1292: */
1293: throw new DatabaseException("Transaction " + id
1294: + " has been closed.");
1295: }
1296:
1297: /**
1298: */
1299: private void close(boolean isCommit) throws DatabaseException {
1300:
1301: synchronized (this ) {
1302: txnState &= ~STATE_BITS;
1303: txnState |= CLOSED;
1304: }
1305:
1306: /*
1307: * UnregisterTxn must be called outside the synchronization on this
1308: * txn, because it gets the TxnManager's allTxns latch. The
1309: * checkpointer also gets the allTxns latch, and within that latch,
1310: * needs to synchronize on individual txns, so we must avoid a latching
1311: * hiearchy conflict.
1312: */
1313: envImpl.getTxnManager().unRegisterTxn(this , isCommit);
1314: }
1315:
1316: /*
1317: * Log support
1318: */
1319:
1320: /**
1321: * @see Loggable#getLogSize
1322: */
1323: public int getLogSize() {
1324: return LogUtils.LONG_BYTES + // id
1325: LogUtils.LONG_BYTES; // lastLoggedLsn
1326: }
1327:
1328: /**
1329: * @see Loggable#writeToLog
1330: */
1331: /*
1332: * It's ok for FindBugs to whine about id not being synchronized.
1333: */
1334: public void writeToLog(ByteBuffer logBuffer) {
1335: LogUtils.writeLong(logBuffer, id);
1336: LogUtils.writeLong(logBuffer, lastLoggedLsn);
1337: }
1338:
1339: /**
1340: * @see Loggable#readFromLog
1341: *
1342: * It's ok for FindBugs to whine about id not being synchronized.
1343: */
1344: public void readFromLog(ByteBuffer logBuffer, byte entryTypeVersion) {
1345: id = LogUtils.readLong(logBuffer);
1346: lastLoggedLsn = LogUtils.readLong(logBuffer);
1347: }
1348:
1349: /**
1350: * @see Loggable#dumpLog
1351: */
1352: public void dumpLog(StringBuffer sb, boolean verbose) {
1353: sb.append("<txn id=\"");
1354: sb.append(super .toString());
1355: sb.append("\">");
1356: sb.append(DbLsn.toString(lastLoggedLsn));
1357: sb.append("</txn>");
1358: }
1359:
1360: /**
1361: * @see Loggable#getTransactionId
1362: */
1363: public long getTransactionId() {
1364: return getId();
1365: }
1366:
1367: /**
1368: * Transfer a single handle lock to the set of corresponding handles at
1369: * commit time.
1370: */
1371: private void transferHandleLockToHandleSet(Long handleLockId,
1372: Set dbHandleSet) throws DatabaseException {
1373:
1374: /* Create a set of destination transactions */
1375: int numHandles = dbHandleSet.size();
1376: Database[] dbHandles = new Database[numHandles];
1377: dbHandles = (Database[]) dbHandleSet.toArray(dbHandles);
1378: Locker[] destTxns = new Locker[numHandles];
1379: for (int i = 0; i < numHandles; i++) {
1380: destTxns[i] = new BasicLocker(envImpl);
1381: }
1382:
1383: /* Move this lock to the destination txns. */
1384: long nodeId = handleLockId.longValue();
1385: lockManager.transferMultiple(nodeId, this , destTxns);
1386:
1387: for (int i = 0; i < numHandles; i++) {
1388:
1389: /*
1390: * Make this handle and its handle protector txn remember each
1391: * other.
1392: */
1393: destTxns[i].addToHandleMaps(handleLockId, dbHandles[i]);
1394: DbInternal.dbSetHandleLocker(dbHandles[i], destTxns[i]);
1395: }
1396: }
1397:
1398: /**
1399: * Send trace messages to the java.util.logger. Don't rely on the logger
1400: * alone to conditionalize whether we send this message, we don't even want
1401: * to construct the message if the level is not enabled. The string
1402: * construction can be numerous enough to show up on a performance profile.
1403: */
1404: private void traceCommit(int numWriteLocks, int numReadLocks) {
1405: Logger logger = envImpl.getLogger();
1406: if (logger.isLoggable(Level.FINE)) {
1407: StringBuffer sb = new StringBuffer();
1408: sb.append(" Commit:id = ").append(id);
1409: sb.append(" numWriteLocks=").append(numWriteLocks);
1410: sb.append(" numReadLocks = ").append(numReadLocks);
1411: Tracer.trace(Level.FINE, envImpl, sb.toString());
1412: }
1413: }
1414:
1415: int getInMemorySize() {
1416: return inMemorySize;
1417: }
1418:
1419: /**
1420: * Store information about a DatabaseImpl that will have to be
1421: * purged at transaction commit or abort. This handles cleanup after
1422: * operations like Environment.truncateDatabase,
1423: * Environment.removeDatabase. Cleanup like this is done outside the
1424: * usual transaction commit or node undo processing, because
1425: * the mapping tree is always AutoTxn'ed to avoid deadlock and is
1426: * essentially non-transactional
1427: */
1428: private static class DatabaseCleanupInfo {
1429: DatabaseImpl dbImpl;
1430:
1431: /* if true, clean on commit. If false, clean on abort. */
1432: boolean deleteAtCommit;
1433:
1434: DatabaseCleanupInfo(DatabaseImpl dbImpl, boolean deleteAtCommit) {
1435: this.dbImpl = dbImpl;
1436: this.deleteAtCommit = deleteAtCommit;
1437: }
1438: }
1439: }
|