Skip to content

Commit 647d88c

Browse files
authored
Ensure that on-demand fatal events are never processed on the main thread (#6113)
Ensure that on-demand fatal events are never processed on the main thread. Also decreased the timeout on main to under 3 seconds, to give more time to other uncaught exception handlers in the chain. Also fixed some formatting. Tested cases: Uncaught exception thrown from the main thread Uncaught exception thrown from a background thread ODF triggered on the main thread ODF triggered on a background thread
1 parent d583311 commit 647d88c

File tree

4 files changed

+35
-25
lines changed

4 files changed

+35
-25
lines changed

firebase-crashlytics/CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Unreleased
2+
* [feature] Added the `isCrashlyticsCollectionEnabled` API to check if Crashlytics collection is enabled.
3+
([Github #5919](//github.com/firebase/firebase-android-sdk/issues/5919))
4+
* [fixed] Ensure that on-demand fatal events are never processed on the main thread.
25

36

47
# 19.0.3
58
* [changed] Update the internal file system to handle long file names.
6-
* [feature] Added the `isCrashlyticsCollectionEnabled` API to check if Crashlytics collection is enabled.
7-
([Github #5919](//github.com/firebase/firebase-android-sdk/issues/5919))
8-
99

1010
## Kotlin
1111
The Kotlin extensions library transitively includes the updated

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsController.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,13 @@ public Task<Void> then(@Nullable Settings settings) throws Exception {
250250
}
251251
});
252252

253+
// Block until the task completes, to prevent the process from ending before logging the report.
254+
// There might be other uncaught exception handlers in the chain, so do not block very long.
253255
try {
254-
// TODO(mrober): Don't block the main thread ever for on-demand fatals.
255-
Utils.awaitEvenIfOnMainThread(handleUncaughtExceptionTask);
256+
// Never block on ODFs, in case they get triggered from the main thread.
257+
if (!isOnDemand) {
258+
Utils.awaitEvenIfOnMainThread(handleUncaughtExceptionTask);
259+
}
256260
} catch (TimeoutException e) {
257261
Logger.getLogger().e("Cannot send reports. Timed out while fetching settings.");
258262
} catch (Exception e) {

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/Utils.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,15 @@
3131
/** Utils */
3232
@SuppressWarnings({"ResultOfMethodCallIgnored", "UnusedReturnValue"})
3333
public final class Utils {
34-
private static final int TIMEOUT_SEC = 4;
34+
/** Timeout in milliseconds for blocking on background threads. */
35+
private static final int BACKGROUND_TIMEOUT_MILLIS = 4_000;
3536

36-
/** @return A tasks that is resolved when either of the given tasks is resolved. */
37+
/** Timeout in milliseconds for blocking on the main thread. Be careful about ANRs. */
38+
private static final int MAIN_TIMEOUT_MILLIS = 2_750;
39+
40+
/**
41+
* @return A tasks that is resolved when either of the given tasks is resolved.
42+
*/
3743
// TODO(b/261014167): Use an explicit executor in continuations.
3844
@SuppressLint("TaskMainThread")
3945
public static <T> Task<T> race(Task<T> t1, Task<T> t2) {
@@ -52,7 +58,9 @@ public static <T> Task<T> race(Task<T> t1, Task<T> t2) {
5258
return result.getTask();
5359
}
5460

55-
/** @return A tasks that is resolved when either of the given tasks is resolved. */
61+
/**
62+
* @return A tasks that is resolved when either of the given tasks is resolved.
63+
*/
5664
public static <T> Task<T> race(Executor executor, Task<T> t1, Task<T> t2) {
5765
final TaskCompletionSource<T> result = new TaskCompletionSource<>();
5866
Continuation<T, Void> continuation =
@@ -119,9 +127,9 @@ public static <T> T awaitEvenIfOnMainThread(Task<T> task)
119127
});
120128

121129
if (Looper.getMainLooper() == Looper.myLooper()) {
122-
latch.await(CrashlyticsCore.DEFAULT_MAIN_HANDLER_TIMEOUT_SEC, TimeUnit.SECONDS);
130+
latch.await(MAIN_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
123131
} else {
124-
latch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
132+
latch.await(BACKGROUND_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
125133
}
126134

127135
if (task.isSuccessful()) {

firebase-crashlytics/src/test/java/com/google/firebase/crashlytics/internal/common/DataCollectionArbiterRobolectricTest.java

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,14 @@
1515
package com.google.firebase.crashlytics.internal.common;
1616

1717
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
18+
import static com.google.common.truth.Truth.assertThat;
1819
import static org.mockito.Mockito.mock;
1920
import static org.mockito.Mockito.when;
20-
import static com.google.common.truth.Truth.assertThat;
2121
import static org.robolectric.Shadows.shadowOf;
2222

2323
import android.content.Context;
2424
import android.os.Bundle;
25-
2625
import com.google.firebase.FirebaseApp;
27-
2826
import org.junit.Before;
2927
import org.junit.Test;
3028
import org.junit.runner.RunWith;
@@ -37,7 +35,7 @@ public class DataCollectionArbiterRobolectricTest {
3735
private FirebaseApp firebaseApp;
3836

3937
private static final String FIREBASE_CRASHLYTICS_COLLECTION_ENABLED =
40-
"firebase_crashlytics_collection_enabled";
38+
"firebase_crashlytics_collection_enabled";
4139

4240
@Before
4341
public void setUp() {
@@ -54,7 +52,7 @@ private DataCollectionArbiter getDataCollectionArbiter(FirebaseApp app) {
5452
public void testSetCrashlyticsDataCollectionEnabled_overridesOtherSettings() {
5553
// Ensure that Manifest metadata is set to false.
5654
editManifestApplicationMetadata(testContext)
57-
.putBoolean(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED, false);
55+
.putBoolean(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED, false);
5856

5957
// Mock FirebaseApp to return default data collection as false.
6058
when(firebaseApp.isDataCollectionDefaultEnabled()).thenReturn(false);
@@ -70,21 +68,21 @@ public void testSetCrashlyticsDataCollectionEnabled_overridesOtherSettings() {
7068
assertThat(arbiter.isAutomaticDataCollectionEnabled()).isFalse();
7169

7270
arbiter.setCrashlyticsDataCollectionEnabled(null);
73-
//Expecting `false` result since manifest metadata value is `false`
71+
// Expecting `false` result since manifest metadata value is `false`
7472
assertThat(arbiter.isAutomaticDataCollectionEnabled()).isFalse();
7573
}
7674

7775
@Test
7876
public void testManifestMetadata_respectedWhenNoOverride() {
7977
editManifestApplicationMetadata(testContext)
80-
.putBoolean(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED, true);
78+
.putBoolean(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED, true);
8179

8280
DataCollectionArbiter arbiter = getDataCollectionArbiter(firebaseApp);
8381

8482
assertThat(arbiter.isAutomaticDataCollectionEnabled()).isTrue();
8583

8684
editManifestApplicationMetadata(testContext)
87-
.putBoolean(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED, false);
85+
.putBoolean(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED, false);
8886

8987
arbiter = getDataCollectionArbiter(firebaseApp);
9088

@@ -93,8 +91,7 @@ public void testManifestMetadata_respectedWhenNoOverride() {
9391

9492
@Test
9593
public void testDefaultDataCollection_usedWhenNoOverrideOrManifestSetting() {
96-
editManifestApplicationMetadata(testContext)
97-
.remove(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED);
94+
editManifestApplicationMetadata(testContext).remove(FIREBASE_CRASHLYTICS_COLLECTION_ENABLED);
9895

9996
DataCollectionArbiter arbiter = getDataCollectionArbiter(firebaseApp);
10097

@@ -104,13 +101,14 @@ public void testDefaultDataCollection_usedWhenNoOverrideOrManifestSetting() {
104101
when(firebaseApp.isDataCollectionDefaultEnabled()).thenReturn(false);
105102
assertThat(arbiter.isAutomaticDataCollectionEnabled()).isFalse();
106103

107-
//No Test of `null` return for firebaseApp.isDataCollectionDefaultEnabled(), since it will never return `null` value
104+
// No Test of `null` return for firebaseApp.isDataCollectionDefaultEnabled(), since it will
105+
// never return `null` value
108106
}
109107

110108
private Bundle editManifestApplicationMetadata(Context context) {
111109
return shadowOf(context.getPackageManager())
112-
.getInternalMutablePackageInfo(context.getPackageName())
113-
.applicationInfo
114-
.metaData;
110+
.getInternalMutablePackageInfo(context.getPackageName())
111+
.applicationInfo
112+
.metaData;
115113
}
116-
}
114+
}

0 commit comments

Comments
 (0)