firebase / firebase/firebase-android-sdk
DuplicateTaskCompletionException in Firestore ThrottledForwardingExecutor causes fatal crash
- Dominant language
- Java
- Stars
- 2.6k
- Forks
- 710
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 31
Description
### Description
We're seeing a fatal `DuplicateTaskCompletionException` crash originating from Firestore's internal `ThrottledForwardingExecutor`. The crash is a race condition where a `TaskCompletionSource` is resolved twice — entirely within SDK code, with no app code in the stack trace.
### Environment
- **Firebase BOM**: 34.11.0
- **firebase-firestore**: 26.1.2
- **play-services-tasks**: 18.4.0
- **Android versions affected**: Multiple (seen on API 30+)
- **Reproducibility**: Intermittent — likely a threading race condition
### Stack trace
```
Fatal Exception: com.google.android.gms.tasks.DuplicateTaskCompletionException: Complete with: result ip$a@22e602d
at com.google.android.gms.tasks.zzw.zzg(com.google.android.gms:play-services-tasks@@18.4.0:1)
at com.google.android.gms.tasks.zzw.zzc(com.google.android.gms:play-services-tasks@@18.4.0:2)
at com.google.android.gms.tasks.zzx.run(com.google.android.gms:play-services-tasks@@18.4.0:3)
at com.google.firebase.firestore.util.ThrottledForwardingExecutor.lambda$execute$0(ThrottledForwardingExecutor.java:54)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
```
### Analysis
The entire stack trace is inside SDK code — no app-level frames. `ThrottledForwardingExecutor` uses a semaphore to cap concurrency and falls back to running tasks synchronously on the calling thread when all slots are taken. This appears to create a window where two threads attempt to complete the same `Task`.
### Suggested fix
Guard `TaskCompletionSource.setResult()` / `setException()` calls with `trySetResult()` / `trySetException()` in the code paths that run through `ThrottledForwardingExecutor`, or ensure the completion callback can only execute once.
### Steps to reproduce
We have not been able to reproduce this on demand. It appears to be a timing-dependent race condition, possibly triggered by network transitions or app backgrounding during in-flight Firestore operations.
Contributor guide
Assessment
This issue has not been assessed yet.