Package com.sleepycat.je.txn

Source Code of com.sleepycat.je.txn.LockManager

/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002, 2011 Oracle and/or its affiliates.  All rights reserved.
*
*/

package com.sleepycat.je.txn;

import static com.sleepycat.je.txn.LockStatDefinition.GROUP_DESC;
import static com.sleepycat.je.txn.LockStatDefinition.GROUP_NAME;
import static com.sleepycat.je.txn.LockStatDefinition.LOCK_OWNERS;
import static com.sleepycat.je.txn.LockStatDefinition.LOCK_READ_LOCKS;
import static com.sleepycat.je.txn.LockStatDefinition.LOCK_REQUESTS;
import static com.sleepycat.je.txn.LockStatDefinition.LOCK_TOTAL;
import static com.sleepycat.je.txn.LockStatDefinition.LOCK_WAITERS;
import static com.sleepycat.je.txn.LockStatDefinition.LOCK_WAITS;
import static com.sleepycat.je.txn.LockStatDefinition.LOCK_WRITE_LOCKS;

import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DeadlockException;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.EnvironmentMutableConfig;
import com.sleepycat.je.LockConflictException;
import com.sleepycat.je.LockNotAvailableException;
import com.sleepycat.je.LockNotGrantedException;
import com.sleepycat.je.LockStats;
import com.sleepycat.je.LockTimeoutException;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.ThreadInterruptedException;
import com.sleepycat.je.TransactionTimeoutException;
import com.sleepycat.je.config.EnvironmentParams;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.DbTree;
import com.sleepycat.je.dbi.EnvConfigObserver;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.dbi.MemoryBudget;
import com.sleepycat.je.dbi.RangeRestartException;
import com.sleepycat.je.latch.Latch;
import com.sleepycat.je.latch.LatchSupport;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.IntStat;
import com.sleepycat.je.utilint.LongStat;
import com.sleepycat.je.utilint.StatGroup;
import com.sleepycat.je.utilint.TinyHashSet;

/**
* LockManager manages locks.
*
* Note that locks are counted as taking up part of the JE cache;
*/
public abstract class LockManager implements EnvConfigObserver {

    /*
     * The total memory cost for a lock is the Lock object, plus its entry and
     * key in the lock hash table.
     *
     * The addition and removal of Lock objects, and the corresponding cost of
     * their hashmap entry and key are tracked through the LockManager.
     */
    static final long TOTAL_LOCKIMPL_OVERHEAD =
        MemoryBudget.LOCKIMPL_OVERHEAD +
        MemoryBudget.HASHMAP_ENTRY_OVERHEAD +
        MemoryBudget.LONG_OVERHEAD;

    static final long TOTAL_THINLOCKIMPL_OVERHEAD =
        MemoryBudget.THINLOCKIMPL_OVERHEAD +
        MemoryBudget.HASHMAP_ENTRY_OVERHEAD +
        MemoryBudget.LONG_OVERHEAD;

    private static final long REMOVE_TOTAL_LOCKIMPL_OVERHEAD =
        0 - TOTAL_LOCKIMPL_OVERHEAD;

    private static final long REMOVE_TOTAL_THINLOCKIMPL_OVERHEAD =
        0 - TOTAL_THINLOCKIMPL_OVERHEAD;

    private static final long THINLOCK_MUTATE_OVERHEAD =
        MemoryBudget.LOCKIMPL_OVERHEAD -
        MemoryBudget.THINLOCKIMPL_OVERHEAD +
        MemoryBudget.LOCKINFO_OVERHEAD;
   
    private static final List<ThreadLocker> EMPTY_THREAD_LOCKERS =
        Collections.emptyList();

    int nLockTables = 1;
    Latch[] lockTableLatches;
    private final Map<Long,Lock>[] lockTables;          // keyed by LSN
    private final boolean oldLockExceptions;
    private final EnvironmentImpl envImpl;
    private final MemoryBudget memoryBudget;

    private final StatGroup stats;
    private final LongStat nRequests; /* number of time a request was made. */
    private final LongStat nWaits;    /* number of time a request blocked. */

    private static RangeRestartException rangeRestartException =
        new RangeRestartException();
    private static boolean lockTableDump = false;

    /**
     * Maps a thread to a set of ThreadLockers.  Currently this map is only
     * maintained (non-null) in a replicated environment because it is only
     * needed for determining when to throw LockPreemptedException.
     *
     * Access to the map need not be synchronized because it is a
     * ConcurrentHashMap.  Access to the TinyHashSet stored for each thread
     * need not be synchronized, since it is only accessed by a single thread.
     *
     * A TinyHashSet is used because typically only a single ThreadLocker per
     * thread will be open at one time.
     *
     * @see ThreadLocker#checkPreempted
     * [#16513]
     */
    private final Map<Thread, TinyHashSet<ThreadLocker>> threadLockers;

