firebase / firebase/firebase-android-sdk

DuplicateTaskCompletionException in Firestore ThrottledForwardingExecutor causes fatal crash

Open
#8,021 0 comments 0 reactions 1 assignee Claimed by @wu-hui View on GitHub
api: firestore
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.