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

com.sleepycat.je.txn.Locker Maven / Gradle / Ivy

The 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.je.txn;

import java.util.HashMap;
import java.util.Map;

import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.LockConflictException;
import com.sleepycat.je.LockNotAvailableException;
import com.sleepycat.je.OperationFailureException;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.dbi.CursorImpl;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DbCleanup;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.tree.BIN;
import com.sleepycat.je.tree.BINReference;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.StatGroup;
import com.sleepycat.je.utilint.TestHook;

/**
 * Locker instances are JE's route to locking and transactional support.  This
 * class is the abstract base class for BasicLocker, ThreadLocker, Txn,
 * MasterTxn and ReadonlyTxn.  Locker instances are in fact only a transaction
 * shell to get to the lock manager, and don't guarantee transactional
 * semantics.
 *
 * Txn (includes Txns marked autoTxn) MasterTxn and ReadonlyTxn instances are
 * truly transactional. They have potentially different transaction begin and
 * end behaviors.
 */
public abstract class Locker {

    protected EnvironmentImpl envImpl;
    protected LockManager lockManager;

    protected long id;                        // transaction id
    private boolean readUncommittedDefault; // read-uncommitted is default

    /* Timeouts */
    boolean defaultNoWait; // true for non-blocking
    private long lockTimeoutMillis;       // timeout period for lock, in ms
    private long txnTimeoutMillis;        // timeout period for txns, in ms
    private long txnStartMillis;          // for txn timeout determination

    /* The lsn of the lock that this locker is waiting for. */
    private Long waitingFor;

    /* The LockType corresponding to waitingFor. */
    private LockType waitingForType;
    
    /* The current thread ID when waitingFor is set. */
    private long waiterThreadId;

    /*
     * DeleteInfo refers to BINReferences that should be sent to the
     * INCompressor for asynchronous compressing after the transaction ends.
     */
    Map deleteInfo;

    /**
     * The thread that created this locker.  Used for debugging, and by the
     * ThreadLocker subclass. Note that thread may be null if the Locker is
     * instantiated by reading the log.
     */
    protected Thread thread;

    /**
     * Set to false when close() is called.  After that point no other locker
     * operations should occur.
     */
    private boolean isOpen = true;

    /**
     * True if my locks can be preempted/stolen.
     */
    private boolean preemptable = true;

    /**
     * Non-null if a lock has been stolen from this locker by the HA replayer.
     */
    private RuntimeException preemptedCause;

    /**
     * Non-null if this locker is replacing another locker that is in the
     * process of closing because a cursor is being moved.
     */
    private Locker closingLocker;

    /** Hook called by lock manager after adding this locker as a waiter. */
    public TestHook afterWaiterHook;

    /**
     * Create a locker id. This constructor is called very often, so it should
     * be as streamlined as possible. It should never be called directly,
     * because the mandatedId mechanism only works if the generateId() method
     * is overridden to use the mandatedId value.
     *
     * @param readUncommittedDefault if true, this transaction does
     * read-uncommitted by default
     * @param noWait if true, non-blocking lock requests are used.
     */
    protected Locker(EnvironmentImpl envImpl,
                     boolean readUncommittedDefault,
                     boolean noWait,
                     long mandatedId) {

        TxnManager txnManager = envImpl.getTxnManager();
        this.lockManager = txnManager.getLockManager();
        this.id = generateId(txnManager, mandatedId);
        this.envImpl = envImpl;
        this.readUncommittedDefault = readUncommittedDefault;
        this.waitingFor = null;
        this.waitingForType = null;
        this.waiterThreadId = -1;

        /* get the default lock timeout. */
        defaultNoWait = noWait;
        lockTimeoutMillis = getInitialLockTimeout();

        /* Check the default txn timeout. */
        txnTimeoutMillis = envImpl.getTxnTimeout();

        txnStartMillis = System.currentTimeMillis();

        /* Save the thread used to create the locker. */
        thread = Thread.currentThread();

        /* Do lazy initialization of deleteInfo, to conserve memory. */
    }
    