    /*
     * @SuppressWarnings is used to stifle a type safety complaint about the
     * assignment of lockTables = new Map[nLockTables]. There's no way to
     * specify the type of the array.
     */
    @SuppressWarnings("unchecked")
    public LockManager(EnvironmentImpl envImpl) {

        DbConfigManager configMgr = envImpl.getConfigManager();
        nLockTables = configMgr.getInt(EnvironmentParams.N_LOCK_TABLES);
        oldLockExceptions =
            configMgr.getBoolean(EnvironmentParams.LOCK_OLD_LOCK_EXCEPTIONS);
        lockTables = new Map[nLockTables];
        lockTableLatches = new Latch[nLockTables];
        for (int i = 0; i < nLockTables; i++) {
            lockTables[i] = new HashMap<Long,Lock>();
            lockTableLatches[i] = new Latch("Lock Table " + i);
        }
        this.envImpl = envImpl;
        memoryBudget = envImpl.getMemoryBudget();

        stats = new StatGroup(GROUP_NAME, GROUP_DESC);
        nRequests = new LongStat(stats, LOCK_REQUESTS);
        nWaits = new LongStat(stats, LOCK_WAITS);

        /* Initialize mutable properties and register for notifications. */
        envConfigUpdate(configMgr, null);
        envImpl.addConfigObserver(this);

        if (envImpl.isReplicated()) {
            threadLockers =
                new ConcurrentHashMap<Thread, TinyHashSet<ThreadLocker>>();
        } else {
            threadLockers = null;
        }
    }

    /**
     * Process notifications of mutable property changes.
     */
    public void envConfigUpdate(DbConfigManager configMgr,
                                EnvironmentMutableConfig ignore) {
        LockInfo.setDeadlockStackTrace(configMgr.getBoolean
            (EnvironmentParams.TXN_DEADLOCK_STACK_TRACE));
        setLockTableDump(configMgr.getBoolean
            (EnvironmentParams.TXN_DUMPLOCKS));
    }

    /**
     * Called when the je.txn.dumpLocks property is changed.
     */
    static void setLockTableDump(boolean enable) {
        lockTableDump = enable;
    }

    int getLockTableIndex(Long lsn) {
        return (((int) lsn.longValue()) & 0x7fffffff) %
            nLockTables;
    }

    int getLockTableIndex(long lsn) {
        return (((int) lsn) & 0x7fffffff) % nLockTables;
    }

    /**
     * Attempt to acquire a lock of <i>type</i> on <i>lsn</i>.  If the lock
     * acquisition would result in a deadlock, throw an exception.<br> If the
     * requested lock is not currently available, block until it is or until
     * timeout milliseconds have elapsed.<br> If a lock of <i>type</i> is
     * already held, return EXISTING.<br> If a WRITE lock is held and a READ
     * lock is requested, return PROMOTION.<br>
     *
     * If a lock request is for a lock that is not currently held, return
     * either NEW or DENIED depending on whether the lock is granted or
     * not.<br>
     *
     * @param lsn The LSN to lock.
     *
     * @param locker The Locker to lock this on behalf of.
     *
     * @param type The lock type requested.
     *
     * @param timeout milliseconds to time out after if lock couldn't be
     * obtained.  0 means block indefinitely.  Not used if nonBlockingRequest
     * is true.
     *
     * @param nonBlockingRequest if true, means don't block if lock can't be
     * acquired, and ignore the timeout parameter.
     *
     * @param jumpAheadOfWaiters grant the lock before other waiters, if any.
     *
     * @return a LockGrantType indicating whether the request was fulfilled or
     * not.  LockGrantType.NEW means the lock grant was fulfilled and the
     * caller did not previously hold the lock.  PROMOTION means the lock was
     * granted and it was a promotion from READ to WRITE.  EXISTING means the
     * lock was already granted (not a promotion).  DENIED means the lock was
     * not granted because the timeout passed without acquiring the lock or
     * timeout was 0 and the lock was not immediately available.
     *
     * @throws LockConflictException if lock could not be acquired.
     *
     * @throws IllegalArgumentException via db/cursor read/write methods, if
     * non-transactional access to a replicated environment is attempted, and
     * read-uncommitted is not specified.
     */
    public LockGrantType lock(long lsn,
                              Locker locker,
                              LockType type,
                              long timeout,
                              boolean nonBlockingRequest,
                              boolean jumpAheadOfWaiters,
                              DatabaseImpl database)
        throws LockConflictException, DatabaseException {

        assert timeout >= 0;

        /* No lock needed for dirty-read, return as soon as possible. */
        if (type == LockType.NONE) {
            return LockGrantType.NONE_NEEDED;
        }

        /*
         * Assert that a replication-defined locker is used for locks on
         * replicated databases.  Two cases are exempt from this rule:
         * - Only NameLNs that identify replicated DBs are replicated, not
         *   all NameLNs in the naming DB, so the naming DB is exempt.
         * - Non-preemption is permissible for selected internal operations
         *   because we can ensure that they are not long running and will not
         *   hold locks interminably.  A BasicLocker is often used internally
         *   in such cases.
         */
        if (envImpl.isReplicated() &&
            database != null &&
            database.isReplicated() &&
            !database.getId().equals(DbTree.NAME_DB_ID) &&
            (locker.getPreemptable() || type.isWriteLock()) &&
            !locker.isReplicationDefined()) {
            throw EnvironmentFailureException.unexpectedState
                ("Locker: " + locker.getClass().getName());
        }

        /*
         * Lock on locker before latching the lockTable to avoid having another
         * notifier perform the notify before the waiter is actually waiting.
         */
        synchronized (locker) {
            LockGrantType ret = null;
            ret = lockInternal(lsn, locker, type, timeout, nonBlockingRequest,
                               jumpAheadOfWaiters, database);
            return ret;
        }
    }

