/* -*- 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 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); } } }