    /**
     * For reading from the log.
     */
    Locker() {       
        defaultNoWait = false;
    }

    protected long getInitialLockTimeout() {
        return envImpl.getLockTimeout();
    }

    public EnvironmentImpl getEnvironment() {
        return envImpl;
    }

    /**
     * A Locker has to generate its next id. Some subtypes, like BasicLocker,
     * have a single id for all instances because they are never used for
     * recovery. Other subtypes ask the txn manager for an id or use a
     * specific, mandated id.
     */
    protected abstract long generateId(TxnManager txnManager, long mandatedId);

    /**
     * @return the transaction's id.
     */
    public long getId() {
        return id;
    }

    /**
     * @return the default no-wait (non-blocking) setting.
     */
    public boolean getDefaultNoWait() {
        return defaultNoWait;
    }

    public void setDefaultNoWait(boolean noWait) {
        defaultNoWait = noWait;
    }

    /**
     * Get the lock timeout period for this locker, in milliseconds
     *
     * WARNING: Be sure to always access the timeout with this accessor, since
     * it is overridden in BuddyLocker.
     */
    public synchronized long getLockTimeout() {
        return lockTimeoutMillis;
    }

    /**
     * Set the lock timeout period for any locks in this transaction,
     * in milliseconds.
     *
     * @param timeout The timeout value for the transaction lifetime, in
     * milliseconds. A value of 0 disables timeouts for the transaction.
     *
     * @throws IllegalArgumentException via Transaction.setLockTimeout
     */
    public synchronized void setLockTimeout(long timeout) {

        if (timeout < 0) {
            throw new IllegalArgumentException
                ("the timeout value cannot be negative");
        } else if (timeout > Math.pow(2, 32)) {
            throw new IllegalArgumentException
                ("the timeout value cannot be greater than 2^32");
        }

        lockTimeoutMillis = timeout;
    }

    /**
     * Set the timeout period for this transaction, in milliseconds.
     *
     * @param timeout The timeout value for the transaction lifetime, in
     * microseconds. A value of 0 disables timeouts for the transaction.
     *
     * @throws IllegalArgumentException via Transaction.setLockTimeout
     */
    public synchronized void setTxnTimeout(long timeout) {

        if (timeout < 0) {
            throw new IllegalArgumentException
                ("the timeout value cannot be negative");
        } else if (timeout > Math.pow(2, 32)) {
            throw new IllegalArgumentException
                ("the timeout value cannot be greater than 2^32");
        }

        txnTimeoutMillis = timeout;
    }

    /**
     * @return true if transaction was created with read-uncommitted as a
     * default.
     */
    public boolean isReadUncommittedDefault() {
        return readUncommittedDefault;
    }

    void setWaitingFor(Long lsn, LockType type) {
        waitingFor = lsn;
        waitingForType = type;
        waiterThreadId = Thread.currentThread().getId();
    }

    void clearWaitingFor() {
        waitingFor = null;
        waitingForType = null;
        waiterThreadId = -1;
    }

    Long getWaitingFor() {
        return waitingFor;
    }

    LockType getWaitingForType() {
        return waitingForType;
    }

    long getWaiterThreadId() {
        return waiterThreadId;
    }

    /**
     * Set the state of a transaction to abort-only.  Should ONLY be called
     * by OperationFailureException.
     */
    public void setOnlyAbortable(OperationFailureException cause) {
        /* no-op unless Txn. */
    }

    /**
     * Set the state of a transaction's IMPORTUNATE bit.
     */
    public void setImportunate(boolean importunate) {
        /* no-op unless Txn. */
    }

    /**
     * Get the state of a transaction's IMPORTUNATE bit.
     */
    public boolean getImportunate() {
        return false;
    }

    /**
     * Allows/disallows my locks from being stolen/preemted.
     */
    public void setPreemptable(boolean preemptable) {
        this.preemptable = preemptable;
    }

    /**
     * Returns whether my locks can be stolen/preemted.
     */
    public boolean getPreemptable() {
        return preemptable;
    }