    private LockGrantType lockInternal(long lsn,
                                       Locker locker,
                                       LockType type,
                                       long timeout,
                                       boolean nonBlockingRequest,
                                       boolean jumpAheadOfWaiters,
                                       DatabaseImpl database)
        throws DeadlockException, DatabaseException {

        Long nid = Long.valueOf(lsn);
        LockAttemptResult result = attemptLock
            (nid, locker, type, nonBlockingRequest, jumpAheadOfWaiters);
        /* If we got the lock or a non-blocking lock was denied, return. */
        if (result.success ||
            result.lockGrant == LockGrantType.DENIED) {
            assert nonBlockingRequest || result.success;
            return result.lockGrant;
        }

        assert checkNoLatchesHeld(nonBlockingRequest):
            LatchSupport.countLatchesHeld() +
            " latches held while trying to lock, lock table =" +
            LatchSupport.latchesHeldToString();

        /*
         * We must have gotten WAIT_* from the lock request. We know that
         * this is a blocking request, because if it wasn't, Lock.lock
         * would have returned DENIED. Go wait!
         */
        assert !nonBlockingRequest;
        try {
            boolean doWait = true;
            boolean isImportunate = locker.getImportunate();

            /*
             * Before blocking, check locker/txn timeout. We need to check here
             * or lock timeouts will always take precedence and we'll never
             * actually get any txn timeouts.
             */
            if (locker.isTimedOut()) {
                if (validateOwnership(nid, locker, type,
                                      !isImportunate,
                                      memoryBudget)) {
                    doWait = false;
                } else if (isImportunate) {
                    result = stealLock(nid, locker, type, memoryBudget);
                    if (result.success) {
                        doWait = false;
                    } else {
                        /* Lock holder is non-preemptable, wait below. */
                    }
                } else {
                    throw makeTimeoutMsg(false /*isLockNotTxnTimeout*/,
                                         locker, lsn, type,
                                         result.lockGrant,
                                         result.useLock,
                                         locker.getTxnTimeout(),
                                         locker.getTxnStartMillis(),
                                         System.currentTimeMillis(),
                                         database);
                }
            }

            boolean keepTime = (timeout > 0);
            long startTime = (keepTime ? System.currentTimeMillis() : 0);
            while (doWait) {
                locker.setWaitingFor(result.useLock);

                try {
                    locker.wait(timeout);
                } catch (InterruptedException IE) {
                    throw new ThreadInterruptedException(envImpl, IE);
                }

                boolean lockerTimedOut = locker.isTimedOut();
                long now = System.currentTimeMillis();
                boolean thisLockTimedOut =
                    (keepTime && (now - startTime >= timeout));
                boolean isRestart =
                    (result.lockGrant == LockGrantType.WAIT_RESTART);

                /*
                 * Re-check for ownership of the lock following wait.  If
                 * we timed out and we don't have ownership then flush this
                 * lock from both the waiters and owners while under the
                 * lock table latch.  See SR 10103.
                 */
                if (validateOwnership(nid, locker, type,
                                      (lockerTimedOut ||
                                      thisLockTimedOut ||
                                      isRestart) &&
                                      !isImportunate,
                                      memoryBudget)) {
                    break;
                } else if (isImportunate) {
                    result = stealLock(nid, locker, type, memoryBudget);
                    if (result.success) {
                        break;
                    } else {
                        /* Lock holder is non-preemptable, wait again. */
                    }
                } else {
                    /* After a restart conflict the lock will not be held. */
                    if (isRestart) {
                        throw rangeRestartException;
                    }

                    if (thisLockTimedOut) {
                        throw makeTimeoutMsg
                            (true /*isLockNotTxnTimeout*/, locker, lsn,
                             type, result.lockGrant, result.useLock,
                             timeout, startTime, now, database);
                    }

                    if (lockerTimedOut) {
                        throw makeTimeoutMsg
                            (false /*isLockNotTxnTimeout*/, locker, lsn,
                             type, result.lockGrant, result.useLock,
                             locker.getTxnTimeout(),
                             locker.getTxnStartMillis(), now, database);
                    }
                }
            }
        } finally {
            locker.setWaitingFor(null);
            assert EnvironmentImpl.maybeForceYield();
        }

        /*
         * After waiting for the lock, we must break out of the wait loop and
         * add the lock to the locker.  This is true even for importunate
         * lockers, since an existing lock (acquired via a release) will not be
         * added to the locker by attemptLock. [#16879]
         */
        locker.addLock(nid, type, result.lockGrant);

        return result.lockGrant;
    }

