diff options
Diffstat (limited to 'guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java')
-rw-r--r-- | guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java | 1038 |
1 files changed, 0 insertions, 1038 deletions
diff --git a/guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java b/guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java deleted file mode 100644 index 528fc8e..0000000 --- a/guava/src/com/google/common/util/concurrent/CycleDetectingLockFactory.java +++ /dev/null @@ -1,1038 +0,0 @@ -/* - * Copyright (C) 2011 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.util.concurrent; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.common.annotations.Beta; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; -import com.google.common.collect.MapMaker; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.ThreadSafe; - -/** - * The {@code CycleDetectingLockFactory} creates {@link ReentrantLock} instances and - * {@link ReentrantReadWriteLock} instances that detect potential deadlock by checking - * for cycles in lock acquisition order. - * <p> - * Potential deadlocks detected when calling the {@code lock()}, - * {@code lockInterruptibly()}, or {@code tryLock()} methods will result in the - * execution of the {@link Policy} specified when creating the factory. The - * currently available policies are: - * <ul> - * <li>DISABLED - * <li>WARN - * <li>THROW - * </ul> - * The locks created by a factory instance will detect lock acquisition cycles - * with locks created by other {@code CycleDetectingLockFactory} instances - * (except those with {@code Policy.DISABLED}). A lock's behavior when a cycle - * is detected, however, is defined by the {@code Policy} of the factory that - * created it. This allows detection of cycles across components while - * delegating control over lock behavior to individual components. - * <p> - * Applications are encouraged to use a {@code CycleDetectingLockFactory} to - * create any locks for which external/unmanaged code is executed while the lock - * is held. (See caveats under <strong>Performance</strong>). - * <p> - * <strong>Cycle Detection</strong> - * <p> - * Deadlocks can arise when locks are acquired in an order that forms a cycle. - * In a simple example involving two locks and two threads, deadlock occurs - * when one thread acquires Lock A, and then Lock B, while another thread - * acquires Lock B, and then Lock A: - * <pre> - * Thread1: acquire(LockA) --X acquire(LockB) - * Thread2: acquire(LockB) --X acquire(LockA) - * </pre> - * Neither thread will progress because each is waiting for the other. In more - * complex applications, cycles can arise from interactions among more than 2 - * locks: - * <pre> - * Thread1: acquire(LockA) --X acquire(LockB) - * Thread2: acquire(LockB) --X acquire(LockC) - * ... - * ThreadN: acquire(LockN) --X acquire(LockA) - * </pre> - * The implementation detects cycles by constructing a directed graph in which - * each lock represents a node and each edge represents an acquisition ordering - * between two locks. - * <ul> - * <li>Each lock adds (and removes) itself to/from a ThreadLocal Set of acquired - * locks when the Thread acquires its first hold (and releases its last - * remaining hold). - * <li>Before the lock is acquired, the lock is checked against the current set - * of acquired locks---to each of the acquired locks, an edge from the - * soon-to-be-acquired lock is either verified or created. - * <li>If a new edge needs to be created, the outgoing edges of the acquired - * locks are traversed to check for a cycle that reaches the lock to be - * acquired. If no cycle is detected, a new "safe" edge is created. - * <li>If a cycle is detected, an "unsafe" (cyclic) edge is created to represent - * a potential deadlock situation, and the appropriate Policy is executed. - * </ul> - * Note that detection of potential deadlock does not necessarily indicate that - * deadlock will happen, as it is possible that higher level application logic - * prevents the cyclic lock acquisition from occurring. One example of a false - * positive is: - * <pre> - * LockA -> LockB -> LockC - * LockA -> LockC -> LockB - * </pre> - * - * <strong>ReadWriteLocks</strong> - * <p> - * While {@code ReadWriteLock} instances have different properties and can form cycles - * without potential deadlock, this class treats {@code ReadWriteLock} instances as - * equivalent to traditional exclusive locks. Although this increases the false - * positives that the locks detect (i.e. cycles that will not actually result in - * deadlock), it simplifies the algorithm and implementation considerably. The - * assumption is that a user of this factory wishes to eliminate any cyclic - * acquisition ordering. - * <p> - * <strong>Explicit Lock Acquisition Ordering</strong> - * <p> - * The {@link CycleDetectingLockFactory.WithExplicitOrdering} class can be used - * to enforce an application-specific ordering in addition to performing general - * cycle detection. - * <p> - * <strong>Garbage Collection</strong> - * <p> - * In order to allow proper garbage collection of unused locks, the edges of - * the lock graph are weak references. - * <p> - * <strong>Performance</strong> - * <p> - * The extra bookkeeping done by cycle detecting locks comes at some cost to - * performance. Benchmarks (as of December 2011) show that: - * - * <ul> - * <li>for an unnested {@code lock()} and {@code unlock()}, a cycle detecting - * lock takes 38ns as opposed to the 24ns taken by a plain lock. - * <li>for nested locking, the cost increases with the depth of the nesting: - * <ul> - * <li> 2 levels: average of 64ns per lock()/unlock() - * <li> 3 levels: average of 77ns per lock()/unlock() - * <li> 4 levels: average of 99ns per lock()/unlock() - * <li> 5 levels: average of 103ns per lock()/unlock() - * <li>10 levels: average of 184ns per lock()/unlock() - * <li>20 levels: average of 393ns per lock()/unlock() - * </ul> - * </ul> - * - * As such, the CycleDetectingLockFactory may not be suitable for - * performance-critical applications which involve tightly-looped or - * deeply-nested locking algorithms. - * - * @author Darick Tong - * @since 13.0 - */ -@Beta -@ThreadSafe -public class CycleDetectingLockFactory { - - /** - * Encapsulates the action to be taken when a potential deadlock is - * encountered. Clients can use one of the predefined {@link Policies} or - * specify a custom implementation. Implementations must be thread-safe. - * - * @since 13.0 - */ - @Beta - @ThreadSafe - public interface Policy { - - /** - * Called when a potential deadlock is encountered. Implementations can - * throw the given {@code exception} and/or execute other desired logic. - * <p> - * Note that the method will be called even upon an invocation of - * {@code tryLock()}. Although {@code tryLock()} technically recovers from - * deadlock by eventually timing out, this behavior is chosen based on the - * assumption that it is the application's wish to prohibit any cyclical - * lock acquisitions. - */ - void handlePotentialDeadlock(PotentialDeadlockException exception); - } - - /** - * Pre-defined {@link Policy} implementations. - * - * @since 13.0 - */ - @Beta - public enum Policies implements Policy { - /** - * When potential deadlock is detected, this policy results in the throwing - * of the {@code PotentialDeadlockException} indicating the potential - * deadlock, which includes stack traces illustrating the cycle in lock - * acquisition order. - */ - THROW { - @Override - public void handlePotentialDeadlock(PotentialDeadlockException e) { - throw e; - } - }, - - /** - * When potential deadlock is detected, this policy results in the logging - * of a {@link Level#SEVERE} message indicating the potential deadlock, - * which includes stack traces illustrating the cycle in lock acquisition - * order. - */ - WARN { - @Override - public void handlePotentialDeadlock(PotentialDeadlockException e) { - logger.log(Level.SEVERE, "Detected potential deadlock", e); - } - }, - - /** - * Disables cycle detection. This option causes the factory to return - * unmodified lock implementations provided by the JDK, and is provided to - * allow applications to easily parameterize when cycle detection is - * enabled. - * <p> - * Note that locks created by a factory with this policy will <em>not</em> - * participate the cycle detection performed by locks created by other - * factories. - */ - DISABLED { - @Override - public void handlePotentialDeadlock(PotentialDeadlockException e) { - } - }; - } - - /** - * Creates a new factory with the specified policy. - */ - public static CycleDetectingLockFactory newInstance(Policy policy) { - return new CycleDetectingLockFactory(policy); - } - - /** - * Equivalent to {@code newReentrantLock(lockName, false)}. - */ - public ReentrantLock newReentrantLock(String lockName) { - return newReentrantLock(lockName, false); - } - - /** - * Creates a {@link ReentrantLock} with the given fairness policy. The - * {@code lockName} is used in the warning or exception output to help - * identify the locks involved in the detected deadlock. - */ - public ReentrantLock newReentrantLock(String lockName, boolean fair) { - return policy == Policies.DISABLED ? new ReentrantLock(fair) - : new CycleDetectingReentrantLock( - new LockGraphNode(lockName), fair); - } - - /** - * Equivalent to {@code newReentrantReadWriteLock(lockName, false)}. - */ - public ReentrantReadWriteLock newReentrantReadWriteLock(String lockName) { - return newReentrantReadWriteLock(lockName, false); - } - - /** - * Creates a {@link ReentrantReadWriteLock} with the given fairness policy. - * The {@code lockName} is used in the warning or exception output to help - * identify the locks involved in the detected deadlock. - */ - public ReentrantReadWriteLock newReentrantReadWriteLock( - String lockName, boolean fair) { - return policy == Policies.DISABLED ? new ReentrantReadWriteLock(fair) - : new CycleDetectingReentrantReadWriteLock( - new LockGraphNode(lockName), fair); - } - - // A static mapping from an Enum type to its set of LockGraphNodes. - private static final Map<Class<? extends Enum>, - Map<? extends Enum, LockGraphNode>> lockGraphNodesPerType = - new MapMaker().weakKeys().makeComputingMap( - new OrderedLockGraphNodesCreator()); - - /** - * Creates a {@code CycleDetectingLockFactory.WithExplicitOrdering<E>}. - */ - public static <E extends Enum<E>> WithExplicitOrdering<E> - newInstanceWithExplicitOrdering(Class<E> enumClass, Policy policy) { - // OrderedLockGraphNodesCreator maps each enumClass to a Map with the - // corresponding enum key type. - checkNotNull(enumClass); - checkNotNull(policy); - @SuppressWarnings("unchecked") - Map<E, LockGraphNode> lockGraphNodes = - (Map<E, LockGraphNode>) lockGraphNodesPerType.get(enumClass); - return new WithExplicitOrdering<E>(policy, lockGraphNodes); - } - - /** - * A {@code CycleDetectingLockFactory.WithExplicitOrdering} provides the - * additional enforcement of an application-specified ordering of lock - * acquisitions. The application defines the allowed ordering with an - * {@code Enum} whose values each correspond to a lock type. The order in - * which the values are declared dictates the allowed order of lock - * acquisition. In other words, locks corresponding to smaller values of - * {@link Enum#ordinal()} should only be acquired before locks with larger - * ordinals. Example: - * - * <pre> {@code - * enum MyLockOrder { - * FIRST, SECOND, THIRD; - * } - * - * CycleDetectingLockFactory.WithExplicitOrdering<MyLockOrder> factory = - * CycleDetectingLockFactory.newInstanceWithExplicitOrdering(Policies.THROW); - * - * Lock lock1 = factory.newReentrantLock(MyLockOrder.FIRST); - * Lock lock2 = factory.newReentrantLock(MyLockOrder.SECOND); - * Lock lock3 = factory.newReentrantLock(MyLockOrder.THIRD); - * - * lock1.lock(); - * lock3.lock(); - * lock2.lock(); // will throw an IllegalStateException - * }</pre> - * - * As with all locks created by instances of {@code CycleDetectingLockFactory} - * explicitly ordered locks participate in general cycle detection with all - * other cycle detecting locks, and a lock's behavior when detecting a cyclic - * lock acquisition is defined by the {@code Policy} of the factory that - * created it. - * <p> - * Note, however, that although multiple locks can be created for a given Enum - * value, whether it be through separate factory instances or through multiple - * calls to the same factory, attempting to acquire multiple locks with the - * same Enum value (within the same thread) will result in an - * IllegalStateException regardless of the factory's policy. For example: - * - * <pre> {@code - * CycleDetectingLockFactory.WithExplicitOrdering<MyLockOrder> factory1 = - * CycleDetectingLockFactory.newInstanceWithExplicitOrdering(...); - * CycleDetectingLockFactory.WithExplicitOrdering<MyLockOrder> factory2 = - * CycleDetectingLockFactory.newInstanceWithExplicitOrdering(...); - * - * Lock lockA = factory1.newReentrantLock(MyLockOrder.FIRST); - * Lock lockB = factory1.newReentrantLock(MyLockOrder.FIRST); - * Lock lockC = factory2.newReentrantLock(MyLockOrder.FIRST); - * - * lockA.lock(); - * - * lockB.lock(); // will throw an IllegalStateException - * lockC.lock(); // will throw an IllegalStateException - * - * lockA.lock(); // reentrant acquisition is okay - * }</pre> - * - * It is the responsibility of the application to ensure that multiple lock - * instances with the same rank are never acquired in the same thread. - * - * @param <E> The Enum type representing the explicit lock ordering. - * @since 13.0 - */ - @Beta - public static final class WithExplicitOrdering<E extends Enum<E>> - extends CycleDetectingLockFactory { - - private final Map<E, LockGraphNode> lockGraphNodes; - - @VisibleForTesting - WithExplicitOrdering( - Policy policy, Map<E, LockGraphNode> lockGraphNodes) { - super(policy); - this.lockGraphNodes = lockGraphNodes; - } - - /** - * Equivalent to {@code newReentrantLock(rank, false)}. - */ - public ReentrantLock newReentrantLock(E rank) { - return newReentrantLock(rank, false); - } - - /** - * Creates a {@link ReentrantLock} with the given fairness policy and rank. - * The values returned by {@link Enum#getDeclaringClass()} and - * {@link Enum#name()} are used to describe the lock in warning or - * exception output. - * - * @throws IllegalStateException If the factory has already created a - * {@code Lock} with the specified rank. - */ - public ReentrantLock newReentrantLock(E rank, boolean fair) { - return policy == Policies.DISABLED ? new ReentrantLock(fair) - : new CycleDetectingReentrantLock(lockGraphNodes.get(rank), fair); - } - - /** - * Equivalent to {@code newReentrantReadWriteLock(rank, false)}. - */ - public ReentrantReadWriteLock newReentrantReadWriteLock(E rank) { - return newReentrantReadWriteLock(rank, false); - } - - /** - * Creates a {@link ReentrantReadWriteLock} with the given fairness policy - * and rank. The values returned by {@link Enum#getDeclaringClass()} and - * {@link Enum#name()} are used to describe the lock in warning or exception - * output. - * - * @throws IllegalStateException If the factory has already created a - * {@code Lock} with the specified rank. - */ - public ReentrantReadWriteLock newReentrantReadWriteLock( - E rank, boolean fair) { - return policy == Policies.DISABLED ? new ReentrantReadWriteLock(fair) - : new CycleDetectingReentrantReadWriteLock( - lockGraphNodes.get(rank), fair); - } - } - - /** - * For a given Enum type, creates an immutable map from each of the Enum's - * values to a corresponding LockGraphNode, with the - * {@code allowedPriorLocks} and {@code disallowedPriorLocks} prepopulated - * with nodes according to the natural ordering of the associated Enum values. - */ - @VisibleForTesting - static class OrderedLockGraphNodesCreator - implements Function<Class<? extends Enum>, - Map<? extends Enum, LockGraphNode>> { - - @Override - @SuppressWarnings("unchecked") // There's no way to properly express with - // wildcards the recursive Enum type required by createNodesFor(), and the - // Map/Function types must use wildcards since they accept any Enum class. - public Map<? extends Enum, LockGraphNode> apply( - Class<? extends Enum> clazz) { - return createNodesFor(clazz); - } - - <E extends Enum<E>> Map<E, LockGraphNode> createNodesFor(Class<E> clazz) { - EnumMap<E, LockGraphNode> map = Maps.newEnumMap(clazz); - E[] keys = clazz.getEnumConstants(); - final int numKeys = keys.length; - ArrayList<LockGraphNode> nodes = - Lists.newArrayListWithCapacity(numKeys); - // Create a LockGraphNode for each enum value. - for (E key : keys) { - LockGraphNode node = new LockGraphNode(getLockName(key)); - nodes.add(node); - map.put(key, node); - } - // Pre-populate all allowedPriorLocks with nodes of smaller ordinal. - for (int i = 1; i < numKeys; i++) { - nodes.get(i).checkAcquiredLocks(Policies.THROW, nodes.subList(0, i)); - } - // Pre-populate all disallowedPriorLocks with nodes of larger ordinal. - for (int i = 0; i < numKeys - 1; i++) { - nodes.get(i).checkAcquiredLocks( - Policies.DISABLED, nodes.subList(i + 1, numKeys)); - } - return Collections.unmodifiableMap(map); - } - - /** - * For the given Enum value {@code rank}, returns the value's - * {@code "EnumClass.name"}, which is used in exception and warning - * output. - */ - private String getLockName(Enum<?> rank) { - return rank.getDeclaringClass().getSimpleName() + "." + rank.name(); - } - } - - //////// Implementation ///////// - - private static final Logger logger = Logger.getLogger( - CycleDetectingLockFactory.class.getName()); - - final Policy policy; - - private CycleDetectingLockFactory(Policy policy) { - this.policy = checkNotNull(policy); - } - - /** - * Tracks the currently acquired locks for each Thread, kept up to date by - * calls to {@link #aboutToAcquire(CycleDetectingLock)} and - * {@link #lockStateChanged(CycleDetectingLock)}. - */ - // This is logically a Set, but an ArrayList is used to minimize the amount - // of allocation done on lock()/unlock(). - private static final ThreadLocal<ArrayList<LockGraphNode>> - acquiredLocks = new ThreadLocal<ArrayList<LockGraphNode>>() { - @Override - protected ArrayList<LockGraphNode> initialValue() { - return Lists.<LockGraphNode>newArrayListWithCapacity(3); - } - }; - - /** - * A Throwable used to record a stack trace that illustrates an example of - * a specific lock acquisition ordering. The top of the stack trace is - * truncated such that it starts with the acquisition of the lock in - * question, e.g. - * - * <pre> - * com...ExampleStackTrace: LockB -> LockC - * at com...CycleDetectingReentrantLock.lock(CycleDetectingLockFactory.java:443) - * at ... - * at ... - * at com...MyClass.someMethodThatAcquiresLockB(MyClass.java:123) - * </pre> - */ - private static class ExampleStackTrace extends IllegalStateException { - - static final StackTraceElement[] EMPTY_STACK_TRACE = - new StackTraceElement[0]; - - static Set<String> EXCLUDED_CLASS_NAMES = ImmutableSet.of( - CycleDetectingLockFactory.class.getName(), - ExampleStackTrace.class.getName(), - LockGraphNode.class.getName()); - - ExampleStackTrace(LockGraphNode node1, LockGraphNode node2) { - super(node1.getLockName() + " -> " + node2.getLockName()); - StackTraceElement[] origStackTrace = getStackTrace(); - for (int i = 0, n = origStackTrace.length; i < n; i++) { - if (WithExplicitOrdering.class.getName().equals( - origStackTrace[i].getClassName())) { - // For pre-populated disallowedPriorLocks edges, omit the stack trace. - setStackTrace(EMPTY_STACK_TRACE); - break; - } - if (!EXCLUDED_CLASS_NAMES.contains(origStackTrace[i].getClassName())) { - setStackTrace(Arrays.copyOfRange(origStackTrace, i, n)); - break; - } - } - } - } - - /** - * Represents a detected cycle in lock acquisition ordering. The exception - * includes a causal chain of {@code ExampleStackTrace} instances to illustrate the - * cycle, e.g. - * - * <pre> - * com....PotentialDeadlockException: Potential Deadlock from LockC -> ReadWriteA - * at ... - * at ... - * Caused by: com...ExampleStackTrace: LockB -> LockC - * at ... - * at ... - * Caused by: com...ExampleStackTrace: ReadWriteA -> LockB - * at ... - * at ... - * </pre> - * - * Instances are logged for the {@code Policies.WARN}, and thrown for - * {@code Policies.THROW}. - * - * @since 13.0 - */ - @Beta - public static final class PotentialDeadlockException - extends ExampleStackTrace { - - private final ExampleStackTrace conflictingStackTrace; - - private PotentialDeadlockException( - LockGraphNode node1, - LockGraphNode node2, - ExampleStackTrace conflictingStackTrace) { - super(node1, node2); - this.conflictingStackTrace = conflictingStackTrace; - initCause(conflictingStackTrace); - } - - public ExampleStackTrace getConflictingStackTrace() { - return conflictingStackTrace; - } - - /** - * Appends the chain of messages from the {@code conflictingStackTrace} to - * the original {@code message}. - */ - @Override - public String getMessage() { - StringBuilder message = new StringBuilder(super.getMessage()); - for (Throwable t = conflictingStackTrace; t != null; t = t.getCause()) { - message.append(", ").append(t.getMessage()); - } - return message.toString(); - } - } - - /** - * Internal Lock implementations implement the {@code CycleDetectingLock} - * interface, allowing the detection logic to treat all locks in the same - * manner. - */ - private interface CycleDetectingLock { - - /** @return the {@link LockGraphNode} associated with this lock. */ - LockGraphNode getLockGraphNode(); - - /** @return {@code true} if the current thread has acquired this lock. */ - boolean isAcquiredByCurrentThread(); - } - - /** - * A {@code LockGraphNode} associated with each lock instance keeps track of - * the directed edges in the lock acquisition graph. - */ - private static class LockGraphNode { - - /** - * The map tracking the locks that are known to be acquired before this - * lock, each associated with an example stack trace. Locks are weakly keyed - * to allow proper garbage collection when they are no longer referenced. - */ - final Map<LockGraphNode, ExampleStackTrace> allowedPriorLocks = - new MapMaker().weakKeys().makeMap(); - - /** - * The map tracking lock nodes that can cause a lock acquisition cycle if - * acquired before this node. - */ - final Map<LockGraphNode, PotentialDeadlockException> - disallowedPriorLocks = new MapMaker().weakKeys().makeMap(); - - final String lockName; - - LockGraphNode(String lockName) { - this.lockName = Preconditions.checkNotNull(lockName); - } - - String getLockName() { - return lockName; - } - - void checkAcquiredLocks( - Policy policy, List<LockGraphNode> acquiredLocks) { - for (int i = 0, size = acquiredLocks.size(); i < size; i++) { - checkAcquiredLock(policy, acquiredLocks.get(i)); - } - } - - /** - * Checks the acquisition-ordering between {@code this}, which is about to - * be acquired, and the specified {@code acquiredLock}. - * <p> - * When this method returns, the {@code acquiredLock} should be in either - * the {@code preAcquireLocks} map, for the case in which it is safe to - * acquire {@code this} after the {@code acquiredLock}, or in the - * {@code disallowedPriorLocks} map, in which case it is not safe. - */ - void checkAcquiredLock(Policy policy, LockGraphNode acquiredLock) { - // checkAcquiredLock() should never be invoked by a lock that has already - // been acquired. For unordered locks, aboutToAcquire() ensures this by - // checking isAcquiredByCurrentThread(). For ordered locks, however, this - // can happen because multiple locks may share the same LockGraphNode. In - // this situation, throw an IllegalStateException as defined by contract - // described in the documentation of WithExplicitOrdering. - Preconditions.checkState( - this != acquiredLock, - "Attempted to acquire multiple locks with the same rank " + - acquiredLock.getLockName()); - - if (allowedPriorLocks.containsKey(acquiredLock)) { - // The acquisition ordering from "acquiredLock" to "this" has already - // been verified as safe. In a properly written application, this is - // the common case. - return; - } - PotentialDeadlockException previousDeadlockException = - disallowedPriorLocks.get(acquiredLock); - if (previousDeadlockException != null) { - // Previously determined to be an unsafe lock acquisition. - // Create a new PotentialDeadlockException with the same causal chain - // (the example cycle) as that of the cached exception. - PotentialDeadlockException exception = new PotentialDeadlockException( - acquiredLock, this, - previousDeadlockException.getConflictingStackTrace()); - policy.handlePotentialDeadlock(exception); - return; - } - // Otherwise, it's the first time seeing this lock relationship. Look for - // a path from the acquiredLock to this. - Set<LockGraphNode> seen = Sets.newIdentityHashSet(); - ExampleStackTrace path = acquiredLock.findPathTo(this, seen); - - if (path == null) { - // this can be safely acquired after the acquiredLock. - // - // Note that there is a race condition here which can result in missing - // a cyclic edge: it's possible for two threads to simultaneous find - // "safe" edges which together form a cycle. Preventing this race - // condition efficiently without _introducing_ deadlock is probably - // tricky. For now, just accept the race condition---missing a warning - // now and then is still better than having no deadlock detection. - allowedPriorLocks.put( - acquiredLock, new ExampleStackTrace(acquiredLock, this)); - } else { - // Unsafe acquisition order detected. Create and cache a - // PotentialDeadlockException. - PotentialDeadlockException exception = - new PotentialDeadlockException(acquiredLock, this, path); - disallowedPriorLocks.put(acquiredLock, exception); - policy.handlePotentialDeadlock(exception); - } - } - - /** - * Performs a depth-first traversal of the graph edges defined by each - * node's {@code allowedPriorLocks} to find a path between {@code this} and - * the specified {@code lock}. - * - * @return If a path was found, a chained {@link ExampleStackTrace} - * illustrating the path to the {@code lock}, or {@code null} if no path - * was found. - */ - @Nullable - private ExampleStackTrace findPathTo( - LockGraphNode node, Set<LockGraphNode> seen) { - if (!seen.add(this)) { - return null; // Already traversed this node. - } - ExampleStackTrace found = allowedPriorLocks.get(node); - if (found != null) { - return found; // Found a path ending at the node! - } - // Recurse the edges. - for (Map.Entry<LockGraphNode, ExampleStackTrace> entry : - allowedPriorLocks.entrySet()) { - LockGraphNode preAcquiredLock = entry.getKey(); - found = preAcquiredLock.findPathTo(node, seen); - if (found != null) { - // One of this node's allowedPriorLocks found a path. Prepend an - // ExampleStackTrace(preAcquiredLock, this) to the returned chain of - // ExampleStackTraces. - ExampleStackTrace path = - new ExampleStackTrace(preAcquiredLock, this); - path.setStackTrace(entry.getValue().getStackTrace()); - path.initCause(found); - return path; - } - } - return null; - } - } - - /** - * CycleDetectingLock implementations must call this method before attempting - * to acquire the lock. - */ - private void aboutToAcquire(CycleDetectingLock lock) { - if (!lock.isAcquiredByCurrentThread()) { - ArrayList<LockGraphNode> acquiredLockList = acquiredLocks.get(); - LockGraphNode node = lock.getLockGraphNode(); - node.checkAcquiredLocks(policy, acquiredLockList); - acquiredLockList.add(node); - } - } - - /** - * CycleDetectingLock implementations must call this method in a - * {@code finally} clause after any attempt to change the lock state, - * including both lock and unlock attempts. Failure to do so can result in - * corrupting the acquireLocks set. - */ - private void lockStateChanged(CycleDetectingLock lock) { - if (!lock.isAcquiredByCurrentThread()) { - ArrayList<LockGraphNode> acquiredLockList = acquiredLocks.get(); - LockGraphNode node = lock.getLockGraphNode(); - // Iterate in reverse because locks are usually locked/unlocked in a - // LIFO order. - for (int i = acquiredLockList.size() - 1; i >=0; i--) { - if (acquiredLockList.get(i) == node) { - acquiredLockList.remove(i); - break; - } - } - } - } - - final class CycleDetectingReentrantLock - extends ReentrantLock implements CycleDetectingLock { - - private final LockGraphNode lockGraphNode; - - private CycleDetectingReentrantLock( - LockGraphNode lockGraphNode, boolean fair) { - super(fair); - this.lockGraphNode = Preconditions.checkNotNull(lockGraphNode); - } - - ///// CycleDetectingLock methods. ///// - - @Override - public LockGraphNode getLockGraphNode() { - return lockGraphNode; - } - - @Override - public boolean isAcquiredByCurrentThread() { - return isHeldByCurrentThread(); - } - - ///// Overridden ReentrantLock methods. ///// - - @Override - public void lock() { - aboutToAcquire(this); - try { - super.lock(); - } finally { - lockStateChanged(this); - } - } - - @Override - public void lockInterruptibly() throws InterruptedException { - aboutToAcquire(this); - try { - super.lockInterruptibly(); - } finally { - lockStateChanged(this); - } - } - - @Override - public boolean tryLock() { - aboutToAcquire(this); - try { - return super.tryLock(); - } finally { - lockStateChanged(this); - } - } - - @Override - public boolean tryLock(long timeout, TimeUnit unit) - throws InterruptedException { - aboutToAcquire(this); - try { - return super.tryLock(timeout, unit); - } finally { - lockStateChanged(this); - } - } - - @Override - public void unlock() { - try { - super.unlock(); - } finally { - lockStateChanged(this); - } - } - } - - final class CycleDetectingReentrantReadWriteLock - extends ReentrantReadWriteLock implements CycleDetectingLock { - - // These ReadLock/WriteLock implementations shadow those in the - // ReentrantReadWriteLock superclass. They are simply wrappers around the - // internal Sync object, so this is safe since the shadowed locks are never - // exposed or used. - private final CycleDetectingReentrantReadLock readLock; - private final CycleDetectingReentrantWriteLock writeLock; - - private final LockGraphNode lockGraphNode; - - private CycleDetectingReentrantReadWriteLock( - LockGraphNode lockGraphNode, boolean fair) { - super(fair); - this.readLock = new CycleDetectingReentrantReadLock(this); - this.writeLock = new CycleDetectingReentrantWriteLock(this); - this.lockGraphNode = Preconditions.checkNotNull(lockGraphNode); - } - - ///// Overridden ReentrantReadWriteLock methods. ///// - - @Override - public ReadLock readLock() { - return readLock; - } - - @Override - public WriteLock writeLock() { - return writeLock; - } - - ///// CycleDetectingLock methods. ///// - - @Override - public LockGraphNode getLockGraphNode() { - return lockGraphNode; - } - - @Override - public boolean isAcquiredByCurrentThread() { - return isWriteLockedByCurrentThread() || getReadHoldCount() > 0; - } - } - - private class CycleDetectingReentrantReadLock - extends ReentrantReadWriteLock.ReadLock { - - final CycleDetectingReentrantReadWriteLock readWriteLock; - - CycleDetectingReentrantReadLock( - CycleDetectingReentrantReadWriteLock readWriteLock) { - super(readWriteLock); - this.readWriteLock = readWriteLock; - } - - @Override - public void lock() { - aboutToAcquire(readWriteLock); - try { - super.lock(); - } finally { - lockStateChanged(readWriteLock); - } - } - - @Override - public void lockInterruptibly() throws InterruptedException { - aboutToAcquire(readWriteLock); - try { - super.lockInterruptibly(); - } finally { - lockStateChanged(readWriteLock); - } - } - - @Override - public boolean tryLock() { - aboutToAcquire(readWriteLock); - try { - return super.tryLock(); - } finally { - lockStateChanged(readWriteLock); - } - } - - @Override - public boolean tryLock(long timeout, TimeUnit unit) - throws InterruptedException { - aboutToAcquire(readWriteLock); - try { - return super.tryLock(timeout, unit); - } finally { - lockStateChanged(readWriteLock); - } - } - - @Override - public void unlock() { - try { - super.unlock(); - } finally { - lockStateChanged(readWriteLock); - } - } - } - - private class CycleDetectingReentrantWriteLock - extends ReentrantReadWriteLock.WriteLock { - - final CycleDetectingReentrantReadWriteLock readWriteLock; - - CycleDetectingReentrantWriteLock( - CycleDetectingReentrantReadWriteLock readWriteLock) { - super(readWriteLock); - this.readWriteLock = readWriteLock; - } - - @Override - public void lock() { - aboutToAcquire(readWriteLock); - try { - super.lock(); - } finally { - lockStateChanged(readWriteLock); - } - } - - @Override - public void lockInterruptibly() throws InterruptedException { - aboutToAcquire(readWriteLock); - try { - super.lockInterruptibly(); - } finally { - lockStateChanged(readWriteLock); - } - } - - @Override - public boolean tryLock() { - aboutToAcquire(readWriteLock); - try { - return super.tryLock(); - } finally { - lockStateChanged(readWriteLock); - } - } - - @Override - public boolean tryLock(long timeout, TimeUnit unit) - throws InterruptedException { - aboutToAcquire(readWriteLock); - try { - return super.tryLock(timeout, unit); - } finally { - lockStateChanged(readWriteLock); - } - } - - @Override - public void unlock() { - try { - super.unlock(); - } finally { - lockStateChanged(readWriteLock); - } - } - } -} |