All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sleepycat.collections.CurrentTransaction Maven / Gradle / Ivy

Go to download

Berkeley DB Java Edition is a open source, transactional storage solution for Java applications. The Direct Persistence Layer (DPL) API is faster and easier to develop, deploy, and manage than serialized object files or ORM-based Java persistence solutions. The Collections API enhances the standard java.util.collections classes allowing them to be persisted to a local file system and accessed concurrently while protected by ACID transactions. Data is stored by serializing objects and managing class and instance data separately so as not to waste space. Berkeley DB Java Edition is the reliable drop-in solution for complex, fast, and scalable storage. Source for this release is in 'je-4.0.92-sources.jar', the Javadoc is located at 'http://download.oracle.com/berkeley-db/docs/je/4.0.92/'.

There is a newer version: 5.0.73
Show newest version
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.collections;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;

import com.sleepycat.compat.DbCompat;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
/*  */
import com.sleepycat.je.EnvironmentFailureException; // for javadoc
/*  */
import com.sleepycat.je.LockMode;
/*  */
import com.sleepycat.je.OperationFailureException; // for javadoc
/*  */
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.util.RuntimeExceptionWrapper;

/**
 * Provides access to the current transaction for the current thread within the
 * context of a Berkeley DB environment.  This class provides explicit
 * transaction control beyond that provided by the {@link TransactionRunner}
 * class.  However, both methods of transaction control manage per-thread
 * transactions.
 *
 * @author Mark Hayes
 */
public class CurrentTransaction {

    /* For internal use, this class doubles as an Environment wrapper. */

    private static WeakHashMap envMap =
        new WeakHashMap();

    private LockMode writeLockMode;
    private boolean cdbMode;
    private boolean txnMode;
    private boolean lockingMode;
    private ThreadLocal localTrans = new ThreadLocal();
    private ThreadLocal localCdbCursors;

    /*
     * Use a WeakReference to the Environment to avoid pinning the environment
     * in the envMap.  The WeakHashMap envMap uses the Environment as a weak
     * key, but this won't prevent GC of the Environment if the map's value has
     * a hard reference to the Environment.  [#15444]
     */
    private WeakReference envRef;

    /**
     * Gets the CurrentTransaction accessor for a specified Berkeley DB
     * environment.  This method always returns the same reference when called
     * more than once with the same environment parameter.
     *
     * @param env is an open Berkeley DB environment.
     *
     * @return the CurrentTransaction accessor for the given environment, or
     * null if the environment is not transactional.
     */
    public static CurrentTransaction getInstance(Environment env) {

        CurrentTransaction currentTxn = getInstanceInternal(env);
        return currentTxn.isTxnMode() ? currentTxn : null;
    }

    /**
     * Gets the CurrentTransaction accessor for a specified Berkeley DB
     * environment.  Unlike getInstance(), this method never returns null.
     *
     * @param env is an open Berkeley DB environment.
     */
    static CurrentTransaction getInstanceInternal(Environment env) {
        synchronized (envMap) {
            CurrentTransaction ct = envMap.get(env);
            if (ct == null) {
                ct = new CurrentTransaction(env);
                envMap.put(env, ct);
            }
            return ct;
        }
    }

    private CurrentTransaction(Environment env) {
        envRef = new WeakReference(env);
        try {
            EnvironmentConfig config = env.getConfig();
            txnMode = config.getTransactional();
            lockingMode = DbCompat.getInitializeLocking(config);
            if (txnMode || lockingMode) {
                writeLockMode = LockMode.RMW;
            } else {
                writeLockMode = LockMode.DEFAULT;
            }
            cdbMode = DbCompat.getInitializeCDB(config);
            if (cdbMode) {
                localCdbCursors = new ThreadLocal();
            }
        } catch (DatabaseException e) {
            throw RuntimeExceptionWrapper.wrapIfNeeded(e);
        }
    }

    /**
     * Returns whether environment is configured for locking.
     */
    final boolean isLockingMode() {

        return lockingMode;
    }

    /**
     * Returns whether this is a transactional environment.
     */
    final boolean isTxnMode() {

        return txnMode;
    }

    /**
     * Returns whether this is a Concurrent Data Store environment.
     */
    final boolean isCdbMode() {

        return cdbMode;
    }

    /**
     * Return the LockMode.RMW or null, depending on whether locking is
     * enabled.  LockMode.RMW will cause an error if passed when locking
     * is not enabled.  Locking is enabled if locking or transactions were
     * specified for this environment.
     */
    final LockMode getWriteLockMode() {

        return writeLockMode;
    }

    /**
     * Returns the underlying Berkeley DB environment.
     *
     * @return the Environment.
     */
    public final Environment getEnvironment() {

        return envRef.get();
    }

    /**
     * Returns the transaction associated with the current thread for this
     * environment, or null if no transaction is active.
     *
     * @return the Transaction.
     */
    public final Transaction getTransaction() {

        Trans trans = (Trans) localTrans.get();
        return (trans != null) ? trans.txn : null;
    }