    /**
     * Returns the Lockers that own a lock on the given LSN.  Note that when
     * this method returns, there is nothing to prevent these lockers from
     * releasing the lock or being closed.
     */
    public abstract Set<LockInfo> getOwners(Long lsn);

    Set<LockInfo> getOwnersInternal(Long lsn, int lockTableIndex) {
        /* Get the target lock. */
        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock useLock = lockTable.get(lsn);
        if (useLock == null) {
            return null;
        }
        return useLock.getOwnersClone();
    }

    /**
     * Returns the LockType if the given locker owns a lock on the given node,
     * or null if the lock is not owned.
     */
    public abstract LockType getOwnedLockType(Long lsn, Locker locker);

    LockType getOwnedLockTypeInternal(Long lsn,
                                      Locker locker,
                                      int lockTableIndex) {
        /* Get the target lock. */
        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock useLock = lockTable.get(lsn);
        if (useLock == null) {
            return null;
        }
        return useLock.getOwnedLockType(locker);
    }

    public abstract boolean isLockUncontended(Long lsn);

    boolean isLockUncontendedInternal(Long lsn, int lockTableIndex) {
        /* Get the target lock. */
        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock useLock = lockTable.get(lsn);
        if (useLock == null) {
            return true;
        }
        return useLock.nWaiters() == 0 &&
               useLock.nOwners() == 0;
    }

    abstract Lock lookupLock(Long lsn)
        throws DatabaseException;

    Lock lookupLockInternal(Long lsn, int lockTableIndex) {
        /* Get the target lock. */
        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock useLock = lockTable.get(lsn);
        return useLock;
    }

    abstract LockAttemptResult attemptLock(Long lsn,
                                           Locker locker,
                                           LockType type,
                                           boolean nonBlockingRequest,
                                           boolean jumpAheadOfWaiters)
        throws DatabaseException;

    LockAttemptResult attemptLockInternal(Long lsn,
                                          Locker locker,
                                          LockType type,
                                          boolean nonBlockingRequest,
                                          boolean jumpAheadOfWaiters,
                                          int lockTableIndex)
        throws DatabaseException {

        nRequests.increment();

        /* Get the target lock. */
        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock useLock = lockTable.get(lsn);
        if (useLock == null) {
            useLock = new ThinLockImpl();
            lockTable.put(lsn, useLock);
            memoryBudget.updateLockMemoryUsage
                (TOTAL_THINLOCKIMPL_OVERHEAD, lockTableIndex);
        }

        /*
         * Attempt to lock.  Possible return values are NEW, PROMOTION, DENIED,
         * EXISTING, WAIT_NEW, WAIT_PROMOTION, WAIT_RESTART.
         */
        LockAttemptResult lar = useLock.lock
            (type, locker, nonBlockingRequest, jumpAheadOfWaiters,
             memoryBudget, lockTableIndex);
        if (lar.useLock != useLock) {
            /* The lock mutated from ThinLockImpl to LockImpl. */
            useLock = lar.useLock;
            lockTable.put(lsn, useLock);
            /* We still have the overhead of the hashtable (locktable). */
            memoryBudget.updateLockMemoryUsage
                (THINLOCK_MUTATE_OVERHEAD, lockTableIndex);
        }
        LockGrantType lockGrant = lar.lockGrant;
        boolean success = false;

        /* Was the attempt successful? */
        if ((lockGrant == LockGrantType.NEW) ||
            (lockGrant == LockGrantType.PROMOTION)) {
            locker.addLock(lsn, type, lockGrant);
            success = true;
        } else if (lockGrant == LockGrantType.EXISTING) {
            success = true;
        } else if (lockGrant == LockGrantType.DENIED) {
            /* Locker.lock will throw LockNotAvailableException. */
        } else {
            nWaits.increment();
        }
        return new LockAttemptResult(useLock, lockGrant, success);
    }

    /**
     * Create a informative lock or txn timeout message.
     */
    abstract LockConflictException makeTimeoutMsg(boolean isLockNotTxnTimeout,
                                                  Locker locker,
                                                  long lsn,
                                                  LockType type,
                                                  LockGrantType grantType,
                                                  Lock useLock,
                                                  long timeout,
                                                  long start,
                                                  long now,
                                                  DatabaseImpl database)
        throws DatabaseException;

