/* * 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.MoreObjects; 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.ConcurrentMap; 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> * <p>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> * <p>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> * <p>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> * <p>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> * * <p>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 ConcurrentMap<Class<? extends Enum>, Map<? extends Enum, LockGraphNode>> lockGraphNodesPerType = new MapMaker().weakKeys().makeMap(); /** * Creates a {@code CycleDetectingLockFactory.WithExplicitOrdering<E>}. */ public static <E extends Enum<E>> WithExplicitOrdering<E> newInstanceWithExplicitOrdering(Class<E> enumClass, Policy policy) { // createNodes 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>) getOrCreateNodes(enumClass); return new WithExplicitOrdering<E>(policy, lockGraphNodes); } private static Map<? extends Enum, LockGraphNode> getOrCreateNodes( Class<? extends Enum> clazz) { Map<? extends Enum, LockGraphNode> existing = lockGraphNodesPerType.get(clazz); if (existing != null) { return existing; } Map<? extends Enum, LockGraphNode> created = createNodes(clazz); existing = lockGraphNodesPerType.putIfAbsent(clazz, created); return MoreObjects.firstNonNull(existing, created); } /** * 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 <E extends Enum<E>> Map<E, LockGraphNode> createNodes(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 static String getLockName(Enum<?> rank) { return rank.getDeclaringClass().getSimpleName() + "." + rank.name(); } /** * <p>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> * * <p>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> * * <p>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); } } //////// 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> * * <p>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); } } } }