blob: a40387090657bb89bf1455ee4a279d142c6b82c9 [file] [log] [blame]
/*
* 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 java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
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;
}
}