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