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

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.testing
import org.signal.core.util.ThreadUtil
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
import java.util.concurrent.CountDownLatch
@ -13,7 +14,7 @@ typealias LogPredicate = (Entry) -> Boolean
*/
class InMemoryLogger : Log.Logger() {
private val executor = SignalExecutors.newCachedSingleThreadExecutor("inmemory-logger")
private val executor = SignalExecutors.newCachedSingleThreadExecutor("inmemory-logger", ThreadUtil.PRIORITY_BACKGROUND_THREAD)
private val predicates = mutableListOf<LogPredicate>()
private val logEntries = mutableListOf<Entry>()

View file

@ -8,6 +8,8 @@ package org.signal.glide.common.executor;
import android.os.HandlerThread;
import android.os.Looper;
import org.signal.core.util.ThreadUtil;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@ -39,7 +41,7 @@ public class FrameDecoderExecutor {
public Looper getLooper(int taskId) {
int idx = taskId % sPoolNumber;
if (idx >= mHandlerThreadGroup.size()) {
HandlerThread handlerThread = new HandlerThread("FrameDecoderExecutor-" + idx);
HandlerThread handlerThread = new HandlerThread("FrameDecoderExecutor-" + idx, ThreadUtil.PRIORITY_BACKGROUND_THREAD);
handlerThread.start();
mHandlerThreadGroup.add(handlerThread);

View file

@ -7,6 +7,7 @@ import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
@ -65,6 +66,7 @@ public class AudioCodec implements Recorder {
new Thread(new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO);
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
byte[] audioRecordData = new byte[bufferSize];
ByteBuffer[] codecInputBuffers = mediaCodec.getInputBuffers();

View file

@ -9,6 +9,7 @@ import android.os.ParcelFileDescriptor;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.voice.VoiceNoteDraft;
@ -25,7 +26,7 @@ public class AudioRecorder {
private static final String TAG = Log.tag(AudioRecorder.class);
private static final ExecutorService executor = SignalExecutors.newCachedSingleThreadExecutor("signal-AudioRecorder");
private static final ExecutorService executor = SignalExecutors.newCachedSingleThreadExecutor("signal-AudioRecorder", ThreadUtil.PRIORITY_UI_BLOCKING_THREAD);
private final Context context;
private final AudioRecordingHandler uiHandler;

View file

@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import org.signal.core.util.Hex;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.DeadlockDetector;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
@ -137,7 +138,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
signalWebSocket,
Optional.of(new SecurityEventListener(context)),
provideGroupsV2Operations(signalServiceConfiguration).getProfileOperations(),
SignalExecutors.newCachedBoundedExecutor("signal-messages", 1, 16, 30),
SignalExecutors.newCachedBoundedExecutor("signal-messages", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD, 1, 16, 30),
ByteUnit.KILOBYTES.toBytes(256),
FeatureFlags.okHttpAutomaticRetry());
}
@ -373,7 +374,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
@Override
public @NonNull DeadlockDetector provideDeadlockDetector() {
HandlerThread handlerThread = new HandlerThread("signal-DeadlockDetector");
HandlerThread handlerThread = new HandlerThread("signal-DeadlockDetector", ThreadUtil.PRIORITY_BACKGROUND_THREAD);
handlerThread.start();
return new DeadlockDetector(new Handler(handlerThread.getLooper()), TimeUnit.SECONDS.toMillis(5));
}

View file

@ -27,7 +27,7 @@ private val TAG = Log.tag(JumboEmoji::class.java)
*/
object JumboEmoji {
private val executor = ThreadUtil.trace(SignalExecutors.newCachedSingleThreadExecutor("jumbo-emoji"))
private val executor = ThreadUtil.trace(SignalExecutors.newCachedSingleThreadExecutor("jumbo-emoji", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD))
const val MAX_JUMBOJI_COUNT = 5

View file

@ -7,6 +7,7 @@ import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import java.util.List;
@ -30,7 +31,7 @@ class InAppScheduler implements Scheduler {
private final Handler handler;
InAppScheduler(@NonNull JobManager jobManager) {
HandlerThread handlerThread = new HandlerThread("InAppScheduler");
HandlerThread handlerThread = new HandlerThread("InAppScheduler", ThreadUtil.PRIORITY_BACKGROUND_THREAD);
handlerThread.start();
this.jobManager = jobManager;

View file

@ -1,7 +1,10 @@
package org.thoughtcrime.securesms.jobmanager.impl;
import android.os.Process;
import androidx.annotation.NonNull;
import org.signal.core.util.ThreadUtil;
import org.thoughtcrime.securesms.jobmanager.ExecutorFactory;
import java.util.concurrent.ExecutorService;
@ -10,6 +13,11 @@ import java.util.concurrent.Executors;
public class DefaultExecutorFactory implements ExecutorFactory {
@Override
public @NonNull ExecutorService newSingleThreadExecutor(@NonNull String name) {
return Executors.newSingleThreadExecutor(r -> new Thread(r, name));
return Executors.newSingleThreadExecutor(r -> new Thread(r, name) {
@Override public void run() {
Process.setThreadPriority(ThreadUtil.PRIORITY_BACKGROUND_THREAD);
super.run();
}
});
}
}

View file

@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
@ -35,7 +36,7 @@ public final class KeyValueStore implements KeyValueReader {
private KeyValueDataSet dataSet;
public KeyValueStore(@NonNull KeyValuePersistentStorage storage) {
this.executor = SignalExecutors.newCachedSingleThreadExecutor("signal-KeyValueStore");
this.executor = SignalExecutors.newCachedSingleThreadExecutor("signal-KeyValueStore", ThreadUtil.PRIORITY_BACKGROUND_THREAD);
this.storage = storage;
}

View file

@ -8,6 +8,7 @@ import androidx.annotation.WorkerThread;
import com.annimon.stream.Stream;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
@ -56,7 +57,7 @@ public class MediaUploadRepository {
public MediaUploadRepository(@NonNull Context context) {
this.context = context;
this.uploadResults = new LinkedHashMap<>();
this.executor = SignalExecutors.newCachedSingleThreadExecutor("signal-MediaUpload");
this.executor = SignalExecutors.newCachedSingleThreadExecutor("signal-MediaUpload", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD);
}
public void startUpload(@NonNull Media media, @Nullable Recipient recipient) {

View file

@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.ExceptionUtil;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.notifications.v2.DefaultMessageNotifier;
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
@ -28,7 +29,7 @@ public class OptimizedMessageNotifier implements MessageNotifier {
@MainThread
public OptimizedMessageNotifier(@NonNull Application context) {
this.limiter = new LeakyBucketLimiter(5, 1000, new Handler(SignalExecutors.getAndStartHandlerThread("signal-notifier").getLooper()));
this.limiter = new LeakyBucketLimiter(5, 1000, new Handler(SignalExecutors.getAndStartHandlerThread("signal-notifier", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD).getLooper()));
this.defaultMessageNotifier = new DefaultMessageNotifier(context);
}

View file

@ -36,6 +36,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BuildConfig;
@ -201,7 +202,7 @@ public final class PartProvider extends BaseContentProvider {
@RequiresApi(26)
private ParcelFileDescriptor getParcelStreamProxyForAttachment(AttachmentId attachmentId) throws IOException {
StorageManager storageManager = Objects.requireNonNull(getContext().getSystemService(StorageManager.class));
HandlerThread thread = SignalExecutors.getAndStartHandlerThread("storageservice-proxy");
HandlerThread thread = SignalExecutors.getAndStartHandlerThread("storageservice-proxy", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD);
Handler handler = new Handler(thread.getLooper());
ParcelFileDescriptor parcelFileDescriptor = storageManager.openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_ONLY,

View file

@ -49,7 +49,7 @@ public final class LiveRecipientCache {
private final AtomicBoolean warmedUp;
public LiveRecipientCache(@NonNull Context context) {
this(context, ThreadUtil.trace(new FilteredExecutor(SignalExecutors.newCachedBoundedExecutor("signal-recipients", 1, 4, 15), () -> !SignalDatabase.inTransaction())));
this(context, ThreadUtil.trace(new FilteredExecutor(SignalExecutors.newCachedBoundedExecutor("signal-recipients", ThreadUtil.PRIORITY_UI_BLOCKING_THREAD, 1, 4, 15), () -> !SignalDatabase.inTransaction())));
}
@VisibleForTesting

View file

@ -16,6 +16,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.signal.core.util.PendingIntentFlags;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.ServiceUtil;
@ -30,7 +31,7 @@ public abstract class TimedEventManager<E> {
private final Handler handler;
public TimedEventManager(@NonNull Application application, @NonNull String threadName) {
HandlerThread handlerThread = new HandlerThread(threadName);
HandlerThread handlerThread = new HandlerThread(threadName, ThreadUtil.PRIORITY_BACKGROUND_THREAD);
handlerThread.start();
this.application = application;

View file

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.util
import org.signal.core.util.ThreadUtil
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.database.LocalMetricsDatabase
@ -29,7 +30,7 @@ object LocalMetrics {
private val eventsById: MutableMap<String, LocalMetricsEvent> = LRUCache(200)
private val lastSplitTimeById: MutableMap<String, Long> = LRUCache(200)
private val executor: Executor = SignalExecutors.newCachedSingleThreadExecutor("signal-LocalMetrics")
private val executor: Executor = SignalExecutors.newCachedSingleThreadExecutor("signal-LocalMetrics", ThreadUtil.PRIORITY_BACKGROUND_THREAD)
private val db: LocalMetricsDatabase by lazy { LocalMetricsDatabase.getInstance(ApplicationDependencies.getApplication()) }
@JvmStatic

View file

@ -8,6 +8,7 @@ import android.media.AudioManager
import android.media.SoundPool
import android.net.Uri
import android.os.Build
import org.signal.core.util.ThreadUtil
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
@ -21,7 +22,7 @@ private val TAG = Log.tag(SignalAudioManager::class.java)
sealed class SignalAudioManager(protected val context: Context, protected val eventListener: EventListener?) {
private var commandAndControlThread = SignalExecutors.getAndStartHandlerThread("call-audio")
private var commandAndControlThread = SignalExecutors.getAndStartHandlerThread("call-audio", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD)
protected val handler = SignalAudioHandler(commandAndControlThread.looper)
protected var state: State = State.UNINITIALIZED

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();
}
};
}
}
}

View file

@ -14,6 +14,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
@ -80,7 +81,7 @@ final class DeviceTransferClient implements Handler.Callback {
this.context = context;
this.clientTask = clientTask;
this.shutdownCallback = shutdownCallback;
this.commandAndControlThread = SignalExecutors.getAndStartHandlerThread("client-cnc");
this.commandAndControlThread = SignalExecutors.getAndStartHandlerThread("client-cnc", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD);
this.handler = new Handler(commandAndControlThread.getLooper(), this);
this.autoRestart = () -> {
Log.i(TAG, "Restarting WiFi Direct since we haven't found anything yet and it could be us.");

View file

@ -14,6 +14,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.signal.devicetransfer.SelfSignedIdentity.SelfSignedKeys;
@ -70,7 +71,7 @@ final class DeviceTransferServer implements Handler.Callback {
this.context = context;
this.serverTask = serverTask;
this.shutdownCallback = shutdownCallback;
this.commandAndControlThread = SignalExecutors.getAndStartHandlerThread("server-cnc");
this.commandAndControlThread = SignalExecutors.getAndStartHandlerThread("server-cnc", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD);
this.handler = new Handler(commandAndControlThread.getLooper(), this);
}

View file

@ -92,7 +92,7 @@ public final class WifiDirect {
WifiDirect(@NonNull Context context) {
this.context = context.getApplicationContext();
this.wifiDirectCallbacksHandler = SignalExecutors.getAndStartHandlerThread("wifi-direct-cb");
this.wifiDirectCallbacksHandler = SignalExecutors.getAndStartHandlerThread("wifi-direct-cb", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD);
}
/**

View file

@ -2,6 +2,7 @@ package org.signal.paging;
import androidx.annotation.NonNull;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
@ -23,7 +24,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
private static final String TAG = Log.tag(FixedSizePagingController.class);
private static final Executor FETCH_EXECUTOR = SignalExecutors.newCachedSingleThreadExecutor("signal-FixedSizePagingController");
private static final Executor FETCH_EXECUTOR = SignalExecutors.newCachedSingleThreadExecutor("signal-FixedSizePagingController", ThreadUtil.PRIORITY_UI_BLOCKING_THREAD);
private static final boolean DEBUG = false;
private final PagedDataSource<Key, Data> dataSource;