diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index df189ed8c..b2d5edef7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -192,6 +192,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr ApplicationDependencies.getFrameRateTracker().start(); ApplicationDependencies.getMegaphoneRepository().onAppForegrounded(); + ApplicationDependencies.getDeadlockDetector().start(); SignalExecutors.BOUNDED.execute(() -> { FeatureFlags.refreshIfNecessary(); @@ -202,7 +203,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr KeyCachingService.onAppForegrounded(this); ApplicationDependencies.getShakeToReport().enable(); checkBuildExpiration(); - ApplicationDependencies.getDeadlockDetector().start(); }); Log.d(TAG, "onStart() took " + (System.currentTimeMillis() - startTime) + " ms"); diff --git a/core-util/src/main/java/org/signal/core/util/concurrent/DeadlockDetector.kt b/core-util/src/main/java/org/signal/core/util/concurrent/DeadlockDetector.kt index aee06a5e7..eace302ca 100644 --- a/core-util/src/main/java/org/signal/core/util/concurrent/DeadlockDetector.kt +++ b/core-util/src/main/java/org/signal/core/util/concurrent/DeadlockDetector.kt @@ -2,6 +2,8 @@ package org.signal.core.util.concurrent import android.os.Handler import org.signal.core.util.logging.Log +import java.util.concurrent.ExecutorService +import java.util.concurrent.ThreadPoolExecutor /** * A class that polls active threads at a set interval and logs when multiple threads are BLOCKED. @@ -32,22 +34,32 @@ class DeadlockDetector(private val handler: Handler, private val pollingInterval thread.state == Thread.State.BLOCKED || (thread.state == Thread.State.WAITING && stack.any { it.methodName.startsWith("lock") }) } val blockedIds: Set = blocked.keys.map(Thread::getId).toSet() + val stillBlocked: Set = blockedIds.intersect(previouslyBlocked) if (blocked.size > 1) { Log.w(TAG, buildLogString("Found multiple blocked threads! Possible deadlock.", blocked)) - } else { - val stillBlocked: Set = blockedIds.intersect(previouslyBlocked) + } else if (stillBlocked.isNotEmpty()) { + val stillBlockedMap: Map> = stillBlocked + .map { blockedId -> + val key: Thread = blocked.keys.first { it.id == blockedId } + val value: Array = blocked[key]!! + Pair(key, value) + } + .toMap() - if (stillBlocked.isNotEmpty()) { - val stillBlockedMap: Map> = stillBlocked - .map { blockedId -> - val key: Thread = blocked.keys.first { it.id == blockedId } - val value: Array = blocked[key]!! - Pair(key, value) - } + Log.w(TAG, buildLogString("Found a long block! Blocked for at least $pollingInterval ms.", stillBlockedMap)) + } + + val fullExecutors: List = EXECUTORS.filter { isExecutorFull(it.executor) } + + if (fullExecutors.isNotEmpty()) { + fullExecutors.forEach { executorInfo -> + val fullMap: Map> = threads + .filter { it.key.name.startsWith(executorInfo.namePrefix) } .toMap() - Log.w(TAG, buildLogString("Found a long block! Blocked for at least $pollingInterval ms.", stillBlockedMap)) + val executor: ThreadPoolExecutor = executorInfo.executor as ThreadPoolExecutor + Log.w(TAG, buildLogString("Found a full executor! ${executor.activeCount}/${executor.corePoolSize} threads active with ${executor.queue.size} tasks queued.", fullMap)) } } @@ -59,9 +71,21 @@ class DeadlockDetector(private val handler: Handler, private val pollingInterval } } + private data class ExecutorInfo( + val executor: ExecutorService, + val namePrefix: String + ) + companion object { private val TAG = Log.tag(DeadlockDetector::class.java) + private val EXECUTORS: Set = setOf( + ExecutorInfo(SignalExecutors.BOUNDED, "signal-bounded-"), + ExecutorInfo(SignalExecutors.BOUNDED_IO, "signal-bounded-io-") + ) + + private const val CONCERNING_QUEUE_THRESHOLD = 4 + private fun buildLogString(description: String, blocked: Map>): String { val stringBuilder = StringBuilder() stringBuilder.append(description).append("\n") @@ -78,5 +102,13 @@ class DeadlockDetector(private val handler: Handler, private val pollingInterval return stringBuilder.toString() } + + private fun isExecutorFull(executor: ExecutorService): Boolean { + return if (executor is ThreadPoolExecutor) { + executor.queue.size > CONCERNING_QUEUE_THRESHOLD + } else { + false + } + } } -} \ No newline at end of file +}