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