    /**
     * Called when a lock is stolen from this locker by the HA replayer.
     */
    public void setPreempted() {

        /*
         * Record the stack trace when a lock is stolen.  This will provide
         * more "cause" information when it is wrapped in a
         * LockPreemptedException that is thrown later -- see checkPreempted.
         */
        preemptedCause = new RuntimeException
            ("Lock was preempted by the replication replayer");
    }

    /**
     * Called when obtaining a lock to cause a LockPreemptedException to be
     * thrown if a lock was preempted earlier.
     *
     * This operation is split into two methods, checkPreempted and
     * throwIfPreempted, so that Txn.checkPreempted can call throwIfPreempted
     * for all its BuddyLockers without causing an infinite recursion.  This
     * method is overridden by BuddyLocker to forward the call to its parent
     * buddy (the Txn), and by Txn to check all its child buddies.
     *
     * @param allowPreemptedLocker is a locker that is being closed as the
     * result of a cursor move operation.  If the operation is successful then
     * allowPreemptedLocker will be closed, and the fact that a lock has been
     * stolen from allowPreemptedLocker can be ignored.
     */
    public void checkPreempted(final Locker allowPreemptedLocker) 
        throws OperationFailureException {

        throwIfPreempted(allowPreemptedLocker);
    }

    /**
     * Called by checkPreempted to cause a LockPreemptedException to be thrown
     * if a lock was preempted earlier.  Creating the LockPreemptedException
     * sets the txn to abort-only.
     *
     * @see #checkPreempted
     */
    final void throwIfPreempted(final Locker allowPreemptedLocker) 
        throws OperationFailureException {

        if (this != allowPreemptedLocker &&
            preemptedCause != null) {
            throw envImpl.createLockPreemptedException(this, preemptedCause);
        }
    }

    /** For unit testing. */
    final boolean isPreempted() {
        return (preemptedCause != null);
    }

    /**
     * This method is called to set the closingLocker when a cursor has been
     * duplicated prior to being moved.  The new locker is informed of the old
     * locker, so that a preempted lock taken by the old locker can be ignored.
     * When the operation is complete, this method is called to clear the
     * closingLocker so that a reference to the old closed locker is not held
     * by this object.  [#16513]
     *
     * @param closingLocker the old locker that will be closed if the new
     * cursor (using this locker) is moved successfully.
     */
    public void setClosingLocker(final Locker closingLocker) {
        this.closingLocker = closingLocker;
    }

    /**
     * See ThreadLocker.allowMultithreadedAccess.
     */
    boolean setAllowMultithreadedAccess(boolean allow) {
        /* Do nothing by default. Is overridden by ThreadLocker. */
        return false;
    }

    protected abstract void checkState(boolean ignoreCalledByAbort)
        throws DatabaseException;

    /**
     * Overridden to perform actions in a non-transactional cursor when it is
     * opened, for example, ReplicaThreadLocker performs consistency checks.
     */
    public void openCursorHook(DatabaseImpl dbImpl) {
        /* Do nothing. */
    }

    /**
     * Returns whether a transaction is method indicates whether the txn is
     * part of the rep stream.
     *
     * A replicated txn must be used for writing to a replicated DB, and a
     * non-replicated txn must be used for writing to a non-replicated DB.
     * This is critical for avoiding corruption when HA failovers occur
     * [#23234] [#23330].
     *
     * See guard in LN.logInternal.
     */
    public boolean isReplicated() {
        return TxnManager.isReplicatedTxn(id);
    }

    /**
     * Returns true if writes may only be to non-replicated DBs using this
     * locker, or false if writes may only be to replicated DBs.
     *
     * By default (this implementation) local-write is true, since it is
     * allowed for all non-txnal lockers and for all lockers in a standalone
     * environment.  This method is overridden and returns false for
     * for user transactions in a replicated environment that are not
     * explicitly configured for local-write.
     *
     * This method is used to describe a locker's configured usage for checking
     * the validity of an API write operation.  This is checked by Cursor
     * methods at the beginning of each write operation.
     */
    public boolean isLocalWrite() {
        return true;
    }

