a173ae132e5036dd52fb9a2e56b9826337555fe0
[yangtools.git] / common / util / src / main / java / org / opendaylight / yangtools / util / concurrent / DeadlockDetectingListeningExecutorService.java
1 /*
2  * Copyright (c) 2014 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.util.concurrent;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12
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;
24
25 /**
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.
28  *
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:
31  * <ul>
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>
35  * </ul>
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
38  * occurred.
39  *
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.
44  *
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()}.
48  *
49  * @author Thomas Pantelis
50  * @author Robert Varga
51  */
52 public class DeadlockDetectingListeningExecutorService extends AsyncNotifyingListeningExecutorService {
53     /*
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.
57      */
58     private final SettableBooleanThreadLocal deadlockDetector = new SettableBooleanThreadLocal();
59     private final Supplier<Exception> deadlockExceptionFunction;
60
61     /**
62      * Constructor.
63      *
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.
67      */
68     public DeadlockDetectingListeningExecutorService(final @NonNull ExecutorService delegate,
69             final @NonNull Supplier<Exception> deadlockExceptionSupplier) {
70         this(delegate, deadlockExceptionSupplier, null);
71     }
72
73     /**
74      * Constructor.
75      *
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.
81      */
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);
87     }
88
89     @Override
90     public void execute(final Runnable command) {
91         getDelegate().execute(wrapRunnable(command));
92     }
93
94     @Override
95     public <T> ListenableFuture<T> submit(final Callable<T> task) {
96         return wrapListenableFuture(super.submit(wrapCallable(task)));
97     }
98
99     @Override
100     public ListenableFuture<?> submit(final Runnable task) {
101         return wrapListenableFuture(super.submit(wrapRunnable(task)));
102     }
103
104     @Override
105     public <T> ListenableFuture<T> submit(final Runnable task, final T result) {
106         return wrapListenableFuture(super.submit(wrapRunnable(task), result));
107     }
108
109     /**
110      * Remove the state this instance may have attached to the calling thread. If no state
111      * was attached this method does nothing.
112      */
113     public void cleanStateForCurrentThread() {
114         deadlockDetector.remove();
115     }
116
117     private SettableBoolean primeDetector() {
118         final SettableBoolean b = deadlockDetector.get();
119         checkState(!b.isSet(), "Detector for {} has already been primed", this);
120         b.set();
121         return b;
122     }
123
124     private Runnable wrapRunnable(final Runnable task) {
125         return () -> {
126             final SettableBoolean b = primeDetector();
127             try {
128                 task.run();
129             } finally {
130                 b.reset();
131             }
132         };
133     }
134
135     private <T> Callable<T> wrapCallable(final Callable<T> delagate) {
136         return () -> {
137             final SettableBoolean b = primeDetector();
138             try {
139                 return delagate.call();
140             } finally {
141                 b.reset();
142             }
143         };
144     }
145
146     private <T> ListenableFuture<T> wrapListenableFuture(final ListenableFuture<T> delegate) {
147         /*
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.
154          */
155         return new ForwardingListenableFuture.SimpleForwardingListenableFuture<T>(delegate) {
156             @Override
157             public T get() throws InterruptedException, ExecutionException {
158                 checkDeadLockDetectorTL();
159                 return super.get();
160             }
161
162             @Override
163             public T get(final long timeout, final TimeUnit unit)
164                     throws InterruptedException, ExecutionException, TimeoutException {
165                 checkDeadLockDetectorTL();
166                 return super.get(timeout, unit);
167             }
168
169             void checkDeadLockDetectorTL() throws ExecutionException {
170                 if (deadlockDetector.get().isSet()) {
171                     throw new ExecutionException("A potential deadlock was detected.",
172                             deadlockExceptionFunction.get());
173                 }
174             }
175         };
176     }
177 }