248 lines
7.9 KiB
Java
248 lines
7.9 KiB
Java
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.gecko.util;
|
|
|
|
import org.mozilla.gecko.annotation.RobocopTarget;
|
|
|
|
import java.util.Map;
|
|
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.MessageQueue;
|
|
import android.util.Log;
|
|
|
|
public final class ThreadUtils {
|
|
private static final String LOGTAG = "ThreadUtils";
|
|
|
|
/**
|
|
* Controls the action taken when a method like
|
|
* {@link ThreadUtils#assertOnUiThread(AssertBehavior)} detects a problem.
|
|
*/
|
|
public static enum AssertBehavior {
|
|
NONE,
|
|
THROW,
|
|
}
|
|
|
|
private static final Thread sUiThread = Looper.getMainLooper().getThread();
|
|
private static final Handler sUiHandler = new Handler(Looper.getMainLooper());
|
|
|
|
private static volatile Thread sBackgroundThread;
|
|
|
|
// Referenced directly from GeckoAppShell in highly performance-sensitive code (The extra
|
|
// function call of the getter was harming performance. (Bug 897123))
|
|
// Once Bug 709230 is resolved we should reconsider this as ProGuard should be able to optimise
|
|
// this out at compile time.
|
|
public static Handler sGeckoHandler;
|
|
public static volatile Thread sGeckoThread;
|
|
|
|
// Delayed Runnable that resets the Gecko thread priority.
|
|
private static final Runnable sPriorityResetRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
resetGeckoPriority();
|
|
}
|
|
};
|
|
|
|
private static boolean sIsGeckoPriorityReduced;
|
|
|
|
@SuppressWarnings("serial")
|
|
public static class UiThreadBlockedException extends RuntimeException {
|
|
public UiThreadBlockedException() {
|
|
super();
|
|
}
|
|
|
|
public UiThreadBlockedException(String msg) {
|
|
super(msg);
|
|
}
|
|
|
|
public UiThreadBlockedException(String msg, Throwable e) {
|
|
super(msg, e);
|
|
}
|
|
|
|
public UiThreadBlockedException(Throwable e) {
|
|
super(e);
|
|
}
|
|
}
|
|
|
|
public static void dumpAllStackTraces() {
|
|
Log.w(LOGTAG, "Dumping ALL the threads!");
|
|
Map<Thread, StackTraceElement[]> allStacks = Thread.getAllStackTraces();
|
|
for (Thread t : allStacks.keySet()) {
|
|
Log.w(LOGTAG, t.toString());
|
|
for (StackTraceElement ste : allStacks.get(t)) {
|
|
Log.w(LOGTAG, ste.toString());
|
|
}
|
|
Log.w(LOGTAG, "----");
|
|
}
|
|
}
|
|
|
|
public static void setBackgroundThread(Thread thread) {
|
|
sBackgroundThread = thread;
|
|
}
|
|
|
|
public static Thread getUiThread() {
|
|
return sUiThread;
|
|
}
|
|
|
|
public static Handler getUiHandler() {
|
|
return sUiHandler;
|
|
}
|
|
|
|
public static void postToUiThread(Runnable runnable) {
|
|
sUiHandler.post(runnable);
|
|
}
|
|
|
|
public static void postDelayedToUiThread(Runnable runnable, long timeout) {
|
|
sUiHandler.postDelayed(runnable, timeout);
|
|
}
|
|
|
|
public static void removeCallbacksFromUiThread(Runnable runnable) {
|
|
sUiHandler.removeCallbacks(runnable);
|
|
}
|
|
|
|
public static Thread getBackgroundThread() {
|
|
return sBackgroundThread;
|
|
}
|
|
|
|
public static Handler getBackgroundHandler() {
|
|
return GeckoBackgroundThread.getHandler();
|
|
}
|
|
|
|
public static void postToBackgroundThread(Runnable runnable) {
|
|
GeckoBackgroundThread.post(runnable);
|
|
}
|
|
|
|
public static void assertOnUiThread(final AssertBehavior assertBehavior) {
|
|
assertOnThread(getUiThread(), assertBehavior);
|
|
}
|
|
|
|
public static void assertOnUiThread() {
|
|
assertOnThread(getUiThread(), AssertBehavior.THROW);
|
|
}
|
|
|
|
public static void assertNotOnUiThread() {
|
|
assertNotOnThread(getUiThread(), AssertBehavior.THROW);
|
|
}
|
|
|
|
@RobocopTarget
|
|
public static void assertOnGeckoThread() {
|
|
assertOnThread(sGeckoThread, AssertBehavior.THROW);
|
|
}
|
|
|
|
public static void assertNotOnGeckoThread() {
|
|
if (sGeckoThread == null) {
|
|
// Cannot be on Gecko thread if Gecko thread is not live yet.
|
|
return;
|
|
}
|
|
assertNotOnThread(sGeckoThread, AssertBehavior.THROW);
|
|
}
|
|
|
|
public static void assertOnBackgroundThread() {
|
|
assertOnThread(getBackgroundThread(), AssertBehavior.THROW);
|
|
}
|
|
|
|
public static void assertOnThread(final Thread expectedThread) {
|
|
assertOnThread(expectedThread, AssertBehavior.THROW);
|
|
}
|
|
|
|
public static void assertOnThread(final Thread expectedThread, AssertBehavior behavior) {
|
|
assertOnThreadComparison(expectedThread, behavior, true);
|
|
}
|
|
|
|
public static void assertNotOnThread(final Thread expectedThread, AssertBehavior behavior) {
|
|
assertOnThreadComparison(expectedThread, behavior, false);
|
|
}
|
|
|
|
private static void assertOnThreadComparison(final Thread expectedThread, AssertBehavior behavior, boolean expected) {
|
|
final Thread currentThread = Thread.currentThread();
|
|
final long currentThreadId = currentThread.getId();
|
|
final long expectedThreadId = expectedThread.getId();
|
|
|
|
if ((currentThreadId == expectedThreadId) == expected) {
|
|
return;
|
|
}
|
|
|
|
final String message;
|
|
if (expected) {
|
|
message = "Expected thread " + expectedThreadId +
|
|
" (\"" + expectedThread.getName() + "\"), but running on thread " +
|
|
currentThreadId + " (\"" + currentThread.getName() + "\")";
|
|
} else {
|
|
message = "Expected anything but " + expectedThreadId +
|
|
" (\"" + expectedThread.getName() + "\"), but running there.";
|
|
}
|
|
|
|
final IllegalThreadStateException e = new IllegalThreadStateException(message);
|
|
|
|
switch (behavior) {
|
|
case THROW:
|
|
throw e;
|
|
default:
|
|
Log.e(LOGTAG, "Method called on wrong thread!", e);
|
|
}
|
|
}
|
|
|
|
public static boolean isOnGeckoThread() {
|
|
if (sGeckoThread != null) {
|
|
return isOnThread(sGeckoThread);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static boolean isOnUiThread() {
|
|
return isOnThread(getUiThread());
|
|
}
|
|
|
|
@RobocopTarget
|
|
public static boolean isOnBackgroundThread() {
|
|
if (sBackgroundThread == null) {
|
|
return false;
|
|
}
|
|
|
|
return isOnThread(sBackgroundThread);
|
|
}
|
|
|
|
@RobocopTarget
|
|
public static boolean isOnThread(Thread thread) {
|
|
return (Thread.currentThread().getId() == thread.getId());
|
|
}
|
|
|
|
/**
|
|
* Reduces the priority of the Gecko thread, allowing other operations
|
|
* (such as those related to the UI and database) to take precedence.
|
|
*
|
|
* Note that there are no guards in place to prevent multiple calls
|
|
* to this method from conflicting with each other.
|
|
*
|
|
* @param timeout Timeout in ms after which the priority will be reset
|
|
*/
|
|
public static void reduceGeckoPriority(long timeout) {
|
|
if (Runtime.getRuntime().availableProcessors() > 1) {
|
|
// Don't reduce priority for multicore devices. We use availableProcessors()
|
|
// for its fast performance. It may give false negatives (i.e. multicore
|
|
// detected as single-core), but we can tolerate this behavior.
|
|
return;
|
|
}
|
|
if (!sIsGeckoPriorityReduced && sGeckoThread != null) {
|
|
sIsGeckoPriorityReduced = true;
|
|
sGeckoThread.setPriority(Thread.MIN_PRIORITY);
|
|
getUiHandler().postDelayed(sPriorityResetRunnable, timeout);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets the priority of a thread whose priority has been reduced
|
|
* by reduceGeckoPriority.
|
|
*/
|
|
public static void resetGeckoPriority() {
|
|
if (sIsGeckoPriorityReduced) {
|
|
sIsGeckoPriorityReduced = false;
|
|
sGeckoThread.setPriority(Thread.NORM_PRIORITY);
|
|
getUiHandler().removeCallbacks(sPriorityResetRunnable);
|
|
}
|
|
}
|
|
}
|