| /* |
| * Copyright 2015 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| package org.webrtc; |
| |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.SystemClock; |
| import androidx.annotation.Nullable; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| public class ThreadUtils { |
| /** |
| * Utility class to be used for checking that a method is called on the correct thread. |
| */ |
| public static class ThreadChecker { |
| @Nullable private Thread thread = Thread.currentThread(); |
| |
| public void checkIsOnValidThread() { |
| if (thread == null) { |
| thread = Thread.currentThread(); |
| } |
| if (Thread.currentThread() != thread) { |
| throw new IllegalStateException("Wrong thread"); |
| } |
| } |
| |
| public void detachThread() { |
| thread = null; |
| } |
| } |
| |
| /** |
| * Throws exception if called from other than main thread. |
| */ |
| public static void checkIsOnMainThread() { |
| if (Thread.currentThread() != Looper.getMainLooper().getThread()) { |
| throw new IllegalStateException("Not on main thread!"); |
| } |
| } |
| |
| /** |
| * Utility interface to be used with executeUninterruptibly() to wait for blocking operations |
| * to complete without getting interrupted.. |
| */ |
| public interface BlockingOperation { void run() throws InterruptedException; } |
| |
| /** |
| * Utility method to make sure a blocking operation is executed to completion without getting |
| * interrupted. This should be used in cases where the operation is waiting for some critical |
| * work, e.g. cleanup, that must complete before returning. If the thread is interrupted during |
| * the blocking operation, this function will re-run the operation until completion, and only then |
| * re-interrupt the thread. |
| */ |
| public static void executeUninterruptibly(BlockingOperation operation) { |
| boolean wasInterrupted = false; |
| while (true) { |
| try { |
| operation.run(); |
| break; |
| } catch (InterruptedException e) { |
| // Someone is asking us to return early at our convenience. We can't cancel this operation, |
| // but we should preserve the information and pass it along. |
| wasInterrupted = true; |
| } |
| } |
| // Pass interruption information along. |
| if (wasInterrupted) { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| |
| public static boolean joinUninterruptibly(final Thread thread, long timeoutMs) { |
| final long startTimeMs = SystemClock.elapsedRealtime(); |
| long timeRemainingMs = timeoutMs; |
| boolean wasInterrupted = false; |
| while (timeRemainingMs > 0) { |
| try { |
| thread.join(timeRemainingMs); |
| break; |
| } catch (InterruptedException e) { |
| // Someone is asking us to return early at our convenience. We can't cancel this operation, |
| // but we should preserve the information and pass it along. |
| wasInterrupted = true; |
| final long elapsedTimeMs = SystemClock.elapsedRealtime() - startTimeMs; |
| timeRemainingMs = timeoutMs - elapsedTimeMs; |
| } |
| } |
| // Pass interruption information along. |
| if (wasInterrupted) { |
| Thread.currentThread().interrupt(); |
| } |
| return !thread.isAlive(); |
| } |
| |
| public static void joinUninterruptibly(final Thread thread) { |
| executeUninterruptibly(new BlockingOperation() { |
| @Override |
| public void run() throws InterruptedException { |
| thread.join(); |
| } |
| }); |
| } |
| |
| public static void awaitUninterruptibly(final CountDownLatch latch) { |
| executeUninterruptibly(new BlockingOperation() { |
| @Override |
| public void run() throws InterruptedException { |
| latch.await(); |
| } |
| }); |
| } |
| |
| public static boolean awaitUninterruptibly(CountDownLatch barrier, long timeoutMs) { |
| final long startTimeMs = SystemClock.elapsedRealtime(); |
| long timeRemainingMs = timeoutMs; |
| boolean wasInterrupted = false; |
| boolean result = false; |
| do { |
| try { |
| result = barrier.await(timeRemainingMs, TimeUnit.MILLISECONDS); |
| break; |
| } catch (InterruptedException e) { |
| // Someone is asking us to return early at our convenience. We can't cancel this operation, |
| // but we should preserve the information and pass it along. |
| wasInterrupted = true; |
| final long elapsedTimeMs = SystemClock.elapsedRealtime() - startTimeMs; |
| timeRemainingMs = timeoutMs - elapsedTimeMs; |
| } |
| } while (timeRemainingMs > 0); |
| // Pass interruption information along. |
| if (wasInterrupted) { |
| Thread.currentThread().interrupt(); |
| } |
| return result; |
| } |
| |
| /** |
| * Post `callable` to `handler` and wait for the result. |
| */ |
| public static <V> V invokeAtFrontUninterruptibly( |
| final Handler handler, final Callable<V> callable) { |
| if (handler.getLooper().getThread() == Thread.currentThread()) { |
| try { |
| return callable.call(); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| // Place-holder classes that are assignable inside nested class. |
| class CaughtException { |
| Exception e; |
| } |
| class Result { |
| public V value; |
| } |
| final Result result = new Result(); |
| final CaughtException caughtException = new CaughtException(); |
| final CountDownLatch barrier = new CountDownLatch(1); |
| handler.post(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| result.value = callable.call(); |
| } catch (Exception e) { |
| caughtException.e = e; |
| } |
| barrier.countDown(); |
| } |
| }); |
| awaitUninterruptibly(barrier); |
| // Re-throw any runtime exception caught inside the other thread. Since this is an invoke, add |
| // stack trace for the waiting thread as well. |
| if (caughtException.e != null) { |
| final RuntimeException runtimeException = new RuntimeException(caughtException.e); |
| runtimeException.setStackTrace( |
| concatStackTraces(caughtException.e.getStackTrace(), runtimeException.getStackTrace())); |
| throw runtimeException; |
| } |
| return result.value; |
| } |
| |
| /** |
| * Post `runner` to `handler`, at the front, and wait for completion. |
| */ |
| public static void invokeAtFrontUninterruptibly(final Handler handler, final Runnable runner) { |
| invokeAtFrontUninterruptibly(handler, new Callable<Void>() { |
| @Override |
| public Void call() { |
| runner.run(); |
| return null; |
| } |
| }); |
| } |
| |
| static StackTraceElement[] concatStackTraces( |
| StackTraceElement[] inner, StackTraceElement[] outer) { |
| final StackTraceElement[] combined = new StackTraceElement[inner.length + outer.length]; |
| System.arraycopy(inner, 0, combined, 0, inner.length); |
| System.arraycopy(outer, 0, combined, inner.length, outer.length); |
| return combined; |
| } |
| } |