    /**
     * Do the real work of creating an lock or txn timeout message.
     */
    LockConflictException makeTimeoutMsgInternal(boolean isLockNotTxnTimeout,
                                                 Locker locker,
                                                 long lsn,
                                                 LockType type,
                                                 LockGrantType grantType,
                                                 Lock useLock,
                                                 long timeout,
                                                 long start,
                                                 long now,
                                                 DatabaseImpl database) {

        /*
         * Because we're accessing parts of the lock, need to have protected
         * access to the lock table because things can be changing out from
         * underneath us.  This is a big hammer to grab for so long while we
         * traverse the graph, but it's only when we have a deadlock and we're
         * creating a debugging message.
         *
         * The alternative would be to handle ConcurrentModificationExceptions
         * and retry until none of them happen.
         */
        if (lockTableDump) {
            System.out.println("++++++++++ begin lock table dump ++++++++++");
            for (int i = 0; i < nLockTables; i++) {
                boolean success = false;
                for (int j = 0; j < 3 && !success; j++) {
                    try {
                        StringBuilder sb = new StringBuilder();
                        dumpToStringNoLatch(sb, i);
                        System.out.println(sb.toString());
                        success = true;
                        break; // for j...
                    } catch (ConcurrentModificationException CME) {
                        continue;
                    }
                }
                if (!success) {
                    System.out.println("Couldn't dump locktable " + i);
                }
            }
            System.out.println("++++++++++ end lock table dump ++++++++++");
        }

        StringBuilder sb = new StringBuilder();
        sb.append(isLockNotTxnTimeout ? "Lock" : "Transaction");
        sb.append(" expired. Locker ").append(locker);
        sb.append(": waited for lock");

        if (database != null) {
            sb.append(" on database=").append(database.getDebugName());
        }
        sb.append(" LockAddr:").append(System.identityHashCode(useLock));
        sb.append(" LSN=").append(DbLsn.getNoFormatString(lsn));
        sb.append(" type=").append(type);
        sb.append(" grant=").append(grantType);
        sb.append(" timeoutMillis=").append(timeout);
        sb.append(" startTime=").append(start);
        sb.append(" endTime=").append(now);
        Set<LockInfo> owners = useLock.getOwnersClone();
        List<LockInfo> waiters = useLock.getWaitersListClone();
        sb.append("\nOwners: ").append(owners);
        sb.append("\nWaiters: ").append(waiters).append("\n");
        StringBuilder deadlockInfo = findDeadlock(useLock, locker);
        if (deadlockInfo != null) {
            sb.append(deadlockInfo);
        }
        LockConflictException ret = isLockNotTxnTimeout ?
            newLockTimeoutException(locker, sb.toString()) :
            newTxnTimeoutException(locker, sb.toString());

        ret.setOwnerTxnIds(getTxnIds(owners));
        ret.setWaiterTxnIds(getTxnIds(waiters));
        ret.setTimeoutMillis(timeout);
        return ret;
    }

    private long[] getTxnIds(Collection<LockInfo> c) {
        long[] ret = new long[c.size()];
        Iterator<LockInfo> iter = c.iterator();
        int i = 0;
        while (iter.hasNext()) {
            LockInfo info = iter.next();
            ret[i++] = info.getLocker().getId();
        }

        return ret;
    }

    /**
     * This method should always be called instead of explicitly creating
     * TransactionTimeoutException, to ensure that je.lock.oldLockExceptions is
     * enforced.
     */
    private LockConflictException newTxnTimeoutException(Locker locker,
                                                         String msg) {
        return oldLockExceptions ?
            new DeadlockException(locker, msg) :
            new TransactionTimeoutException(locker, msg);
    }

    /**
     * This method should always be called instead of explicitly creating
     * LockTimeoutException, to ensure that je.lock.oldLockExceptions is
     * enforced.
     */
    private LockConflictException newLockTimeoutException(Locker locker,
                                                          String msg) {
        return oldLockExceptions ?
            new DeadlockException(locker, msg) :
            new LockTimeoutException(locker, msg);
    }

    /**
     * This method should always be called instead of explicitly creating
     * LockNotAvailableException, to ensure that je.lock.oldLockExceptions is
     * enforced.
     */
    LockConflictException newLockNotAvailableException(Locker locker,
                                                       String msg) {
        return oldLockExceptions ?
            new LockNotGrantedException(locker, msg) :
            new LockNotAvailableException(locker, msg);
    }

    /**
     * Release a lock and possibly notify any waiters that they have been
     * granted the lock.
     *
     * @param lsn The LSN of the lock to release.
     *
     * @return true if the lock is released successfully, false if
     * the lock is not currently being held.
     */
    public boolean release(long lsn, Locker locker)
        throws DatabaseException {

        synchronized (locker) {
            Set<Locker> newOwners =
                releaseAndFindNotifyTargets(lsn, locker);

            if (newOwners == null) {
                return false;
            }

            if (newOwners.size() > 0) {

                /*
                 * There is a new set of owners and/or there are restart
                 * waiters that should be notified.
                 */
                Iterator<Locker> iter = newOwners.iterator();

                while (iter.hasNext()) {
                    Locker lockerToNotify = iter.next();

                    /* Use notifyAll to support multiple threads per txn. */
                    synchronized (lockerToNotify) {
                        lockerToNotify.notifyAll();
                    }

                    assert EnvironmentImpl.maybeForceYield();
                }
            }

            return true;
        }
    }

    /**
     * Release the lock, and return the set of new owners to notify, if any.
     *
     * @return
     * null if the lock does not exist or the given locker was not the owner,
     * a non-empty set if owners should be notified after releasing,
     * an empty set if no notification is required.
     */
    abstract Set<Locker> releaseAndFindNotifyTargets(long lsn,
                                                     Locker locker)
        throws DatabaseException;