    /**
     * Returns whether auto-commit may be performed by the collections API.
     * True is returned if no collections API transaction is currently active,
     * and no XA transaction is currently active.
     */
    boolean isAutoCommitAllowed()
        throws DatabaseException {

        return getTransaction() == null &&
               DbCompat.getThreadTransaction(getEnvironment()) == null;
    }

    /**
     * Begins a new transaction for this environment and associates it with
     * the current thread.  If a transaction is already active for this
     * environment and thread, a nested transaction will be created.
     *
     * @param config the transaction configuration used for calling
     * {@link Environment#beginTransaction}, or null to use the default
     * configuration.
     *
     * @return the new transaction.
     *
     * 
     * @throws com.sleepycat.je.rep.InsufficientReplicasException if the Master
     * in a replicated environment could not contact a quorum of replicas as
     * determined by the {@link com.sleepycat.je.Durability.ReplicaAckPolicy}.
     *
     * @throws com.sleepycat.je.rep.ReplicaConsistencyException if a replica
     * in a replicated environment cannot become consistent within the timeout
     * period.
     *
     * @throws EnvironmentFailureException if an unexpected, internal or
     * environment-wide failure occurs.
     * 
     *
     * @throws DatabaseException if the transaction cannot be started, in which
     * case any existing transaction is not affected.
     *
     * @throws IllegalStateException if a transaction is already active and
     * nested transactions are not supported by the environment.
     */
    public final Transaction beginTransaction(TransactionConfig config)
        throws DatabaseException {

        Environment env = getEnvironment();
        Trans trans = (Trans) localTrans.get();
        if (trans != null) {
            if (trans.txn != null) {
                if (!DbCompat.NESTED_TRANSACTIONS) {
                    throw new IllegalStateException
                        ("Nested transactions are not supported");
                }
                Transaction parentTxn = trans.txn;
                trans = new Trans(trans, config);
                trans.txn = env.beginTransaction(parentTxn, config);
                localTrans.set(trans);
            } else {
                trans.txn = env.beginTransaction(null, config);
                trans.config = config;
            }
        } else {
            trans = new Trans(null, config);
            trans.txn = env.beginTransaction(null, config);
            localTrans.set(trans);
        }
        return trans.txn;
    }

    /**
     * Commits the transaction that is active for the current thread for this
     * environment and makes the parent transaction (if any) the current
     * transaction.
     *
     * @return the parent transaction or null if the committed transaction was
     * not nested.
     *
     * 
     * @throws com.sleepycat.je.rep.InsufficientReplicasException if the master
     * in a replicated environment could not contact a quorum of replicas as
     * determined by the {@link com.sleepycat.je.Durability.ReplicaAckPolicy}.
     * The application must abort the transaction and can choose to retry it.
     *
     * @throws com.sleepycat.je.rep.InsufficientAcksException if the master in
     * a replicated environment did not receive enough replica acknowledgments,
     * although the commit succeeded locally.
     *
     * @throws com.sleepycat.je.rep.ReplicaWriteException if a write operation
     * was performed with this transaction, but this node is now a Replica.
     *
     * @throws OperationFailureException if this exception occurred earlier and
     * caused the transaction to be invalidated.
     *
     * @throws EnvironmentFailureException if an unexpected, internal or
     * environment-wide failure occurs.
     * 
     *
     * @throws DatabaseException if an error occurs committing the transaction.
     * The transaction will still be closed and the parent transaction will
     * become the current transaction.
     *
     * @throws IllegalStateException if no transaction is active for the
     * current thread for this environment.
     */
    public final Transaction commitTransaction()
        throws DatabaseException, IllegalStateException {

        Trans trans = (Trans) localTrans.get();
        if (trans != null && trans.txn != null) {
            Transaction parent = closeTxn(trans);
            trans.txn.commit();
            return parent;
        } else {
            throw new IllegalStateException("No transaction is active");
        }
    }

    /**
     * Aborts the transaction that is active for the current thread for this
     * environment and makes the parent transaction (if any) the current
     * transaction.
     *
     * @return the parent transaction or null if the aborted transaction was
     * not nested.
     *
     * 
     * @throws EnvironmentFailureException if an unexpected, internal or
     * environment-wide failure occurs.
     * 
     *
     * @throws DatabaseException if an error occurs aborting the transaction.
     * The transaction will still be closed and the parent transaction will
     * become the current transaction.
     *
     * @throws IllegalStateException if no transaction is active for the
     * current thread for this environment.
     */
    public final Transaction abortTransaction()
        throws DatabaseException, IllegalStateException {

        Trans trans = (Trans) localTrans.get();
        if (trans != null && trans.txn != null) {
            Transaction parent = closeTxn(trans);
            trans.txn.abort();
            return parent;
        } else {
            throw new IllegalStateException("No transaction is active");
        }
    }

    /**
     * Returns whether the current transaction is a readUncommitted
     * transaction.
     */
    final boolean isReadUncommitted() {

        Trans trans = (Trans) localTrans.get();
        if (trans != null && trans.config != null) {
            return trans.config.getReadUncommitted();
        } else {
            return false;
        }
    }

