Introduce thread priorities for threads and handlerthreads.

This commit is contained in:
Clark
2023-03-08 17:29:44 -05:00
committed by Greyson Parrelli
parent 2cef06cd6e
commit 79a062c838
22 changed files with 88 additions and 33 deletions

View File

@@ -2,6 +2,7 @@ package org.signal.core.util;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -18,6 +19,20 @@ import java.util.concurrent.ExecutorService;
*/
public final class ThreadUtil {
/**
* Default background thread priority.
*/
public static final int PRIORITY_BACKGROUND_THREAD = Process.THREAD_PRIORITY_BACKGROUND;
/**
* Important background thread priority. This is slightly lower priority than the UI thread. Use for critical work that should run as fast as
* possible, but shouldn't block the UI (e.g. message sends)
*/
public static final int PRIORITY_IMPORTANT_BACKGROUND_THREAD = Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE;
/**
* As important as the UI thread. Use for absolutely critical UI blocking tasks/threads. For example fetching data for display in a recyclerview, or
* anything that will block UI.
*/
public static final int PRIORITY_UI_BLOCKING_THREAD = Process.THREAD_PRIORITY_DEFAULT;
private static volatile Handler handler;
@VisibleForTesting

View File

@@ -1,6 +1,7 @@
package org.signal.core.util.concurrent;
import android.os.HandlerThread;
import android.os.Process;
import androidx.annotation.NonNull;
@@ -17,15 +18,20 @@ import java.util.concurrent.atomic.AtomicInteger;
public final class SignalExecutors {
public static final ExecutorService UNBOUNDED = ThreadUtil.trace(Executors.newCachedThreadPool(new NumberedThreadFactory("signal-unbounded")));
public static final ExecutorService BOUNDED = ThreadUtil.trace(Executors.newFixedThreadPool(4, new NumberedThreadFactory("signal-bounded")));
public static final ExecutorService SERIAL = ThreadUtil.trace(Executors.newSingleThreadExecutor(new NumberedThreadFactory("signal-serial")));
public static final ExecutorService BOUNDED_IO = ThreadUtil.trace(newCachedBoundedExecutor("signal-io-bounded", 1, 32, 30));
public static final ExecutorService UNBOUNDED = ThreadUtil.trace(Executors.newCachedThreadPool(new NumberedThreadFactory("signal-unbounded", ThreadUtil.PRIORITY_BACKGROUND_THREAD)));
public static final ExecutorService BOUNDED = ThreadUtil.trace(Executors.newFixedThreadPool(4, new NumberedThreadFactory("signal-bounded", ThreadUtil.PRIORITY_BACKGROUND_THREAD)));
public static final ExecutorService SERIAL = ThreadUtil.trace(Executors.newSingleThreadExecutor(new NumberedThreadFactory("signal-serial", ThreadUtil.PRIORITY_BACKGROUND_THREAD)));
public static final ExecutorService BOUNDED_IO = ThreadUtil.trace(newCachedBoundedExecutor("signal-io-bounded", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD, 1, 32, 30));
private SignalExecutors() {}
public static ExecutorService newCachedSingleThreadExecutor(final String name) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 15, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), r -> new Thread(r, name));
public static ExecutorService newCachedSingleThreadExecutor(final String name, int priority) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 15, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), r -> new Thread(r, name) {
@Override public void run() {
Process.setThreadPriority(priority);
super.run();
}
});
executor.allowCoreThreadTimeOut(true);
return executor;
}
@@ -41,7 +47,7 @@ public final class SignalExecutors {
* So we make a queue that will always return false if it's non-empty to ensure new threads get
* created. Then, if a task gets rejected, we simply add it to the queue.
*/
public static ExecutorService newCachedBoundedExecutor(final String name, int minThreads, int maxThreads, int timeoutSeconds) {
public static ExecutorService newCachedBoundedExecutor(final String name, int priority, int minThreads, int maxThreads, int timeoutSeconds) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(minThreads,
maxThreads,
timeoutSeconds,
@@ -55,7 +61,7 @@ public final class SignalExecutors {
return false;
}
}
}, new NumberedThreadFactory(name));
}, new NumberedThreadFactory(name, priority));
threadPool.setRejectedExecutionHandler((runnable, executor) -> {
try {
@@ -73,28 +79,36 @@ public final class SignalExecutors {
* which processor work in FIFO order.
*/
public static ExecutorService newFixedLifoThreadExecutor(String name, int minThreads, int maxThreads) {
return new ThreadPoolExecutor(minThreads, maxThreads, 0, TimeUnit.MILLISECONDS, new LinkedBlockingLifoQueue<>(), new NumberedThreadFactory(name));
return new ThreadPoolExecutor(minThreads, maxThreads, 0, TimeUnit.MILLISECONDS, new LinkedBlockingLifoQueue<>(), new NumberedThreadFactory(name, ThreadUtil.PRIORITY_BACKGROUND_THREAD));
}
public static HandlerThread getAndStartHandlerThread(@NonNull String name) {
HandlerThread handlerThread = new HandlerThread(name);
public static HandlerThread getAndStartHandlerThread(@NonNull String name, int priority) {
HandlerThread handlerThread = new HandlerThread(name, priority);
handlerThread.start();
return handlerThread;
}
private static class NumberedThreadFactory implements ThreadFactory {
private final int priority;
private final String baseName;
private final AtomicInteger counter;
NumberedThreadFactory(@NonNull String baseName) {
NumberedThreadFactory(@NonNull String baseName, int priority) {
this.priority = priority;
this.baseName = baseName;
this.counter = new AtomicInteger();
}
@Override
public Thread newThread(@NonNull Runnable r) {
return new Thread(r, baseName + "-" + counter.getAndIncrement());
return new Thread(r, baseName + "-" + counter.getAndIncrement()) {
@Override
public void run() {
Process.setThreadPriority(priority);
super.run();
}
};
}
}
}