    /**
     * Do the real work of releaseAndFindNotifyTargets
     */
    Set<Locker> releaseAndFindNotifyTargetsInternal(long lsn,
                                                    Locker locker,
                                                    int lockTableIndex) {
        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock useLock = lockTable.get(lsn);
        if (useLock == null) {
            useLock = lockTable.get(Long.valueOf(lsn));
        }

        if (useLock == null) {
            /* Lock doesn't exist. */
            return null;
        }

        Set<Locker> lockersToNotify =
            useLock.release(locker, memoryBudget, lockTableIndex);
        if (lockersToNotify == null) {
            /* Not owner. */
            return null;
        }

        /* If it's not in use at all, remove it from the lock table. */
        if ((useLock.nWaiters() == 0) &&
            (useLock.nOwners() == 0)) {
            lockTables[lockTableIndex].remove(lsn);
            if (useLock.isThin()) {
                memoryBudget.updateLockMemoryUsage
                    (REMOVE_TOTAL_THINLOCKIMPL_OVERHEAD, lockTableIndex);
            } else {
                memoryBudget.updateLockMemoryUsage
                    (REMOVE_TOTAL_LOCKIMPL_OVERHEAD, lockTableIndex);
            }
        }

        return lockersToNotify;
    }

    /**
     * Demote a lock from write to read. Call back to the owning locker to
     * move this to its read collection.
     * @param lock The lock to release. If null, use LSN to find lock
     * @param locker
     */
    abstract void demote(long lsn, Locker locker)
        throws DatabaseException;

    /**
     * Do the real work of demote.
     */
    void demoteInternal(long lsn, Locker locker, int lockTableIndex) {
        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock useLock = lockTable.get(Long.valueOf(lsn));
        /* Lock may or may not be currently held. */
        if (useLock != null) {
            useLock.demote(locker);
            locker.moveWriteToReadLock(lsn, useLock);
        }
    }

    /**
     * Test the status of the lock on LSN.  If any transaction holds any
     * lock on it, true is returned.  If no transaction holds a lock on it,
     * false is returned.
     *
     * This method is only used by unit tests.
     *
     * @param lsn The LSN to check.
     * @return true if any transaction holds any lock on the LSN. false
     * if no lock is held by any transaction.
     */
    abstract boolean isLocked(Long lsn)
        throws DatabaseException;

    /**
     * Do the real work of isLocked.
     */
    boolean isLockedInternal(Long lsn, int lockTableIndex) {

        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock entry = lockTable.get(lsn);
        if (entry == null) {
            return false;
        }

        return entry.nOwners() != 0;
    }

    /**
     * Return true if this locker owns this a lock of this type on given node.
     *
     * This method is only used by unit tests.
     */
    abstract boolean isOwner(Long lsn, Locker locker, LockType type)
        throws DatabaseException;

    /**
     * Do the real work of isOwner.
     */
    boolean isOwnerInternal(Long lsn,
                            Locker locker,
                            LockType type,
                            int lockTableIndex) {

        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock entry = lockTable.get(lsn);
        if (entry == null) {
            return false;
        }

        return entry.isOwner(locker, type);
    }

    /**
     * Return true if this locker is waiting on this lock.
     *
     * This method is only used by unit tests.
     */
    abstract boolean isWaiter(Long lsn, Locker locker)
        throws DatabaseException;

    /**
     * Do the real work of isWaiter.
     */
    boolean isWaiterInternal(Long lsn,
                             Locker locker,
                             int lockTableIndex) {

        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock entry = lockTable.get(lsn);
        if (entry == null) {
            return false;
        }

        return entry.isWaiter(locker);
    }

    /**
     * Return the number of waiters for this lock.
     */
    abstract int nWaiters(Long lsn)
        throws DatabaseException;

    /**
     * Do the real work of nWaiters.
     */
    int nWaitersInternal(Long lsn, int lockTableIndex) {

        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock entry = lockTable.get(lsn);
        if (entry == null) {
            return -1;
        }

        return entry.nWaiters();
    }

    /**
     * Return the number of owners of this lock.
     */
    abstract int nOwners(Long lsn)
        throws DatabaseException;

    /**
     * Do the real work of nWaiters.
     */
    int nOwnersInternal(Long lsn, int lockTableIndex) {

        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock entry = lockTable.get(lsn);
        if (entry == null) {
            return -1;
        }

        return entry.nOwners();
    }

    /**
     * @return the transaction that owns the write lock for this
     */
    abstract Locker getWriteOwnerLocker(Long lsn)
        throws DatabaseException;

    /**
     * Do the real work of getWriteOwnerLocker.
     */
    Locker getWriteOwnerLockerInternal(Long lsn, int lockTableIndex) {
        Map<Long,Lock> lockTable = lockTables[lockTableIndex];
        Lock lock = lockTable.get(lsn);
        if (lock == null) {
            return null;
        } else if (lock.nOwners() > 1) {
            /* not a write lock */
            return null;
        } else {
            return lock.getWriteOwnerLocker();
        }
    }

