Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9ed73c4

Browse files
authoredAug 6, 2024··
Create a util for Crashlytics tasks, and move race into it (#6158)
Create a util for Crashlytics-specific Tasks, and move `race` into it. This util class will hold other things later, so they don't make the worker class a mess. We will need something like `submitWaiting` next to block until a task is complete, this is how data collection is implemented.
1 parent 0605925 commit 9ed73c4

File tree

5 files changed

+414
-186
lines changed

5 files changed

+414
-186
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.crashlytics.internal.concurrency;
18+
19+
import java.util.concurrent.ExecutorService;
20+
import java.util.concurrent.Executors;
21+
22+
/** Convenience methods for use in Crashlytics concurrency tests. */
23+
class ConcurrencyTesting {
24+
25+
/** Returns the current thread's name. */
26+
static String getThreadName() {
27+
return Thread.currentThread().getName();
28+
}
29+
30+
/** Creates a simple executor that runs on a single named thread. */
31+
static ExecutorService newNamedSingleThreadExecutor(String name) {
32+
return Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, name));
33+
}
34+
35+
/** Convenient sleep method that propagates the interruption, but does not throw. */
36+
static void sleep(long millis) {
37+
try {
38+
Thread.sleep(millis);
39+
} catch (InterruptedException ex) {
40+
Thread.currentThread().interrupt();
41+
}
42+
}
43+
44+
/** Helps to de-flake a test. */
45+
static void deflake() {
46+
// An easy, but ugly, way to fix a flaky test.
47+
sleep(1);
48+
}
49+
50+
private ConcurrencyTesting() {}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.crashlytics.internal.concurrency;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static com.google.firebase.crashlytics.internal.concurrency.ConcurrencyTesting.sleep;
21+
import static org.junit.Assert.assertThrows;
22+
23+
import com.google.android.gms.tasks.Task;
24+
import com.google.android.gms.tasks.TaskCompletionSource;
25+
import com.google.android.gms.tasks.Tasks;
26+
import com.google.firebase.concurrent.TestOnlyExecutors;
27+
import java.util.concurrent.CancellationException;
28+
import java.util.concurrent.ExecutionException;
29+
import java.util.concurrent.Executors;
30+
import java.util.concurrent.TimeUnit;
31+
import java.util.concurrent.TimeoutException;
32+
import org.junit.Test;
33+
34+
public class CrashlyticsTasksTest {
35+
36+
@Test
37+
public void raceReturnsFirstResult() throws Exception {
38+
// Create 2 tasks on different workers to race.
39+
Task<String> task1 =
40+
new CrashlyticsWorker(TestOnlyExecutors.background())
41+
.submit(
42+
() -> {
43+
sleep(200);
44+
return "first";
45+
});
46+
Task<String> task2 =
47+
new CrashlyticsWorker(TestOnlyExecutors.background())
48+
.submit(
49+
() -> {
50+
sleep(400);
51+
return "slow";
52+
});
53+
54+
Task<String> task = CrashlyticsTasks.race(task1, task2);
55+
String result = Tasks.await(task);
56+
57+
assertThat(result).isEqualTo("first");
58+
}
59+
60+
@Test
61+
public void raceReturnsFirstException() {
62+
// Create 2 tasks on different workers to race.
63+
Task<String> task1 =
64+
new CrashlyticsWorker(TestOnlyExecutors.background())
65+
.submitTask(
66+
() -> {
67+
sleep(200);
68+
return Tasks.forException(new ArithmeticException());
69+
});
70+
Task<String> task2 =
71+
new CrashlyticsWorker(TestOnlyExecutors.background())
72+
.submitTask(
73+
() -> {
74+
sleep(400);
75+
return Tasks.forException(new IllegalStateException());
76+
});
77+
78+
Task<String> task = CrashlyticsTasks.race(task1, task2);
79+
ExecutionException thrown = assertThrows(ExecutionException.class, () -> Tasks.await(task));
80+
81+
// The first task throws an ArithmeticException.
82+
assertThat(thrown).hasCauseThat().isInstanceOf(ArithmeticException.class);
83+
}
84+
85+
@Test
86+
public void raceFirstCancelsReturnsSecondResult() throws Exception {
87+
// Create 2 tasks on different workers to race.
88+
Task<String> task1 =
89+
new CrashlyticsWorker(TestOnlyExecutors.background())
90+
.submitTask(
91+
() -> {
92+
sleep(200);
93+
return Tasks.forCanceled();
94+
});
95+
Task<String> task2 =
96+
new CrashlyticsWorker(TestOnlyExecutors.background())
97+
.submitTask(
98+
() -> {
99+
sleep(400);
100+
return Tasks.forResult("I am slow but didn't cancel.");
101+
});
102+
103+
Task<String> task = CrashlyticsTasks.race(task1, task2);
104+
String result = Tasks.await(task);
105+
106+
assertThat(result).isEqualTo("I am slow but didn't cancel.");
107+
}
108+
109+
@Test
110+
public void raceBothCancel() {
111+
// Create 2 tasks on different workers to race.
112+
Task<String> task1 =
113+
new CrashlyticsWorker(TestOnlyExecutors.background())
114+
.submitTask(
115+
() -> {
116+
sleep(200);
117+
return Tasks.forCanceled();
118+
});
119+
Task<String> task2 =
120+
new CrashlyticsWorker(TestOnlyExecutors.background())
121+
.submitTask(
122+
() -> {
123+
sleep(400);
124+
return Tasks.forCanceled();
125+
});
126+
127+
Task<String> task = CrashlyticsTasks.race(task1, task2);
128+
129+
// Both cancelled, so cancel the race result.
130+
assertThrows(CancellationException.class, () -> Tasks.await(task));
131+
}
132+
133+
@Test
134+
public void raceTasksOnSameWorker() throws Exception {
135+
CrashlyticsWorker worker = new CrashlyticsWorker(TestOnlyExecutors.background());
136+
137+
// Create 2 tasks on the same worker to race.
138+
Task<String> task1 =
139+
worker.submit(
140+
() -> {
141+
sleep(20);
142+
return "first";
143+
});
144+
Task<String> task2 =
145+
worker.submit(
146+
() -> {
147+
sleep(30);
148+
return "second";
149+
});
150+
151+
Task<String> task = CrashlyticsTasks.race(task1, task2);
152+
String result = Tasks.await(task);
153+
154+
assertThat(result).isEqualTo("first");
155+
}
156+
157+
@Test
158+
public void raceTasksOnSameSingleThreadWorker() throws Exception {
159+
CrashlyticsWorker worker = new CrashlyticsWorker(Executors.newSingleThreadExecutor());
160+
161+
// Create 2 tasks on the same worker to race.
162+
Task<String> task1 = worker.submit(() -> "first");
163+
Task<String> task2 = worker.submit(() -> "second");
164+
165+
Task<String> task = CrashlyticsTasks.race(task1, task2);
166+
String result = Tasks.await(task);
167+
168+
// The first task is submitted to this single thread worker first, so will always be first.
169+
assertThat(result).isEqualTo("first");
170+
}
171+
172+
@Test
173+
public void raceTaskOneOnWorkerAnotherNeverCompletes() throws Exception {
174+
// Create a task on a worker, and another that never completes, to race.
175+
Task<String> task1 =
176+
new CrashlyticsWorker(TestOnlyExecutors.background()).submit(() -> "first");
177+
Task<String> task2 = new TaskCompletionSource<String>().getTask();
178+
179+
Task<String> task = CrashlyticsTasks.race(task1, task2);
180+
String result = Tasks.await(task);
181+
182+
assertThat(result).isEqualTo("first");
183+
}
184+
185+
@Test
186+
public void raceTaskOneOnWorkerAnotherOtherThatCompletesFirst() throws Exception {
187+
CrashlyticsWorker worker = new CrashlyticsWorker(TestOnlyExecutors.background());
188+
189+
// Add a decoy task to the worker to take up some time.
190+
worker.submitTask(
191+
() -> {
192+
sleep(200);
193+
return Tasks.forResult(null);
194+
});
195+
196+
// Create a task on this worker, and another, to race.
197+
Task<String> task1 = worker.submit(() -> "worker");
198+
TaskCompletionSource<String> task2 = new TaskCompletionSource<>();
199+
task2.trySetResult("other");
200+
201+
Task<String> task = CrashlyticsTasks.race(task1, task2.getTask());
202+
String result = Tasks.await(task);
203+
204+
// The other tasks completes first because the first task is queued up later on the worker.
205+
assertThat(result).isEqualTo("other");
206+
}
207+
208+
@Test
209+
public void raceNoExecutor() throws Exception {
210+
// Create tasks with no explicit executor.
211+
TaskCompletionSource<String> task1 = new TaskCompletionSource<>();
212+
TaskCompletionSource<String> task2 = new TaskCompletionSource<>();
213+
214+
Task<String> task = CrashlyticsTasks.race(task1.getTask(), task2.getTask());
215+
216+
// Set a task result from another thread.
217+
new Thread(
218+
() -> {
219+
sleep(300);
220+
task1.trySetResult("yes");
221+
})
222+
.start();
223+
224+
String result = Tasks.await(task);
225+
226+
assertThat(result).isEqualTo("yes");
227+
}
228+
229+
@Test
230+
public void raceTasksThatNeverResolve() {
231+
// Create tasks that will never resolve.
232+
Task<String> task1 = new TaskCompletionSource<String>().getTask();
233+
Task<String> task2 = new TaskCompletionSource<String>().getTask();
234+
235+
Task<String> task = CrashlyticsTasks.race(task1, task2);
236+
237+
// Since the tasks never resolve, the await will timeout.
238+
assertThrows(TimeoutException.class, () -> Tasks.await(task, 300, TimeUnit.MILLISECONDS));
239+
}
240+
}