    private Transaction closeTxn(Trans trans) {

        localTrans.set(trans.parent);
        return (trans.parent != null) ? trans.parent.txn : null;
    }

    private static class Trans {

        private Trans parent;
        private Transaction txn;
        private TransactionConfig config;

        private Trans(Trans parent, TransactionConfig config) {

            this.parent = parent;
            this.config = config;
        }
    }

    /**
     * Opens a cursor for a given database, dup'ing an existing CDB cursor if
     * one is open for the current thread.
     */
    Cursor openCursor(Database db,
                      CursorConfig cursorConfig,
                      boolean writeCursor,
                      Transaction txn)
        throws DatabaseException {

        if (cdbMode) {
            CdbCursors cdbCursors = null;
            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
            if (cdbCursorsMap == null) {
                cdbCursorsMap = new WeakHashMap();
                localCdbCursors.set(cdbCursorsMap);
            } else {
                cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
            }
            if (cdbCursors == null) {
                cdbCursors = new CdbCursors();
                cdbCursorsMap.put(db, cdbCursors);
            }

            /*
             * In CDB mode the cursorConfig specified by the user is ignored
             * and only the writeCursor parameter is honored.  This is the only
             * meaningful cursor attribute for CDB, and here we count on
             * writeCursor flag being set correctly by the caller.
             */
            List cursors;
            CursorConfig cdbConfig;
            if (writeCursor) {
                if (cdbCursors.readCursors.size() > 0) {

                    /*
                     * Although CDB allows opening a write cursor when a read
                     * cursor is open, a self-deadlock will occur if a write is
                     * attempted for a record that is read-locked; we should
                     * avoid self-deadlocks at all costs
                     */
                    throw new IllegalStateException
                        ("Cannot open CDB write cursor when read cursor " +
                         "is open");
                }
                cursors = cdbCursors.writeCursors;
                cdbConfig = new CursorConfig();
                DbCompat.setWriteCursor(cdbConfig, true);
            } else {
                cursors = cdbCursors.readCursors;
                cdbConfig = null;
            }
            Cursor cursor;
            if (cursors.size() > 0) {
                Cursor other = ((Cursor) cursors.get(0));
                cursor = other.dup(false);
            } else {
                cursor = db.openCursor(null, cdbConfig);
            }
            cursors.add(cursor);
            return cursor;
        } else {
            return db.openCursor(txn, cursorConfig);
        }
    }

    /**
     * Duplicates a cursor for a given database.
     *
     * @param writeCursor true to open a write cursor in a CDB environment, and
     * ignored for other environments.
     *
     * @param samePosition is passed through to Cursor.dup().
     *
     * @return the open cursor.
     *
     * @throws DatabaseException if a database problem occurs.
     */
    Cursor dupCursor(Cursor cursor, boolean writeCursor, boolean samePosition)
        throws DatabaseException {

        if (cdbMode) {
            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
            if (cdbCursorsMap != null) {
                Database db = cursor.getDatabase();
                CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
                if (cdbCursors != null) {
                    List cursors = writeCursor ? cdbCursors.writeCursors
                                               : cdbCursors.readCursors;
                    if (cursors.contains(cursor)) {
                        Cursor newCursor = cursor.dup(samePosition);
                        cursors.add(newCursor);
                        return newCursor;
                    }
                }
            }
            throw new IllegalStateException("Cursor to dup not tracked");
        } else {
            return cursor.dup(samePosition);
        }
    }

    /**
     * Closes a cursor.
     *
     * @param cursor the cursor to close.
     *
     * @throws DatabaseException if a database problem occurs.
     */
    void closeCursor(Cursor cursor)
        throws DatabaseException {

        if (cursor == null) {
            return;
        }
        if (cdbMode) {
            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
            if (cdbCursorsMap != null) {
                Database db = cursor.getDatabase();
                CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap.get(db);
                if (cdbCursors != null) {
                    if (cdbCursors.readCursors.remove(cursor) ||
                        cdbCursors.writeCursors.remove(cursor)) {
                        cursor.close();
                        return;
                    }
                }
            }
            throw new IllegalStateException
                ("Closing CDB cursor that was not known to be open");
        } else {
            cursor.close();
        }
    }

    /**
     * Returns true if a CDB cursor is open and therefore a Database write
     * operation should not be attempted since a self-deadlock may result.
     */
    boolean isCDBCursorOpen(Database db) {
        if (cdbMode) {
            WeakHashMap cdbCursorsMap = (WeakHashMap) localCdbCursors.get();
            if (cdbCursorsMap != null) {
                CdbCursors cdbCursors = (CdbCursors) cdbCursorsMap.get(db);

                if (cdbCursors != null &&
                    (cdbCursors.readCursors.size() > 0 ||
                     cdbCursors.writeCursors.size() > 0)) {
                    return true;
                }
            }
        }
        return false;
    }

    static final class CdbCursors {

        List writeCursors = new ArrayList();
        List readCursors = new ArrayList();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy