213 lines
7.0 KiB
Java
213 lines
7.0 KiB
Java
|
/*
|
||
|
* 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;
|
||
|
}
|
||
|
}
|