Skip to content

Commit 771063f

Browse files
authored
Plumb the executor through waitForDataCollectionPermission in Crashlytics (#2925)
Plumb the executor through waitForDataCollectionPermission to resolve concurrency issue in Crashlytics when racing two tasks.
1 parent 8d3f125 commit 771063f

File tree

5 files changed

+37
-18
lines changed

5 files changed

+37
-18
lines changed

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsControllerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ public void testUploadDisabledThenOptIn() throws Exception {
413413

414414
final DataCollectionArbiter arbiter = mock(DataCollectionArbiter.class);
415415
when(arbiter.isAutomaticDataCollectionEnabled()).thenReturn(false);
416-
when(arbiter.waitForDataCollectionPermission())
416+
when(arbiter.waitForDataCollectionPermission(any(Executor.class)))
417417
.thenReturn(new TaskCompletionSource<Void>().getTask());
418418
when(arbiter.waitForAutomaticDataCollectionEnabled())
419419
.thenReturn(new TaskCompletionSource<Void>().getTask());

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/settings/DefaultSettingsControllerTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public void testCachedSettingsLoad_newInstanceIdentifier() throws Exception {
142142
when(mockSettingsJsonParser.parseSettingsJson(fetchedJson)).thenReturn(fetchedSettings);
143143

144144
TaskCompletionSource<Void> dataCollectionPermission = new TaskCompletionSource<>();
145-
when(mockDataCollectionArbiter.waitForDataCollectionPermission())
145+
when(mockDataCollectionArbiter.waitForDataCollectionPermission(any(Executor.class)))
146146
.thenReturn(dataCollectionPermission.getTask());
147147

148148
final SettingsRequest requestData = buildSettingsRequest();
@@ -187,7 +187,7 @@ public void testExpiredCachedSettingsLoad() throws Exception {
187187
when(mockSettingsJsonParser.parseSettingsJson(fetchedJson)).thenReturn(fetchedSettings);
188188

189189
TaskCompletionSource<Void> dataCollectionPermission = new TaskCompletionSource<>();
190-
when(mockDataCollectionArbiter.waitForDataCollectionPermission())
190+
when(mockDataCollectionArbiter.waitForDataCollectionPermission(any(Executor.class)))
191191
.thenReturn(dataCollectionPermission.getTask());
192192

193193
final SettingsRequest requestData = buildSettingsRequest();
@@ -270,7 +270,7 @@ public void testSkipCachedSettingsLoad() throws Exception {
270270
.thenReturn(expiredCachedSettings);
271271

272272
TaskCompletionSource<Void> dataCollectionPermission = new TaskCompletionSource<>();
273-
when(mockDataCollectionArbiter.waitForDataCollectionPermission())
273+
when(mockDataCollectionArbiter.waitForDataCollectionPermission(any(Executor.class)))
274274
.thenReturn(dataCollectionPermission.getTask());
275275

276276
final SettingsRequest requestData = buildSettingsRequest();
@@ -323,7 +323,7 @@ public void testLastDitchSettingsLoad() throws Exception {
323323
.thenReturn(expiredCachedSettings);
324324

325325
TaskCompletionSource<Void> dataCollectionPermission = new TaskCompletionSource<>();
326-
when(mockDataCollectionArbiter.waitForDataCollectionPermission())
326+
when(mockDataCollectionArbiter.waitForDataCollectionPermission(any(Executor.class)))
327327
.thenReturn(dataCollectionPermission.getTask());
328328

329329
final SettingsRequest requestData = buildSettingsRequest();
@@ -360,7 +360,7 @@ public void testNoAvailableSettingsLoad() throws Exception {
360360
when(mockCachedSettingsIo.readCachedSettings()).thenReturn(null);
361361

362362
TaskCompletionSource<Void> dataCollectionPermission = new TaskCompletionSource<>();
363-
when(mockDataCollectionArbiter.waitForDataCollectionPermission())
363+
when(mockDataCollectionArbiter.waitForDataCollectionPermission(any(Executor.class)))
364364
.thenReturn(dataCollectionPermission.getTask());
365365

366366
final SettingsRequest requestData = buildSettingsRequest();

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.android.gms.tasks.TaskCompletionSource;
2525
import com.google.firebase.FirebaseApp;
2626
import com.google.firebase.crashlytics.internal.Logger;
27+
import java.util.concurrent.Executor;
2728

2829
// Determines whether automatic data collection is enabled.
2930
public class DataCollectionArbiter {
@@ -115,9 +116,11 @@ public Task<Void> waitForAutomaticDataCollectionEnabled() {
115116
* Returns a task which will be resolved when either: 1) automatic data collection has been
116117
* enabled, or 2) grantDataCollectionPermission has been called.
117118
*/
118-
public Task<Void> waitForDataCollectionPermission() {
119+
public Task<Void> waitForDataCollectionPermission(Executor executor) {
119120
return Utils.race(
120-
dataCollectionExplicitlyApproved.getTask(), waitForAutomaticDataCollectionEnabled());
121+
executor,
122+
dataCollectionExplicitlyApproved.getTask(),
123+
waitForAutomaticDataCollectionEnabled());
121124
}
122125

123126
/**

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

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package com.google.firebase.crashlytics.internal.common;
1616

17+
import static java.util.Objects.requireNonNull;
18+
1719
import android.os.Looper;
1820
import androidx.annotation.NonNull;
1921
import com.google.android.gms.tasks.Continuation;
@@ -36,22 +38,36 @@ private Utils() {}
3638
public static <T> Task<T> race(Task<T> t1, Task<T> t2) {
3739
final TaskCompletionSource<T> result = new TaskCompletionSource<>();
3840
Continuation<T, Void> continuation =
39-
new Continuation<T, Void>() {
40-
@Override
41-
public Void then(@NonNull Task<T> task) throws Exception {
42-
if (task.isSuccessful()) {
43-
result.trySetResult(task.getResult());
44-
} else {
45-
result.trySetException(task.getException());
46-
}
47-
return null;
41+
task -> {
42+
if (task.isSuccessful()) {
43+
result.trySetResult(task.getResult());
44+
} else {
45+
result.trySetException(requireNonNull(task.getException()));
4846
}
47+
return null;
4948
};
5049
t1.continueWith(continuation);
5150
t2.continueWith(continuation);
5251
return result.getTask();
5352
}
5453

54+
/** @return A tasks that is resolved when either of the given tasks is resolved. */
55+
public static <T> Task<T> race(Executor executor, Task<T> t1, Task<T> t2) {
56+
final TaskCompletionSource<T> result = new TaskCompletionSource<>();
57+
Continuation<T, Void> continuation =
58+
task -> {
59+
if (task.isSuccessful()) {
60+
result.trySetResult(task.getResult());
61+
} else {
62+
result.trySetException(requireNonNull(task.getException()));
63+
}
64+
return null;
65+
};
66+
t1.continueWith(executor, continuation);
67+
t2.continueWith(executor, continuation);
68+
return result.getTask();
69+
}
70+
5571
/** Similar to Tasks.call, but takes a Callable that returns a Task. */
5672
public static <T> Task<T> callTask(Executor executor, Callable<Task<T>> callable) {
5773
final TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/settings/SettingsController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ public Task<Void> loadSettingsData(SettingsCacheBehavior cacheBehavior, Executor
186186

187187
// Kick off fetching fresh settings.
188188
return dataCollectionArbiter
189-
.waitForDataCollectionPermission()
189+
.waitForDataCollectionPermission(executor)
190190
.onSuccessTask(
191191
executor,
192192
new SuccessContinuation<Void, Void>() {

0 commit comments

Comments
 (0)