Add an observer to log blocked threads.

This commit is contained in:
Greyson Parrelli 2021-10-08 15:18:52 -04:00
parent f65de84c19
commit 0c8b6f8ef8
10 changed files with 194 additions and 15 deletions

View file

@ -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() {

View file

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

View file

@ -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

View file

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

View file

@ -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

View file

@ -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<Thread, Array<StackTraceElement>>): 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()
}
}
}

View file

@ -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'],
]
}

View file

@ -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'],
]

View file

@ -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'],
]
}

View file

@ -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'],