2 * Copyright (c) 2014 Brocade Communications Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.util.concurrent;
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.util.concurrent.ForwardingListenableFuture;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import java.util.concurrent.Callable;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.Executor;
18 import java.util.concurrent.ExecutorService;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.TimeoutException;
21 import java.util.function.Supplier;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
26 * An implementation of ListeningExecutorService that attempts to detect deadlock scenarios that
27 * could occur if clients invoke the returned Future's <code>get</code> methods synchronously.
29 * <p>Deadlock scenarios are most apt to occur with a backing single-threaded executor where setting of
30 * the Future's result is executed on the single thread. Here's a scenario:
32 * <li>Client code is currently executing in an executor's single thread.</li>
33 * <li>The client submits another task to the same executor.</li>
34 * <li>The client calls <code>get()</code> synchronously on the returned Future</li>
36 * The second submitted task will never execute since the single thread is currently executing
37 * the client code which is blocked waiting for the submitted task to complete. Thus, deadlock has
40 * <p>This class prevents this scenario via the use of a ThreadLocal variable. When a task is invoked,
41 * the ThreadLocal is set and, when a task completes, the ThreadLocal is cleared. Futures returned
42 * from this class override the <code>get</code> methods to check if the ThreadLocal is set. If it is,
43 * an ExecutionException is thrown with a custom cause.
45 * <p>Note that the ThreadLocal is not removed automatically, so some state may be left hanging off of
46 * threads which have encountered this class. If you need to clean that state up, use
47 * {@link #cleanStateForCurrentThread()}.
49 * @author Thomas Pantelis
50 * @author Robert Varga
52 public class DeadlockDetectingListeningExecutorService extends AsyncNotifyingListeningExecutorService {
54 * We cannot use a static field simply because our API contract allows nesting, which means some
55 * tasks may be submitted to underlay and some to overlay service -- and the two cases need to
56 * be discerned reliably.
58 private final SettableBooleanThreadLocal deadlockDetector = new SettableBooleanThreadLocal();
59 private final Supplier<Exception> deadlockExceptionFunction;
64 * @param delegate the backing ExecutorService.
65 * @param deadlockExceptionSupplier Supplier that returns an Exception instance to set as the
66 * cause of the ExecutionException when a deadlock is detected.
68 public DeadlockDetectingListeningExecutorService(final @NonNull ExecutorService delegate,
69 final @NonNull Supplier<Exception> deadlockExceptionSupplier) {
70 this(delegate, deadlockExceptionSupplier, null);
76 * @param delegate the backing ExecutorService.
77 * @param deadlockExceptionSupplier Supplier that returns an Exception instance to set as the
78 * cause of the ExecutionException when a deadlock is detected.
79 * @param listenableFutureExecutor the executor used to run listener callbacks asynchronously.
80 * If null, no executor is used.
82 public DeadlockDetectingListeningExecutorService(final @NonNull ExecutorService delegate,
83 @NonNull final Supplier<Exception> deadlockExceptionSupplier,
84 @Nullable final Executor listenableFutureExecutor) {
85 super(delegate, listenableFutureExecutor);
86 this.deadlockExceptionFunction = requireNonNull(deadlockExceptionSupplier);
90 public void execute(final Runnable command) {
91 getDelegate().execute(wrapRunnable(command));
95 public <T> ListenableFuture<T> submit(final Callable<T> task) {
96 return wrapListenableFuture(super.submit(wrapCallable(task)));
100 public ListenableFuture<?> submit(final Runnable task) {
101 return wrapListenableFuture(super.submit(wrapRunnable(task)));
105 public <T> ListenableFuture<T> submit(final Runnable task, final T result) {
106 return wrapListenableFuture(super.submit(wrapRunnable(task), result));
110 * Remove the state this instance may have attached to the calling thread. If no state
111 * was attached this method does nothing.
113 public void cleanStateForCurrentThread() {
114 deadlockDetector.remove();
117 private SettableBoolean primeDetector() {
118 final SettableBoolean b = deadlockDetector.get();
119 checkState(!b.isSet(), "Detector for {} has already been primed", this);
124 private Runnable wrapRunnable(final Runnable task) {
126 final SettableBoolean b = primeDetector();
135 private <T> Callable<T> wrapCallable(final Callable<T> delagate) {
137 final SettableBoolean b = primeDetector();
139 return delagate.call();
146 private <T> ListenableFuture<T> wrapListenableFuture(final ListenableFuture<T> delegate) {
148 * This creates a forwarding Future that overrides calls to get(...) to check, via the
149 * ThreadLocal, if the caller is doing a blocking call on a thread from this executor. If
150 * so, we detect this as a deadlock and throw an ExecutionException even though it may not
151 * be a deadlock if there are more than 1 thread in the pool. Either way, there's bad
152 * practice somewhere, either on the client side for doing a blocking call or in the
153 * framework's threading model.
155 return new ForwardingListenableFuture.SimpleForwardingListenableFuture<T>(delegate) {
157 public T get() throws InterruptedException, ExecutionException {
158 checkDeadLockDetectorTL();
163 public T get(final long timeout, final TimeUnit unit)
164 throws InterruptedException, ExecutionException, TimeoutException {
165 checkDeadLockDetectorTL();
166 return super.get(timeout, unit);
169 void checkDeadLockDetectorTL() throws ExecutionException {
170 if (deadlockDetector.get().isSet()) {
171 throw new ExecutionException("A potential deadlock was detected.",
172 deadlockExceptionFunction.get());