‎firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/concurrency/CrashlyticsWorkerTest.java

Lines changed: 53 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
package com.google.firebase.crashlytics.internal.concurrency;
1818

1919
import static com.google.common.truth.Truth.assertThat;
20+
import static com.google.firebase.crashlytics.internal.concurrency.ConcurrencyTesting.deflake;
21+
import static com.google.firebase.crashlytics.internal.concurrency.ConcurrencyTesting.getThreadName;
22+
import static com.google.firebase.crashlytics.internal.concurrency.ConcurrencyTesting.newNamedSingleThreadExecutor;
23+
import static com.google.firebase.crashlytics.internal.concurrency.ConcurrencyTesting.sleep;
2024
import static org.junit.Assert.assertThrows;
2125

2226
import com.google.android.gms.tasks.Task;
23-
import com.google.android.gms.tasks.TaskCompletionSource;
2427
import com.google.android.gms.tasks.Tasks;
2528
import com.google.firebase.concurrent.TestOnlyExecutors;
2629
import java.io.IOException;
@@ -30,6 +33,7 @@
3033
import java.util.Set;
3134
import java.util.concurrent.CancellationException;
3235
import java.util.concurrent.ExecutionException;
36+
import java.util.concurrent.ExecutorService;
3337
import java.util.concurrent.Executors;
3438
import java.util.concurrent.ThreadPoolExecutor;
3539
import java.util.concurrent.TimeUnit;
@@ -59,7 +63,7 @@ public void executesTasksOnThreadPool() throws Exception {
5963

6064
// Find thread names by adding the names we touch to the set.
6165
for (int i = 0; i < 100; i++) {
62-
crashlyticsWorker.submit(() -> threads.add(Thread.currentThread().getName()));
66+
crashlyticsWorker.submit(() -> threads.add(getThreadName()));
6367
}
6468

6569
crashlyticsWorker.await();
@@ -396,16 +400,16 @@ public void submitTaskFromAnotherWorkerDoesNotUseLocalThreads() throws Exception
396400
// 1 active thread when doing a local task.
397401
assertThat(Tasks.await(localWorker.submit(localExecutor::getActiveCount))).isEqualTo(1);
398402

399-
sleep(1); // The test is a bit flaky without this.
400-
401403
// 0 active local threads when waiting for other task.
402404
// Waiting for a task from another worker does not block a local thread.
405+
deflake();
403406
assertThat(Tasks.await(localWorker.submitTask(() -> otherTask))).isEqualTo(0);
404407

405408
// 1 active thread when doing a task.
406409
assertThat(Tasks.await(localWorker.submit(localExecutor::getActiveCount))).isEqualTo(1);
407410

408411
// No active threads after.
412+
deflake();
409413
assertThat(localExecutor.getActiveCount()).isEqualTo(0);
410414
}
411415