    /*
     * Check if we got ownership while we were waiting.  If we didn't get
     * ownership, and we timed out, remove this locker from the set of
     * waiters. Do this in a critical section to prevent any orphaning of the
     * lock -- we must be in a critical section between the time that we check
     * ownership and when we flush any waiters (SR #10103)
     * @return true if you are the owner.
     */
    abstract boolean validateOwnership(Long lsn,
                                       Locker locker,
                                       LockType type,
                                       boolean flushFromWaiters,
                                       MemoryBudget mb)
        throws DatabaseException;

    /*
     * Do the real work of validateOwnershipInternal.
     */
    boolean validateOwnershipInternal(Long lsn,
                                      Locker locker,
                                      LockType type,
                                      boolean flushFromWaiters,
                                      MemoryBudget mb,
                                      int lockTableIndex) {
        if (isOwnerInternal(lsn, locker, type, lockTableIndex)) {
            return true;
        }

        if (flushFromWaiters) {
            Lock entry = lockTables[lockTableIndex].get(lsn);
            if (entry != null) {
                entry.flushWaiter(locker, mb, lockTableIndex);
            }
        }
        return false;
    }

    abstract protected LockAttemptResult stealLock(Long lsn,
                                                   Locker locker,
                                                   LockType lockType,
                                                   MemoryBudget mb)
        throws DatabaseException;

    protected LockAttemptResult stealLockInternal(Long lsn,
                                                  Locker locker,
                                                  LockType lockType,
                                                  MemoryBudget mb,
                                                  int lockTableIndex)
        throws DatabaseException {

        Lock entry = lockTables[lockTableIndex].get(lsn);
        assert entry != null;

        /*
         * Note that flushWaiter may do nothing, because the lock may have been
         * granted to our locker after the prior call to attemptLock and before
         * the call to this method.
         */
        entry.flushWaiter(locker, mb, lockTableIndex);

        /* Remove all owners except for our owner. */
        entry.stealLock(locker, mb, lockTableIndex);

        /*
         * The lock attempt normally succeeds, but can fail if the lock holder
         * is non-preemptable.
         */
        return attemptLockInternal
            (lsn, locker, lockType, false /*nonBlockingRequest*/,
             false /*jumpAheadOfWaiters*/, lockTableIndex);
    }

    /**
     * Called when a ThreadLocker is created.
     */
    public void registerThreadLocker(final ThreadLocker locker) {
        if (threadLockers == null) {
            return;
        }
        final Thread thread = Thread.currentThread();
        final TinyHashSet<ThreadLocker> set = threadLockers.get(thread);
        if (set != null) {
            final boolean added = set.add(locker);
            assert added;
        } else {
            threadLockers.put(thread, new TinyHashSet(locker));
        }
    }

    /**
     * Called when a ThreadLocker is closed.
     */
    public void unregisterThreadLocker(final ThreadLocker locker) {
        if (threadLockers == null) {
            return;
        }
        final Thread thread = Thread.currentThread();
        final TinyHashSet<ThreadLocker> set = threadLockers.get(thread);
        assert set != null;
        final boolean removed = set.remove(locker);
        assert removed;
        if (threadLockers.size() == 0) {
            threadLockers.remove(thread);
        }
    }

    /**
     * Returns an iterator over all thread lockers for the given thread, or
     * an empty iterator if none.
     */
    public Iterator<ThreadLocker> getThreadLockers(final Thread thread) {
        if (threadLockers == null) {
            return EMPTY_THREAD_LOCKERS.iterator();
        }
        final TinyHashSet<ThreadLocker> set = threadLockers.get(thread);
        if (set == null) {
            return EMPTY_THREAD_LOCKERS.iterator();
        }
        return set.iterator();
    }

    /**
     * Statistics
     */
    public LockStats lockStat(StatsConfig config)
        throws DatabaseException {

        StatGroup latchStats = new StatGroup("Locktable latches",
                                             "Shows lock table contention");
        for (int i = 0; i < nLockTables; i++) {
            latchStats.addAll(lockTableLatches[i].getLatchStats());
        }

        /* Dump info about the lock table. */
        StatGroup tableStats =
            new StatGroup("Locktable",
                          "The types of locks held in the lock table");
        if (!config.getFast()) {
            dumpLockTable(tableStats, false /*clear*/);
        }
       
        return new LockStats(stats.cloneGroup(config.getClear()),
                             latchStats.cloneGroup(config.getClear()),
                             tableStats.cloneGroup(config.getClear()));
    }

