Cleanup use of Guava library
[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
9 package org.opendaylight.yangtools.util.concurrent;
10
11 import static com.google.common.base.Preconditions.checkState;
12 import static java.util.Objects.requireNonNull;
13
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;
22 import java.util.function.Supplier;
23 import javax.annotation.Nonnull;
24 import javax.annotation.Nullable;
25
26 /**
27  * An implementation of ListeningExecutorService that attempts to detect deadlock scenarios that
28  * could occur if clients invoke the returned Future's <code>get</code> methods synchronously.
29  *
30  * <p>Deadlock scenarios are most apt to occur with a backing single-threaded executor where setting of
31  * the Future's result is executed on the single thread. Here's a scenario:
32  * <ul>
33  * <li>Client code is currently executing in an executor's single thread.</li>
34  * <li>The client submits another task to the same executor.</li>
35  * <li>The client calls <code>get()</code> synchronously on the returned Future</li>
36  * </ul>
37  * The second submitted task will never execute since the single thread is currently executing
38  * the client code which is blocked waiting for the submitted task to complete. Thus, deadlock has
39  * occurred.
40  *
41  * <p>This class prevents this scenario via the use of a ThreadLocal variable. When a task is invoked,
42  * the ThreadLocal is set and, when a task completes, the ThreadLocal is cleared. Futures returned
43  * from this class override the <code>get</code> methods to check if the ThreadLocal is set. If it is,
44  * an ExecutionException is thrown with a custom cause.
45  *
46  * <p>Note that the ThreadLocal is not removed automatically, so some state may be left hanging off of
47  * threads which have encountered this class. If you need to clean that state up, use
48  * {@link #cleanStateForCurrentThread()}.
49  *
50  * @author Thomas Pantelis
51  * @author Robert Varga
52  */
53 public class DeadlockDetectingListeningExecutorService extends AsyncNotifyingListeningExecutorService {
54     /*
55      * We cannot use a static field simply because our API contract allows nesting, which means some
56      * tasks may be submitted to underlay and some to overlay service -- and the two cases need to
57      * be discerned reliably.
58      */
59     private final SettableBooleanThreadLocal deadlockDetector = new SettableBooleanThreadLocal();
60     private final Supplier<Exception> deadlockExceptionFunction;
61
62     /**
63      * Constructor.
64      *
65      * @param delegate the backing ExecutorService.
66      * @param deadlockExceptionSupplier Supplier that returns an Exception instance to set as the
67      *             cause of the ExecutionException when a deadlock is detected.
68      */
69     public DeadlockDetectingListeningExecutorService(final ExecutorService delegate,
70             @Nonnull final Supplier<Exception> deadlockExceptionSupplier) {
71         this(delegate, deadlockExceptionSupplier, null);
72     }
73
74     /**
75      * Constructor.
76      *
77      * @param delegate the backing ExecutorService.
78      * @param deadlockExceptionSupplier Supplier that returns an Exception instance to set as the
79      *             cause of the ExecutionException when a deadlock is detected.
80      * @param listenableFutureExecutor the executor used to run listener callbacks asynchronously.
81      *             If null, no executor is used.
82      */
83     public DeadlockDetectingListeningExecutorService(final ExecutorService delegate,
84             @Nonnull final Supplier<Exception> deadlockExceptionSupplier,
85             @Nullable final Executor listenableFutureExecutor) {
86         super(delegate, listenableFutureExecutor);
87         this.deadlockExceptionFunction = requireNonNull(deadlockExceptionSupplier);
88     }
89
90     @Override
91     public void execute(@Nonnull final Runnable command) {
92         getDelegate().execute(wrapRunnable(command));
93     }
94
95     @Nonnull
96     @Override
97     public <T> ListenableFuture<T> submit(final Callable<T> task) {
98         return wrapListenableFuture(super.submit(wrapCallable(task)));
99     }
100
101     @Nonnull
102     @Override
103     public ListenableFuture<?> submit(final Runnable task) {
104         return wrapListenableFuture(super.submit(wrapRunnable(task)));
105     }
106
107     @Nonnull
108     @Override
109     public <T> ListenableFuture<T> submit(final Runnable task, final T result) {
110         return wrapListenableFuture(super.submit(wrapRunnable(task), result));
111     }
112
113     /**
114      * Remove the state this instance may have attached to the calling thread. If no state
115      * was attached this method does nothing.
116      */
117     public void cleanStateForCurrentThread() {
118         deadlockDetector.remove();
119     }
120
121     private SettableBoolean primeDetector() {
122         final SettableBoolean b = deadlockDetector.get();
123         checkState(!b.isSet(), "Detector for {} has already been primed", this);
124         b.set();
125         return b;
126     }
127
128     private Runnable wrapRunnable(final Runnable task) {
129         return () -> {
130             final SettableBoolean b = primeDetector();
131             try {
132                 task.run();
133             } finally {
134                 b.reset();
135             }
136         };
137     }
138
139     private <T> Callable<T> wrapCallable(final Callable<T> delagate) {
140         return () -> {
141             final SettableBoolean b = primeDetector();
142             try {
143                 return delagate.call();
144             } finally {
145                 b.reset();
146             }
147         };
148     }
149
150     private <T> ListenableFuture<T> wrapListenableFuture(final ListenableFuture<T> delegate) {
151         /*
152          * This creates a forwarding Future that overrides calls to get(...) to check, via the
153          * ThreadLocal, if the caller is doing a blocking call on a thread from this executor. If
154          * so, we detect this as a deadlock and throw an ExecutionException even though it may not
155          * be a deadlock if there are more than 1 thread in the pool. Either way, there's bad
156          * practice somewhere, either on the client side for doing a blocking call or in the
157          * framework's threading model.
158          */
159         return new ForwardingListenableFuture.SimpleForwardingListenableFuture<T>(delegate) {
160             @Override
161             public T get() throws InterruptedException, ExecutionException {
162                 checkDeadLockDetectorTL();
163                 return super.get();
164             }
165
166             @Override
167             public T get(final long timeout, @Nonnull final TimeUnit unit)
168                     throws InterruptedException, ExecutionException, TimeoutException {
169                 checkDeadLockDetectorTL();
170                 return super.get(timeout, unit);
171             }
172
173             void checkDeadLockDetectorTL() throws ExecutionException {
174                 if (deadlockDetector.get().isSet()) {
175                     throw new ExecutionException("A potential deadlock was detected.",
176                             deadlockExceptionFunction.get());
177                 }
178             }
179         };
180     }
181 }