    /**
     * Returns whether writes are prohibited using this locker.
     */
    public boolean isReadOnly() {
        return false;
    }

    /*
     * Obtain and release locks.
     */

    /**
     * Abstract method to a blocking or non-blocking lock of the given type on
     * the given LSN.  Unlike the lock() method, this method does not throw
     * LockNotAvailableException and can therefore be used by nonBlockingLock
     * to probe for a lock without the overhead of an exception stack trace.
     *
     * @param lsn is the node to lock.
     *
     * @param lockType is the type of lock to request.
     *
     * @param noWait is true to override the defaultNoWait setting.  If true,
     * or if defaultNoWait is true, throws LockNotAvailableException if the
     * lock cannot be granted without waiting.
     *
     * @param jumpAheadOfWaiters grant the lock before other waiters, if any.
     *
     * @param database is the database containing lsn.
     *
     * @throws LockConflictException if a blocking lock could not be acquired.
     */
    abstract LockResult lockInternal(long lsn,
                                     LockType lockType,
                                     boolean noWait,
                                     boolean jumpAheadOfWaiters,
                                     DatabaseImpl database)
        throws LockConflictException, DatabaseException;

    /**
     * Request a blocking or non-blocking lock of the given type on the given
     * LSN.
     *
     * @param lsn is the node to lock.
     *
     * @param lockType is the type of lock to request.
     *
     * @param noWait is true to override the defaultNoWait setting.  If true,
     * or if defaultNoWait is true, throws LockNotAvailableException if the
     * lock cannot be granted without waiting.
     *
     * @param database is the database containing lsn.
     *
     * @throws LockNotAvailableException if a non-blocking lock was denied.
     *
     * @throws LockConflictException if a blocking lock could not be acquired.
     */
    public LockResult lock(long lsn,
                           LockType lockType,
                           boolean noWait,
                           DatabaseImpl database)
        throws LockNotAvailableException, LockConflictException {

        final LockResult result = lockInternal
            (lsn, lockType, noWait, false /*jumpAheadOfWaiters*/, database);

        if (result.getLockGrant() == LockGrantType.DENIED) {
            /* DENIED can only be returned for a non-blocking lock. */
            throw new LockNotAvailableException(
                this, "Non-blocking lock was denied.");
        } else {
            checkPreempted(closingLocker);
            return result;
        }
    }