    public StatGroup loadStats(StatsConfig config) {
        StatGroup copyStats = stats.cloneGroup(config.getClear());

        StatGroup latchStats = new StatGroup("Locktable latches",
                                             "Shows lock table contention");
        for (int i = 0; i < nLockTables; i++) {
            latchStats.addAll(lockTableLatches[i].getLatchStats());
            if (config.getClear()) {
                lockTableLatches[i].clear();
            }
        }
        /* Add all the latch stats to the whole stats group. */
        copyStats.addAll(latchStats);

        StatGroup tableStats =
            new StatGroup("Locktable",
                          "The types of locks held in the lock table");
        if (!config.getFast()) {
            dumpLockTable(tableStats, config.getClear());
        }
        /* Add all the lock table stats to the whole stats group. */
        copyStats.addAll(tableStats);
       
        return copyStats;
    }

    /**
     * Dump the lock table to the lock stats.
     */
    abstract void dumpLockTable(StatGroup tableStats, boolean clear)
        throws DatabaseException;

    /**
     * Do the real work of dumpLockTableInternal.
     */
    void dumpLockTableInternal(StatGroup tableStats, int i, boolean clear) {
        StatGroup oneTable = new StatGroup("Single lock table",
                                           "Temporary stat group");

        IntStat totalLocks = new IntStat(oneTable, LOCK_TOTAL);
        IntStat waiters = new IntStat(oneTable, LOCK_WAITERS);
        IntStat owners = new IntStat(oneTable, LOCK_OWNERS);
        IntStat readLocks = new IntStat(oneTable, LOCK_READ_LOCKS);
        IntStat writeLocks = new IntStat(oneTable, LOCK_WRITE_LOCKS);

        Map<Long, Lock> lockTable = lockTables[i];
        totalLocks.add(lockTable.size());

        for (Lock lock : lockTable.values()) {
            waiters.add(lock.nWaiters());
            owners.add(lock.nOwners());

            /* Go through all the owners for a lock. */
            for (LockInfo info : lock.getOwnersClone()) {
                if (info.getLockType().isWriteLock()) {
                    writeLocks.increment();
                } else {
                    readLocks.increment();
                }
            }
        }
        tableStats.addAll(oneTable);
    }

    /**
     * Debugging
     */
    public void dump()
        throws DatabaseException {

        System.out.println(dumpToString());
    }

    public String dumpToString()
        throws DatabaseException {

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < nLockTables; i++) {
            lockTableLatches[i].acquire();
            try {
                dumpToStringNoLatch(sb, i);
            } finally {
                lockTableLatches[i].release();
            }
        }
        return sb.toString();
    }

    private void dumpToStringNoLatch(StringBuilder sb, int whichTable) {
        Map<Long,Lock> lockTable = lockTables[whichTable];
        Iterator<Map.Entry<Long,Lock>> entries =
            lockTable.entrySet().iterator();

        while (entries.hasNext()) {
            Map.Entry<Long,Lock> entry = entries.next();
            Long lsn = entry.getKey();
            Lock lock = entry.getValue();
            sb.append("---- LSN: ").
               append(DbLsn.getNoFormatString(lsn)).
               append("----\n");
            sb.append(lock);
            sb.append('\n');
        }
    }

    private boolean checkNoLatchesHeld(boolean nonBlockingRequest) {
        if (nonBlockingRequest) {
            return true; // don't check if it's a non blocking request.
        } else {
            return (LatchSupport.countLatchesHeld() == 0);
        }
    }

    private StringBuilder findDeadlock(Lock lock, Locker rootLocker) {

        Set<Locker> ownerSet = new HashSet<Locker>();
        ownerSet.add(rootLocker);
        StringBuilder ret = findDeadlock1(ownerSet, lock, rootLocker);
        if (ret != null) {
            return ret;
        } else {
            return null;
        }
    }

    private StringBuilder findDeadlock1(Set<Locker> ownerSet,
                                       Lock lock,
                                       Locker rootLocker) {

        Iterator<LockInfo> ownerIter = lock.getOwnersClone().iterator();
        while (ownerIter.hasNext()) {
            LockInfo info = ownerIter.next();
            Locker locker = info.getLocker();
            Lock waitsFor = locker.getWaitingFor();
            if (ownerSet.contains(locker) ||
                locker == rootLocker) {
                /* Found a cycle. */
                StringBuilder ret = new StringBuilder();
                ret.append("Transaction ").append(locker.toString());
                ret.append(" owns LockAddr:").
                    append(System.identityHashCode(lock));
                ret.append(" ").append(info).append("\n");
                ret.append("Transaction ").append(locker.toString());
                ret.append(" waits for");
                if (waitsFor == null) {
                    ret.append(" nothing");
                } else {
                    ret.append(" LockAddr:");
                    ret.append(System.identityHashCode(waitsFor));
                }
                ret.append("\n");
                return ret;
            }
            if (waitsFor != null) {
                ownerSet.add(locker);
                StringBuilder sb = findDeadlock1(ownerSet, waitsFor,
                                                rootLocker);
                if (sb != null) {
                    String waitInfo =
                        "Transaction " + locker + " waits for " +
                        waitsFor + "\n";
                    sb.insert(0, waitInfo);
                    return sb;
                }
                ownerSet.remove(locker); // is this necessary?
            }
        }

        return null;
    }
}
TOP

Related Classes of com.sleepycat.je.txn.LockManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.