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
9 package org.opendaylight.yangtools.util.concurrent;
11 import static com.google.common.base.Preconditions.checkNotNull;
13 import com.google.common.base.Function;
14 import com.google.common.util.concurrent.ForwardingListenableFuture;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import java.util.concurrent.Callable;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.Executor;
19 import java.util.concurrent.ExecutorService;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.TimeoutException;
23 import javax.annotation.Nullable;
26 * An implementation of ListeningExecutorService that attempts to detect deadlock scenarios that
27 * could occur if clients invoke the returned Future's <ode>get</code> methods synchronously.
29 * 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 * 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 * @author Thomas Pantelis
47 public class DeadlockDetectingListeningExecutorService extends AsyncNotifyingListeningExecutorService {
48 private final ThreadLocal<Boolean> deadlockDetector = new ThreadLocal<>();
49 private final Function<Void, Exception> deadlockExceptionFunction;
54 * @param delegate the backing ExecutorService.
55 * @param deadlockExceptionFunction Function that returns an Exception instance to set as the
56 * cause of the ExecutionException when a deadlock is detected.
58 public DeadlockDetectingListeningExecutorService( ExecutorService delegate,
59 Function<Void,Exception> deadlockExceptionFunction ) {
60 this(delegate, deadlockExceptionFunction, null);
66 * @param delegate the backing ExecutorService.
67 * @param deadlockExceptionFunction Function that returns an Exception instance to set as the
68 * cause of the ExecutionException when a deadlock is detected.
69 * @param listenableFutureExecutor the executor used to run listener callbacks asynchronously.
70 * If null, no executor is used.
72 public DeadlockDetectingListeningExecutorService( ExecutorService delegate,
73 Function<Void,Exception> deadlockExceptionFunction,
74 @Nullable Executor listenableFutureExecutor ) {
75 super(delegate, listenableFutureExecutor);
76 this.deadlockExceptionFunction = checkNotNull(deadlockExceptionFunction);
80 public void execute( Runnable command ){
81 getDelegate().execute(wrapRunnable(command));
85 public <T> ListenableFuture<T> submit( Callable<T> task ){
86 return wrapListenableFuture(super.submit(wrapCallable(task)));
90 public ListenableFuture<?> submit( Runnable task ){
91 return wrapListenableFuture(super.submit(wrapRunnable(task)));
95 public <T> ListenableFuture<T> submit( Runnable task, T result ){
96 return wrapListenableFuture(super.submit(wrapRunnable(task), result));
99 private Runnable wrapRunnable(final Runnable task) {
100 return new Runnable() {
103 deadlockDetector.set(Boolean.TRUE);
107 deadlockDetector.remove();
113 private <T> Callable<T> wrapCallable(final Callable<T> delagate) {
114 return new Callable<T>() {
116 public T call() throws Exception {
117 deadlockDetector.set(Boolean.TRUE);
119 return delagate.call();
121 deadlockDetector.remove();
127 private <T> ListenableFuture<T> wrapListenableFuture(final ListenableFuture<T> delegate ) {
129 * This creates a forwarding Future that overrides calls to get(...) to check, via the
130 * ThreadLocal, if the caller is doing a blocking call on a thread from this executor. If
131 * so, we detect this as a deadlock and throw an ExecutionException even though it may not
132 * be a deadlock if there are more than 1 thread in the pool. Either way, there's bad
133 * practice somewhere, either on the client side for doing a blocking call or in the
134 * framework's threading model.
136 return new ForwardingListenableFuture.SimpleForwardingListenableFuture<T>(delegate) {
138 public T get() throws InterruptedException, ExecutionException {
139 checkDeadLockDetectorTL();
144 public T get(final long timeout, final TimeUnit unit)
145 throws InterruptedException, ExecutionException, TimeoutException {
146 checkDeadLockDetectorTL();
147 return super.get(timeout, unit);
150 void checkDeadLockDetectorTL() throws ExecutionException {
151 if (deadlockDetector.get() != null) {
152 throw new ExecutionException("A potential deadlock was detected.",
153 deadlockExceptionFunction.apply(null));