    /**
     * Request a non-blocking lock of the given type on the given LSN.
     *
     * 

Unlike lock(), this method returns LockGrantType.DENIED if the lock * is denied rather than throwing LockNotAvailableException. This method * should therefore not be used as the final lock for a user operation, * since in that case LockNotAvailableException should be thrown for a * denied lock. It is normally used only to probe for a lock internally, * and other recourse is taken if the lock is denied.

* * @param lsn is the node to lock. * * @param lockType is the type of lock to request. * * @param jumpAheadOfWaiters grant the lock before other waiters, if any. * * @param database is the database containing LSN. */ public LockResult nonBlockingLock(long lsn, LockType lockType, boolean jumpAheadOfWaiters, DatabaseImpl database) { final LockResult result = lockInternal (lsn, lockType, true /*noWait*/, jumpAheadOfWaiters, database); if (result.getLockGrant() != LockGrantType.DENIED) { checkPreempted(closingLocker); } return result; } /** * Release the lock on this LN and remove from the transaction's owning * set. */ public synchronized boolean releaseLock(long lsn) throws DatabaseException { boolean ret = lockManager.release(lsn, this); removeLock(lsn); return ret; } /** * Revert this lock from a write lock to a read lock. */ public void demoteLock(long lsn) throws DatabaseException { /* * If successful, the lock manager will call back to the transaction * and adjust the location of the lock in the lock collection. */ lockManager.demote(lsn, this); } /** * Called when an LN is logged by an operation that will not hold the lock * such as eviction/checkpoint deferred-write logging or cleaner LN * migration. We must acquire a lock on the new LSN on behalf of every * locker that currently holds a lock on the old LSN. * * Lock is non-blocking because no contention is possible on the new LSN. * * Because this locker is being used by multiple threads, this method may * be called for a locker that has been closed or for which the lock on the * old LSN has been released. Unlike other locking methods, in this case * we simply return rather than report an error. */ public synchronized void lockAfterLsnChange(long oldLsn, long newLsn, DatabaseImpl dbImpl) { if (!isValid()) { /* Locker was recently closed, made abort-only, etc. */ return; } final LockType lockType = lockManager.getOwnedLockType(oldLsn, this); if (lockType == null) { /* Lock was recently released. */ return; } final LockResult lockResult = nonBlockingLock (newLsn, lockType, false /*jumpAheadOfWaiters*/, dbImpl); if (lockResult.getLockGrant() == LockGrantType.DENIED) { throw EnvironmentFailureException.unexpectedState ("No contention is possible on new LSN: " + DbLsn.getNoFormatString(newLsn) + " old LSN: " + DbLsn.getNoFormatString(oldLsn) + " LockType: " + lockType); } } /** * In the case where logging occurs before locking, allow lockers to reject * the operation (e.g., if writing on a replica) and also prepare to undo * in the (very unlikely) event that logging succeeds but locking fails. */ public abstract void preLogWithoutLock(DatabaseImpl database); /** * Throws ReplicaWriteException if called for a locker on a Replica. This * implementation does nothing but is overridden by replication lockers. * [#20543] */ public void disallowReplicaWrite() { } /** * Returns whether this locker is transactional. */ public abstract boolean isTransactional(); /** * Returns whether the isolation level of this locker is serializable. */ public abstract boolean isSerializableIsolation(); /** * Returns whether the isolation level of this locker is read-committed. */ public abstract boolean isReadCommittedIsolation(); /** * Returns the underlying Txn if the locker is transactional, or null if * the locker is non-transactional. For a Txn-based locker, this method * returns 'this'. For a BuddyLocker, this method may return the buddy. */ public abstract Txn getTxnLocker(); /** * Returns a Transaction if the locker is transctional, or null otherwise. */ public Transaction getTransaction() { return null; } /** * Only BuddyLockers have buddies. */ Locker getBuddy() { return null; } /** * Creates a fresh non-transactional locker, while retaining any * transactional locks held by this locker. This method is called when the * cursor for this locker is cloned. * *

This method must return a locker that shares locks with this * locker, e.g., a ThreadLocker.

* *

In general, transactional lockers return 'this' when this method is * called, while non-transactional lockers return a new instance.

*/ public abstract Locker newNonTxnLocker() throws DatabaseException; /** * Releases any non-transactional locks held by this locker. This method * is called when the cursor moves to a new position or is closed. * *

In general, transactional lockers do nothing when this method is * called, while non-transactional lockers release all locks as if * operationEnd were called.

*/ public abstract void releaseNonTxnLocks() throws DatabaseException; /** * Releases locks and closes the locker at the end of a non-transactional * cursor operation. For a transctional cursor this method should do * nothing, since locks must be held until transaction end. */ public abstract void nonTxnOperationEnd() throws DatabaseException; /** * By default the set of buddy lockers is not maintained. This is * overridden by Txn. */ void addBuddy(BuddyLocker buddy) { } /** * By default the set of buddy lockers is not maintained. This is * overridden by Txn. */ void removeBuddy(BuddyLocker buddy) { } /** * Returns whether this locker can share locks with the given locker. */ public boolean sharesLocksWith(Locker other) { return false; } /** * The equivalent of calling operationEnd(true). */ public final void operationEnd() throws DatabaseException { operationEnd(true); } /** * A SUCCESS status equals operationOk. */ public final void operationEnd(OperationStatus status) throws DatabaseException { operationEnd(status == OperationStatus.SUCCESS); } /** * Different types of transactions do different things when the operation * ends. Txn does nothing, auto Txn commits or aborts, and BasicLocker (and * its subclasses) just releases locks. * * @param operationOK is whether the operation succeeded, since * that may impact ending behavior. (i.e for an auto Txn) */ public abstract void operationEnd(boolean operationOK) throws DatabaseException; /** * Should be called by all subclasses when the locker is no longer used. * For Txns and auto Txns this is at commit or abort. For * non-transactional lockers it is at operationEnd. */ void close() throws DatabaseException { isOpen = false; } /** * Used to determine whether the locker is usable. * * FUTURE: Note that this method is overridden by Txn, and Txn.abort sets * the state to closed when it begins rather than when it ends, but calls * close() (the method above) when it ends. This is not ideal and deserves * attention in the future. */ public boolean isValid() { return isOpen; } /** * @see Txn#addOpenedDatabase */ public void addOpenedDatabase(Database dbHandle) { } /** * @see HandleLocker#allowReleaseLockAfterLsnChange */ public boolean allowReleaseLockAfterLsnChange() { return false; } /** * Tell this transaction about a cursor. */ public abstract void registerCursor(CursorImpl cursor); /** * Remove a cursor from this txn. */ public abstract void unRegisterCursor(CursorImpl cursor); /** * Returns true if locking is required for this Locker. All Txnal lockers * require it; most BasicLockers do not, but BasicLockers on internal dbs * do. */ public abstract boolean lockingRequired(); /* * Transactional support */ /** * @return the WriteLockInfo for this node. */ public abstract WriteLockInfo getWriteLockInfo(long lsn); /** * Database operations like remove, truncate and rename require that * DatabaseImpls are deleted or updated at transaction commit or abort. */ public abstract void addDbCleanup(DbCleanup cleanup); /** * Add delete information, to be added to the inCompressor queue when the * transaction ends. */ public void addDeleteInfo(BIN bin) { /* * Skip queue addition if a delta will be logged. In this case the * slot compression will occur in BIN.beforeLog, when a full version is * logged. */ if (bin.shouldLogDelta()) { return; } synchronized (this) { /* Maintain only one binRef per node. */ if (deleteInfo == null) { deleteInfo = new HashMap<>(); } Long nodeId = bin.getNodeId(); if (deleteInfo.containsKey(nodeId)) { return; } deleteInfo.put(nodeId, bin.createReference()); } } /* * Manage locks owned by this transaction. Note that transactions that will * be multithreaded must override these methods and provide synchronized * implementations. */ /** * Add a lock to set owned by this transaction. */ protected abstract void addLock(Long lsn, LockType type, LockGrantType grantStatus) throws DatabaseException; /** * Remove the lock from the set owned by this transaction. If specified to * LockManager.release, the lock manager will call this when its releasing * a lock. */ abstract void removeLock(long lsn) throws DatabaseException; /** * A lock is being demoted. Move it from the write collection into the read * collection. */ abstract void moveWriteToReadLock(long lsn, Lock lock); /** * Get lock count, for per transaction lock stats, for internal debugging. */ public abstract StatGroup collectStats() throws DatabaseException; /* * Check txn timeout, if set. Called by the lock manager when blocking on a * lock. */ public boolean isTimedOut() { long timeout = getTxnTimeout(); if (timeout != 0) { long diff = System.currentTimeMillis() - txnStartMillis; if (diff > timeout) { return true; } } return false; } /** * Get the transaction timeout period for this locker, in milliseconds * * public for jca/ra/JELocalTransaction. * * WARNING: Be sure to always access the timeout with this accessor, since * it is overridden in BuddyLocker. */ public synchronized long getTxnTimeout() { return txnTimeoutMillis; } long getTxnStartMillis() { return txnStartMillis; } /** * @return if this locker has ever been rolled back. */ public boolean isRolledBack() { return false; // most Locker types will never roll back. } /** * This method is safe to call without synchronizing and this fact is * relied on by LockManager when creating exception messages. */ @Override public String toString() { String className = getClass().getName(); className = className.substring(className.lastIndexOf('.') + 1); return System.identityHashCode(this) + " " + Long.toString(id) + "_" + ((thread == null) ? "" : thread.getName()) + "_" + className; } /** * Dump lock table, for debugging */ public void dumpLockTable() throws DatabaseException { lockManager.dump(); } }