1 /*
2  *  Copyright 2015 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 package org.webrtc;
12 
13 import android.os.Handler;
14 import android.os.Looper;
15 import android.os.SystemClock;
16 import java.util.concurrent.Callable;
17 import java.util.concurrent.CountDownLatch;
18 import java.util.concurrent.TimeUnit;
19 
20 public class ThreadUtils {
21   /**
22    * Utility class to be used for checking that a method is called on the correct thread.
23    */
24   public static class ThreadChecker {
25     private Thread thread = Thread.currentThread();
26 
checkIsOnValidThread()27     public void checkIsOnValidThread() {
28       if (thread == null) {
29         thread = Thread.currentThread();
30       }
31       if (Thread.currentThread() != thread) {
32         throw new IllegalStateException("Wrong thread");
33       }
34     }
35 
detachThread()36     public void detachThread() {
37       thread = null;
38     }
39   }
40 
41   /**
42    * Throws exception if called from other than main thread.
43    */
checkIsOnMainThread()44   public static void checkIsOnMainThread() {
45     if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
46       throw new IllegalStateException("Not on main thread!");
47     }
48   }
49 
50   /**
51    * Utility interface to be used with executeUninterruptibly() to wait for blocking operations
52    * to complete without getting interrupted..
53    */
run()54   public interface BlockingOperation { void run() throws InterruptedException; }
55 
56   /**
57    * Utility method to make sure a blocking operation is executed to completion without getting
58    * interrupted. This should be used in cases where the operation is waiting for some critical
59    * work, e.g. cleanup, that must complete before returning. If the thread is interrupted during
60    * the blocking operation, this function will re-run the operation until completion, and only then
61    * re-interrupt the thread.
62    */
executeUninterruptibly(BlockingOperation operation)63   public static void executeUninterruptibly(BlockingOperation operation) {
64     boolean wasInterrupted = false;
65     while (true) {
66       try {
67         operation.run();
68         break;
69       } catch (InterruptedException e) {
70         // Someone is asking us to return early at our convenience. We can't cancel this operation,
71         // but we should preserve the information and pass it along.
72         wasInterrupted = true;
73       }
74     }
75     // Pass interruption information along.
76     if (wasInterrupted) {
77       Thread.currentThread().interrupt();
78     }
79   }
80 
joinUninterruptibly(final Thread thread, long timeoutMs)81   public static boolean joinUninterruptibly(final Thread thread, long timeoutMs) {
82     final long startTimeMs = SystemClock.elapsedRealtime();
83     long timeRemainingMs = timeoutMs;
84     boolean wasInterrupted = false;
85     while (timeRemainingMs > 0) {
86       try {
87         thread.join(timeRemainingMs);
88         break;
89       } catch (InterruptedException e) {
90         // Someone is asking us to return early at our convenience. We can't cancel this operation,
91         // but we should preserve the information and pass it along.
92         wasInterrupted = true;
93         final long elapsedTimeMs = SystemClock.elapsedRealtime() - startTimeMs;
94         timeRemainingMs = timeoutMs - elapsedTimeMs;
95       }
96     }
97     // Pass interruption information along.
98     if (wasInterrupted) {
99       Thread.currentThread().interrupt();
100     }
101     return !thread.isAlive();
102   }
103 
joinUninterruptibly(final Thread thread)104   public static void joinUninterruptibly(final Thread thread) {
105     executeUninterruptibly(new BlockingOperation() {
106       @Override
107       public void run() throws InterruptedException {
108         thread.join();
109       }
110     });
111   }
112 
awaitUninterruptibly(final CountDownLatch latch)113   public static void awaitUninterruptibly(final CountDownLatch latch) {
114     executeUninterruptibly(new BlockingOperation() {
115       @Override
116       public void run() throws InterruptedException {
117         latch.await();
118       }
119     });
120   }
121 
awaitUninterruptibly(CountDownLatch barrier, long timeoutMs)122   public static boolean awaitUninterruptibly(CountDownLatch barrier, long timeoutMs) {
123     final long startTimeMs = SystemClock.elapsedRealtime();
124     long timeRemainingMs = timeoutMs;
125     boolean wasInterrupted = false;
126     boolean result = false;
127     do {
128       try {
129         result = barrier.await(timeRemainingMs, TimeUnit.MILLISECONDS);
130         break;
131       } catch (InterruptedException e) {
132         // Someone is asking us to return early at our convenience. We can't cancel this operation,
133         // but we should preserve the information and pass it along.
134         wasInterrupted = true;
135         final long elapsedTimeMs = SystemClock.elapsedRealtime() - startTimeMs;
136         timeRemainingMs = timeoutMs - elapsedTimeMs;
137       }
138     } while (timeRemainingMs > 0);
139     // Pass interruption information along.
140     if (wasInterrupted) {
141       Thread.currentThread().interrupt();
142     }
143     return result;
144   }
145 
146   // TODO(sakal): This method is broken. It should be removed: crbug.com/webrtc/8456
147   @SuppressWarnings("WaitNotInLoop")
waitUninterruptibly(final Object object)148   public static void waitUninterruptibly(final Object object) {
149     executeUninterruptibly(new BlockingOperation() {
150       @Override
151       public void run() throws InterruptedException {
152         object.wait();
153       }
154     });
155   }
156 
157   /**
158    * Post |callable| to |handler| and wait for the result.
159    */
invokeAtFrontUninterruptibly( final Handler handler, final Callable<V> callable)160   public static <V> V invokeAtFrontUninterruptibly(
161       final Handler handler, final Callable<V> callable) {
162     if (handler.getLooper().getThread() == Thread.currentThread()) {
163       try {
164         return callable.call();
165       } catch (Exception e) {
166         throw new RuntimeException(e);
167       }
168     }
169     // Place-holder classes that are assignable inside nested class.
170     class CaughtException {
171       Exception e;
172     }
173     class Result {
174       public V value;
175     }
176     final Result result = new Result();
177     final CaughtException caughtException = new CaughtException();
178     final CountDownLatch barrier = new CountDownLatch(1);
179     handler.post(new Runnable() {
180       @Override
181       public void run() {
182         try {
183           result.value = callable.call();
184         } catch (Exception e) {
185           caughtException.e = e;
186         }
187         barrier.countDown();
188       }
189     });
190     awaitUninterruptibly(barrier);
191     // Re-throw any runtime exception caught inside the other thread. Since this is an invoke, add
192     // stack trace for the waiting thread as well.
193     if (caughtException.e != null) {
194       final RuntimeException runtimeException = new RuntimeException(caughtException.e);
195       runtimeException.setStackTrace(
196           concatStackTraces(caughtException.e.getStackTrace(), runtimeException.getStackTrace()));
197       throw runtimeException;
198     }
199     return result.value;
200   }
201 
202   /**
203    * Post |runner| to |handler|, at the front, and wait for completion.
204    */
invokeAtFrontUninterruptibly(final Handler handler, final Runnable runner)205   public static void invokeAtFrontUninterruptibly(final Handler handler, final Runnable runner) {
206     invokeAtFrontUninterruptibly(handler, new Callable<Void>() {
207       @Override
208       public Void call() {
209         runner.run();
210         return null;
211       }
212     });
213   }
214 
concatStackTraces( StackTraceElement[] inner, StackTraceElement[] outer)215   static StackTraceElement[] concatStackTraces(
216       StackTraceElement[] inner, StackTraceElement[] outer) {
217     final StackTraceElement[] combined = new StackTraceElement[inner.length + outer.length];
218     System.arraycopy(inner, 0, combined, 0, inner.length);
219     System.arraycopy(outer, 0, combined, inner.length, outer.length);
220     return combined;
221   }
222 }
223