From 0c8b6f8ef86c5200f1e5eb3a24c239935d17e6fc Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 8 Oct 2021 15:18:52 -0400 Subject: [PATCH] Add an observer to log blocked threads. --- .../securesms/ApplicationContext.java | 6 +- .../dependencies/ApplicationDependencies.java | 14 +++++ .../ApplicationDependencyProvider.java | 11 ++++ .../securesms/util/FrameRateTracker.java | 4 +- core-util/build.gradle | 14 ++++- .../core/util/concurrent/DeadlockDetector.kt | 60 +++++++++++++++++++ core-util/witness-verifications.gradle | 30 ++++++++++ .../lib/witness-verifications.gradle | 20 +++++-- paging/lib/witness-verifications.gradle | 20 +++++-- video/witness-verifications.gradle | 30 ++++++++++ 10 files changed, 194 insertions(+), 15 deletions(-) create mode 100644 core-util/src/main/java/org/signal/core/util/concurrent/DeadlockDetector.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index aa0113dab..62d0a6541 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -191,7 +191,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr long startTime = System.currentTimeMillis(); Log.i(TAG, "App is now visible."); - ApplicationDependencies.getFrameRateTracker().begin(); + ApplicationDependencies.getFrameRateTracker().start(); ApplicationDependencies.getMegaphoneRepository().onAppForegrounded(); SignalExecutors.BOUNDED.execute(() -> { @@ -203,6 +203,7 @@ 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"); @@ -213,8 +214,9 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr Log.i(TAG, "App is no longer visible."); KeyCachingService.onAppBackgrounded(this); ApplicationDependencies.getMessageNotifier().clearVisibleThread(); - ApplicationDependencies.getFrameRateTracker().end(); + ApplicationDependencies.getFrameRateTracker().stop(); ApplicationDependencies.getShakeToReport().disable(); + ApplicationDependencies.getDeadlockDetector().stop(); } public PersistentLogger getPersistentLogger() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index d986e890f..6a51f6719 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -5,6 +5,7 @@ import android.app.Application; import androidx.annotation.MainThread; import androidx.annotation.NonNull; +import org.signal.core.util.concurrent.DeadlockDetector; import org.thoughtcrime.securesms.KbsEnclave; import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusSender; @@ -106,6 +107,7 @@ public class ApplicationDependencies { private static volatile SimpleExoPlayerPool exoPlayerPool; private static volatile AudioManagerCompat audioManagerCompat; private static volatile DonationsService donationsService; + private static volatile DeadlockDetector deadlockDetector; @MainThread public static void init(@NonNull Application application, @NonNull Provider provider) { @@ -603,6 +605,17 @@ public class ApplicationDependencies { return donationsService; } + public static @NonNull DeadlockDetector getDeadlockDetector() { + if (deadlockDetector == null) { + synchronized (LOCK) { + if (deadlockDetector == null) { + deadlockDetector = provider.provideDeadlockDetector(); + } + } + } + return deadlockDetector; + } + public interface Provider { @NonNull GroupsV2Operations provideGroupsV2Operations(); @NonNull SignalServiceAccountManager provideSignalServiceAccountManager(); @@ -639,5 +652,6 @@ public class ApplicationDependencies { @NonNull SimpleExoPlayerPool provideExoPlayerPool(); @NonNull AudioManagerCompat provideAndroidCallAudioManager(); @NonNull DonationsService provideDonationsService(); + @NonNull DeadlockDetector provideDeadlockDetector(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index f898f2cf1..cb5d71068 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -2,9 +2,12 @@ package org.thoughtcrime.securesms.dependencies; import android.app.Application; import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; import androidx.annotation.NonNull; +import org.signal.core.util.concurrent.DeadlockDetector; import org.signal.core.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.components.TypingStatusRepository; @@ -76,6 +79,7 @@ import org.whispersystems.signalservice.api.websocket.WebSocketFactory; import org.whispersystems.signalservice.internal.websocket.WebSocketConnection; import java.util.UUID; +import java.util.concurrent.TimeUnit; /** * Implementation of {@link ApplicationDependencies.Provider} that provides real app dependencies. @@ -311,6 +315,13 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr FeatureFlags.okHttpAutomaticRetry()); } + @Override + public @NonNull DeadlockDetector provideDeadlockDetector() { + HandlerThread handlerThread = new HandlerThread("signal-DeadlockDetector"); + handlerThread.start(); + return new DeadlockDetector(new Handler(handlerThread.getLooper()), TimeUnit.SECONDS.toMillis(5)); + } + private @NonNull WebSocketFactory provideWebSocketFactory(@NonNull SignalWebSocketHealthMonitor healthMonitor) { return new WebSocketFactory() { @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FrameRateTracker.java b/app/src/main/java/org/thoughtcrime/securesms/util/FrameRateTracker.java index df2e29ae3..7319b6642 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FrameRateTracker.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FrameRateTracker.java @@ -40,7 +40,7 @@ public class FrameRateTracker { updateRefreshRate(); } - public void begin() { + public void start() { Log.d(TAG, String.format(Locale.ENGLISH, "Beginning frame rate tracking. Screen refresh rate: %.2f hz, or %.2f ms per frame.", refreshRate, idealTimePerFrameNanos / (float) 1_000_000)); lastFrameTimeNanos = System.nanoTime(); @@ -48,7 +48,7 @@ public class FrameRateTracker { Choreographer.getInstance().postFrameCallback(calculator); } - public void end() { + public void stop() { Choreographer.getInstance().removeFrameCallback(calculator); } diff --git a/core-util/build.gradle b/core-util/build.gradle index 94bb4e319..d00cf2871 100644 --- a/core-util/build.gradle +++ b/core-util/build.gradle @@ -1,6 +1,10 @@ -apply plugin: 'com.android.library' -apply plugin: 'com.google.protobuf' -apply plugin: 'witness' +plugins { + id 'com.android.library' + id 'com.google.protobuf' + id 'witness' + id 'kotlin-android' + id 'kotlin-kapt' +} apply from: 'witness-verifications.gradle' android { @@ -18,6 +22,9 @@ android { sourceCompatibility JAVA_VERSION targetCompatibility JAVA_VERSION } + kotlinOptions { + jvmTarget = '1.8' + } } dependencyVerification { @@ -46,6 +53,7 @@ dependencies { api libs.androidx.annotation + implementation libs.androidx.core.ktx implementation libs.google.protobuf.javalite testImplementation testLibs.junit.junit 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 new file mode 100644 index 000000000..f638ed507 --- /dev/null +++ b/core-util/src/main/java/org/signal/core/util/concurrent/DeadlockDetector.kt @@ -0,0 +1,60 @@ +package org.signal.core.util.concurrent + +import android.os.Handler +import android.os.SystemClock +import org.signal.core.util.logging.Log +import java.util.concurrent.TimeUnit + +/** + * A class that polls active threads at a set interval and logs when multiple threads are BLOCKED. + */ +class DeadlockDetector(private val handler: Handler, private val pollingInterval: Long) { + + private var running = false + + fun start() { + Log.d(TAG, "Beginning deadlock monitoring."); + running = true + handler.postDelayed(this::poll, pollingInterval) + } + + fun stop() { + Log.d(TAG, "Ending deadlock monitoring."); + running = false + handler.removeCallbacksAndMessages(null) + } + + private fun poll() { + val threads = Thread.getAllStackTraces() + val blocked = threads.filterKeys { thread -> thread.state == Thread.State.BLOCKED } + + if (blocked.size > 1) { + Log.w(TAG, buildLogString(blocked)) + } + + if (running) { + handler.postDelayed(this::poll, pollingInterval) + } + } + + companion object { + private val TAG = Log.tag(DeadlockDetector::class.java) + + private fun buildLogString(blocked: Map>): String { + val stringBuilder = StringBuilder() + stringBuilder.append("Found multiple blocked threads! Possible deadlock.\n") + + for (entry in blocked) { + stringBuilder.append("-- [${entry.key.id}] ${entry.key.name}\n") + + for (element in entry.value) { + stringBuilder.append("$element\n") + } + + stringBuilder.append("\n") + } + + return stringBuilder.toString() + } + } +} \ No newline at end of file diff --git a/core-util/witness-verifications.gradle b/core-util/witness-verifications.gradle index 91d6a56ed..ffac9e3c7 100644 --- a/core-util/witness-verifications.gradle +++ b/core-util/witness-verifications.gradle @@ -6,7 +6,37 @@ dependencyVerification { ['androidx.annotation:annotation:1.2.0', '9029262bddce116e6d02be499e4afdba21f24c239087b76b3b57d7e98b490a36'], + ['androidx.arch.core:core-common:2.0.0', + '4b80b337779b526e64b0ee0ca9e0df43b808344d145f8e9b1c42a134dac57ad8'], + + ['androidx.collection:collection:1.0.0', + '9c8d117b5c2bc120a1cdfeb857e05b495b16c36013570372a708f7827e3ac9f9'], + + ['androidx.core:core-ktx:1.5.0', + '5964cfe7a4882da2a00fb6ca3d3a072d04139208186f7bc4b3cb66022764fc42'], + + ['androidx.core:core:1.5.0', + '2b279712795689069cfb63e48b3ab63c32a5649bdda44c482eb8f81ca1a72161'], + + ['androidx.lifecycle:lifecycle-common:2.0.0', + '7bad7a188804adea6fa1f35d5ef99b705f20bd93ecadde484760ff86b535fefc'], + + ['androidx.lifecycle:lifecycle-runtime:2.0.0', + 'e4afc9e636183f6f3e0edf1cf46121a492ffd2c673075bb07f55c7a99dd43cfb'], + + ['androidx.versionedparcelable:versionedparcelable:1.1.1', + '57e8d93260d18d5b9007c9eed3c64ad159de90c8609ebfc74a347cbd514535a4'], + ['com.google.protobuf:protobuf-javalite:3.10.0', '215a94dbe100130295906b531bb72a26965c7ac8fcd9a75bf8054a8ac2abf4b4'], + + ['org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32', + 'e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145'], + + ['org.jetbrains.kotlin:kotlin-stdlib:1.4.32', + '13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba'], + + ['org.jetbrains:annotations:13.0', + 'ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478'], ] } diff --git a/device-transfer/lib/witness-verifications.gradle b/device-transfer/lib/witness-verifications.gradle index 4492eabec..58841e8c3 100644 --- a/device-transfer/lib/witness-verifications.gradle +++ b/device-transfer/lib/witness-verifications.gradle @@ -24,8 +24,11 @@ dependencyVerification { ['androidx.collection:collection:1.1.0', '632a0e5407461de774409352940e292a291037724207a787820c77daf7d33b72'], - ['androidx.core:core:1.3.0', - '1c6b6626f15185d8f4bc7caac759412a1ab6e851ecf7526387d9b9fadcabdb63'], + ['androidx.core:core-ktx:1.5.0', + '5964cfe7a4882da2a00fb6ca3d3a072d04139208186f7bc4b3cb66022764fc42'], + + ['androidx.core:core:1.5.0', + '2b279712795689069cfb63e48b3ab63c32a5649bdda44c482eb8f81ca1a72161'], ['androidx.cursoradapter:cursoradapter:1.0.0', 'a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564'], @@ -69,8 +72,8 @@ dependencyVerification { ['androidx.vectordrawable:vectordrawable:1.1.0', '46fd633ac01b49b7fcabc263bf098c5a8b9e9a69774d234edcca04fb02df8e26'], - ['androidx.versionedparcelable:versionedparcelable:1.1.0', - '9a1d77140ac222b7866b5054ee7d159bc1800987ed2d46dd6afdd145abb710c1'], + ['androidx.versionedparcelable:versionedparcelable:1.1.1', + '57e8d93260d18d5b9007c9eed3c64ad159de90c8609ebfc74a347cbd514535a4'], ['androidx.viewpager:viewpager:1.0.0', '147af4e14a1984010d8f155e5e19d781f03c1d70dfed02a8e0d18428b8fc8682'], @@ -81,6 +84,15 @@ dependencyVerification { ['org.greenrobot:eventbus:3.0.0', '180d4212467df06f2fbc9c8d8a2984533ac79c87769ad883bc421612f0b4e17c'], + ['org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32', + 'e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145'], + + ['org.jetbrains.kotlin:kotlin-stdlib:1.4.32', + '13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba'], + + ['org.jetbrains:annotations:13.0', + 'ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478'], + ['org.whispersystems:signal-client-java:0.9.6', 'f64aeb071eae2e1e2413902da6554c03e22f66d7a59ffdd79f3beeb0248ea054'], ] diff --git a/paging/lib/witness-verifications.gradle b/paging/lib/witness-verifications.gradle index 2d744ab1b..aac30f8e9 100644 --- a/paging/lib/witness-verifications.gradle +++ b/paging/lib/witness-verifications.gradle @@ -39,8 +39,11 @@ dependencyVerification { ['androidx.coordinatorlayout:coordinatorlayout:1.1.0', '44a9e30abf56af1025c52a0af506fee9c4131aa55efda52f9fd9451211c5e8cb'], - ['androidx.core:core:1.3.1', - 'e92ea65a37d589943d405a6a54d1be9d12a225948f26c4e41e511dd55e81efb6'], + ['androidx.core:core-ktx:1.5.0', + '5964cfe7a4882da2a00fb6ca3d3a072d04139208186f7bc4b3cb66022764fc42'], + + ['androidx.core:core:1.5.0', + '2b279712795689069cfb63e48b3ab63c32a5649bdda44c482eb8f81ca1a72161'], ['androidx.cursoradapter:cursoradapter:1.0.0', 'a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564'], @@ -105,8 +108,8 @@ dependencyVerification { ['androidx.vectordrawable:vectordrawable:1.1.0', '46fd633ac01b49b7fcabc263bf098c5a8b9e9a69774d234edcca04fb02df8e26'], - ['androidx.versionedparcelable:versionedparcelable:1.1.0', - '9a1d77140ac222b7866b5054ee7d159bc1800987ed2d46dd6afdd145abb710c1'], + ['androidx.versionedparcelable:versionedparcelable:1.1.1', + '57e8d93260d18d5b9007c9eed3c64ad159de90c8609ebfc74a347cbd514535a4'], ['androidx.viewpager2:viewpager2:1.0.0', 'e95c0031d4cc247cd48196c6287e58d2cee54d9c79b85afea7c90920330275af'], @@ -119,5 +122,14 @@ dependencyVerification { ['com.google.protobuf:protobuf-javalite:3.10.0', '215a94dbe100130295906b531bb72a26965c7ac8fcd9a75bf8054a8ac2abf4b4'], + + ['org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32', + 'e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145'], + + ['org.jetbrains.kotlin:kotlin-stdlib:1.4.32', + '13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba'], + + ['org.jetbrains:annotations:13.0', + 'ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478'], ] } diff --git a/video/witness-verifications.gradle b/video/witness-verifications.gradle index ba56007b9..fede72105 100644 --- a/video/witness-verifications.gradle +++ b/video/witness-verifications.gradle @@ -6,9 +6,39 @@ dependencyVerification { ['androidx.annotation:annotation:1.2.0', '9029262bddce116e6d02be499e4afdba21f24c239087b76b3b57d7e98b490a36'], + ['androidx.arch.core:core-common:2.0.0', + '4b80b337779b526e64b0ee0ca9e0df43b808344d145f8e9b1c42a134dac57ad8'], + + ['androidx.collection:collection:1.0.0', + '9c8d117b5c2bc120a1cdfeb857e05b495b16c36013570372a708f7827e3ac9f9'], + + ['androidx.core:core-ktx:1.5.0', + '5964cfe7a4882da2a00fb6ca3d3a072d04139208186f7bc4b3cb66022764fc42'], + + ['androidx.core:core:1.5.0', + '2b279712795689069cfb63e48b3ab63c32a5649bdda44c482eb8f81ca1a72161'], + + ['androidx.lifecycle:lifecycle-common:2.0.0', + '7bad7a188804adea6fa1f35d5ef99b705f20bd93ecadde484760ff86b535fefc'], + + ['androidx.lifecycle:lifecycle-runtime:2.0.0', + 'e4afc9e636183f6f3e0edf1cf46121a492ffd2c673075bb07f55c7a99dd43cfb'], + + ['androidx.versionedparcelable:versionedparcelable:1.1.1', + '57e8d93260d18d5b9007c9eed3c64ad159de90c8609ebfc74a347cbd514535a4'], + ['com.google.protobuf:protobuf-javalite:3.10.0', '215a94dbe100130295906b531bb72a26965c7ac8fcd9a75bf8054a8ac2abf4b4'], + ['org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32', + 'e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145'], + + ['org.jetbrains.kotlin:kotlin-stdlib:1.4.32', + '13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba'], + + ['org.jetbrains:annotations:13.0', + 'ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478'], + ['org.mp4parser:isoparser:1.9.39', 'a3a7172648f1ac4b2a369ecca2861317e472179c842a5217b08643ba0a1dfa12'],