@@ -522,163 +526,59 @@ public void submitTaskWithContinuationExecutesInOrder() throws Exception {
522526
}
523527

524528
@Test
525-
public void raceReturnsFirstResult() throws Exception {
526-
// Create 2 tasks on different workers to race.
527-
Task<String> task1 =
528-
new CrashlyticsWorker(TestOnlyExecutors.background())
529-
.submit(
530-
() -> {
531-
sleep(200);
532-
return "first";
533-
});
534-
Task<String> task2 =
535-
new CrashlyticsWorker(TestOnlyExecutors.background())
536-
.submit(
537-
() -> {
538-
sleep(400);
539-
return "slow";
540-
});
541-
542-
Task<String> task = crashlyticsWorker.race(task1, task2);
543-
String result = Tasks.await(task);
544-
545-
assertThat(result).isEqualTo("first");
546-
}
547-
548-
@Test
549-
public void raceReturnsFirstException() {
550-
// Create 2 tasks on different workers to race.
551-
Task<String> task1 =
552-
new CrashlyticsWorker(TestOnlyExecutors.background())
553-
.submitTask(
554-
() -> {
555-
sleep(200);
556-
return Tasks.forException(new ArithmeticException());
557-
});
558-
Task<String> task2 =
559-
new CrashlyticsWorker(TestOnlyExecutors.background())
560-
.submitTask(
561-
() -> {
562-
sleep(400);
563-
return Tasks.forException(new IllegalStateException());
564-
});
565-
566-
Task<String> task = crashlyticsWorker.race(task1, task2);
567-
ExecutionException thrown = assertThrows(ExecutionException.class, () -> Tasks.await(task));
568-
569-
// The first task throws an ArithmeticException.
570-
assertThat(thrown).hasCauseThat().isInstanceOf(ArithmeticException.class);
571-
}
572-
573-
@Test
574-
public void raceFirstCancelsReturnsSecondResult() throws Exception {
575-
// Create 2 tasks on different workers to race.
576-
Task<String> task1 =
577-
new CrashlyticsWorker(TestOnlyExecutors.background())
578-
.submitTask(
579-
() -> {
580-
sleep(200);
581-
return Tasks.forCanceled();
582-
});
583-
Task<String> task2 =
584-
new CrashlyticsWorker(TestOnlyExecutors.background())
585-
.submitTask(
586-
() -> {
587-
sleep(400);
588-
return Tasks.forResult("I am slow but didn't cancel.");
589-
});
590-
591-
Task<String> task = crashlyticsWorker.race(task1, task2);
592-
String result = Tasks.await(task);
593-
594-
assertThat(result).isEqualTo("I am slow but didn't cancel.");
595-
}
596-
597-
@Test
598-
public void raceBothCancel() {
599-
// Create 2 tasks on different workers to race.
600-
Task<String> task1 =
601-
new CrashlyticsWorker(TestOnlyExecutors.background())
602-
.submitTask(
603-
() -> {
604-
sleep(200);
605-
return Tasks.forCanceled();
606-
});
607-
Task<String> task2 =
608-
new CrashlyticsWorker(TestOnlyExecutors.background())
609-
.submitTask(
610-
() -> {
611-
sleep(400);
612-
return Tasks.forCanceled();
613-
});
614-
615-
Task<String> task = crashlyticsWorker.race(task1, task2);
616-
617-
// Both cancelled, so cancel the race result.
618-
assertThrows(CancellationException.class, () -> Tasks.await(task));
619-
}
620-
621-
@Test
622-
public void raceTasksOnSameWorker() throws Exception {
623-
// Create 2 tasks on this worker to race.
624-
Task<String> task1 =
625-
crashlyticsWorker.submit(
626-
() -> {
627-
sleep(200);
628-
return "first";
629-
});
630-
Task<String> task2 =
631-
crashlyticsWorker.submit(
632-
() -> {
633-
sleep(300);
634-
return "second";
635-
});
529+
public void tasksRunOnCorrectThreads() throws Exception {
530+
ExecutorService executor = newNamedSingleThreadExecutor("workerThread");
531+
CrashlyticsWorker worker = new CrashlyticsWorker(executor);
636532

637-
Task<String> task = crashlyticsWorker.race(task1, task2);
638-
String result = Tasks.await(task);
533+
ExecutorService otherExecutor = newNamedSingleThreadExecutor("otherThread");
534+
CrashlyticsWorker otherWorker = new CrashlyticsWorker(otherExecutor);
639535

640-
// The first task is submitted to this worker first, so will always be first.
641-
assertThat(result).isEqualTo("first");
642-
}
643-
644-
@Test
645-
public void raceTaskOneOnSameWorkerAnotherNeverCompletes() throws Exception {
646-
// Create a task on this worker, and another that never completes, to race.
647-
Task<String> task1 = crashlyticsWorker.submit(() -> "first");
648-
Task<String> task2 = new TaskCompletionSource<String>().getTask();
536+
// Submit a Runnable.
537+
worker.submit(
538+
() -> {
539+
// The runnable blocks an underlying thread.
540+
assertThat(getThreadName()).isEqualTo("workerThread");
541+
});
649542

650-
Task<String> task = crashlyticsWorker.race(task1, task2);
651-
String result = Tasks.await(task);
543+
// Submit a Callable.
544+
worker.submit(
545+
() -> {
546+
// The callable blocks an underlying thread.
547+
assertThat(getThreadName()).isEqualTo("workerThread");
548+
return null;
549+
});
652550

653-
assertThat(result).isEqualTo("first");
654-
}
551+
// Submit a Callable<Task>.
552+
worker.submitTask(
553+
() -> {
554+
// The callable itself blocks an underlying thread.
555+
assertThat(getThreadName()).isEqualTo("workerThread");
556+
return otherWorker.submit(
557+
() -> {
558+
// The called task blocks an underlying thread in its own executor.
559+
assertThat(getThreadName()).isEqualTo("otherThread");
560+
});
561+
});
655562

656-
@Test
657-
public void raceTaskOneOnSameWorkerAnotherOtherThatCompletesFirst() throws Exception {
658-
// Add a decoy task to the worker to take up some time.
659-
crashlyticsWorker.submitTask(
563+
// Submit a Callable<Task> with a Continuation.
564+
worker.submitTask(
660565
() -> {
661-
sleep(200);
566+
// The callable itself blocks an underlying thread.
567+
assertThat(getThreadName()).isEqualTo("workerThread");
568+
return otherWorker.submitTask(
569+
() -> {
570+
// The called task blocks an underlying thread in its own executor.
571+
assertThat(getThreadName()).isEqualTo("otherThread");
572+
return Tasks.forResult(null);
573+
});
574+
},
575+
task -> {
576+
// The continuation blocks an underlying thread of the original worker.
577+
assertThat(getThreadName()).isEqualTo("workerThread");
662578
return Tasks.forResult(null);
663579
});
664580

665-
// Create a task on this worker, and another, to race.
666-
Task<String> task1 = crashlyticsWorker.submit(() -> "same worker");
667-
TaskCompletionSource<String> task2 = new TaskCompletionSource<>();
668-
task2.trySetResult("other");
669-
670-
Task<String> task = crashlyticsWorker.race(task1, task2.getTask());
671-
String result = Tasks.await(task);
672-
673-
// The other tasks completes first because the first task is queued up later on the worker.
674-
assertThat(result).isEqualTo("other");
675-
}
676-
677-
private static void sleep(long millis) {
678-
try {
679-
Thread.sleep(millis);
680-
} catch (InterruptedException ex) {
681-
Thread.currentThread().interrupt();
682-
}
581+
// Await on the worker to force all the tasks to run their assertions.
582+
worker.await();
683583
}
684584
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.crashlytics.internal.concurrency;
18+
19+
import com.google.android.gms.tasks.CancellationTokenSource;
20+
import com.google.android.gms.tasks.Continuation;
21+
import com.google.android.gms.tasks.Task;
22+
import com.google.android.gms.tasks.TaskCompletionSource;
23+
import com.google.android.gms.tasks.Tasks;
24+
import java.util.concurrent.Executor;
25+
import java.util.concurrent.atomic.AtomicBoolean;
26+
27+
/**
28+
* Crashlytics specific utilities for dealing with Tasks.
29+
*
30+
* @hide
31+
*/
32+
public final class CrashlyticsTasks {
33+
/** An Executor that runs on the calling thread. */
34+
private static final Executor DIRECT = Runnable::run;
35+
36+
/**
37+
* Returns a task that is resolved when either of the given tasks is resolved.
38+
*
39+
* <p>If both tasks are cancelled, the returned task will be cancelled.
40+
*/
41+
public static <T> Task<T> race(Task<T> task1, Task<T> task2) {
42+
CancellationTokenSource cancellation = new CancellationTokenSource();
43+
TaskCompletionSource<T> result = new TaskCompletionSource<>(cancellation.getToken());
44+
45+
AtomicBoolean otherTaskCancelled = new AtomicBoolean(false);
46+
47+
Continuation<T, Task<Void>> continuation =
48+
task -> {
49+
if (task.isSuccessful()) {
50+
// Task is complete and successful.
51+
result.trySetResult(task.getResult());
52+
} else if (task.getException() != null) {
53+
// Task is complete but unsuccessful.
54+
result.trySetException(task.getException());
55+
} else if (otherTaskCancelled.getAndSet(true)) {
56+
// Both tasks are cancelled.
57+
cancellation.cancel();
58+
}
59+
return Tasks.forResult(null);
60+
};
61+
62+
task1.continueWithTask(DIRECT, continuation);
63+
task2.continueWithTask(DIRECT, continuation);
64+
65+
return result.getTask();
66+
}
67+
68+
private CrashlyticsTasks() {}
69+
}

‎firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/concurrency/CrashlyticsWorker.java

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,18 @@
1717
package com.google.firebase.crashlytics.internal.concurrency;
1818

1919
import androidx.annotation.VisibleForTesting;
20-
import com.google.android.gms.tasks.CancellationTokenSource;
2120
import com.google.android.gms.tasks.Continuation;
2221
import com.google.android.gms.tasks.Task;
23-
import com.google.android.gms.tasks.TaskCompletionSource;
2422
import com.google.android.gms.tasks.Tasks;
2523
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2624
import java.util.concurrent.Callable;
2725
import java.util.concurrent.ExecutionException;
2826
import java.util.concurrent.ExecutorService;
2927
import java.util.concurrent.TimeUnit;
3028
import java.util.concurrent.TimeoutException;
31-
import java.util.concurrent.atomic.AtomicBoolean;
3229

3330
/**
34-
* Helper for executing tasks sequentially on the given executor service.
31+
* Worker for executing tasks sequentially on the given executor service.
3532
*
3633
* <p>Work on the queue may block, or it may return a Task, such that the underlying thread may be
3734
* re-used while the worker queue is still blocked.
@@ -154,35 +151,6 @@ public <T, R> Task<R> submitTask(
154151
}
155152
}
156153

157-
/**
158-
* Returns a task that is resolved when either of the given tasks is resolved.
159-
*
160-
* <p>When both tasks are cancelled, the returned task will be cancelled.
161-
*/
162-
public <T> Task<T> race(Task<T> task1, Task<T> task2) {
163-
CancellationTokenSource cancellation = new CancellationTokenSource();
164-
TaskCompletionSource<T> result = new TaskCompletionSource<>(cancellation.getToken());
165-
166-
AtomicBoolean otherTaskCancelled = new AtomicBoolean(false);
167-
168-
Continuation<T, Task<Void>> continuation =
169-
task -> {
170-
if (task.isSuccessful()) {
171-
result.trySetResult(task.getResult());
172-
} else if (task.getException() != null) {
173-
result.trySetException(task.getException());
174-
} else if (otherTaskCancelled.getAndSet(true)) {
175-
cancellation.cancel();
176-
}
177-
return Tasks.forResult(null);
178-
};
179-
180-
task1.continueWithTask(executor, continuation);
181-
task2.continueWithTask(executor, continuation);
182-
183-
return result.getTask();
184-
}
185-
186154
/**
187155
* Blocks until all current pending tasks have completed, up to 30 seconds. Useful for testing.
188156
*

0 commit comments

Comments
 (0)
Please sign in to comment.