Skip to content

fix(firestore, android): catch RejectedExecutionException in sendOnSnapshotEvent#8974

Merged
mikehardy merged 3 commits into
invertase:mainfrom
christian-apollo:fix/firestore-rejected-execution-exception
May 5, 2026
Merged

fix(firestore, android): catch RejectedExecutionException in sendOnSnapshotEvent#8974
mikehardy merged 3 commits into
invertase:mainfrom
christian-apollo:fix/firestore-rejected-execution-exception

Conversation

@christian-apollo
Copy link
Copy Markdown
Contributor

@christian-apollo christian-apollo commented Apr 13, 2026

Summary

Context

The existing invalidate() correctly removes listeners before calling super.invalidate(). But AsyncEventListener dispatches via Handler.post() — if a snapshot event is already in the Handler queue before invalidation starts, it will still run sendOnSnapshotEvent after the executor is shut down. This is a standard executor-shutdown race that reordering alone cannot prevent.

Dropping the event is safe because the module is tearing down and no JS listener remains to consume it.

Test plan

  • Verified the crash no longer reproduces by running with active Firestore collection listeners and forcing rapid module invalidation cycles
  • Confirmed normal snapshot delivery is unaffected — the catch only activates when the executor is already terminated

Note

Low Risk
Low risk: adds defensive RejectedExecutionException handling around async executor usage during module teardown to prevent crashes; behavior only changes in teardown/race scenarios.

Overview
Prevents Android crashes when Firestore/Realtime Database callbacks arrive after module invalidation has shut down the executor.

Wraps several Tasks.call(...) usages in Firestore collection/document snapshot delivery, Firestore transaction getDocument, and Realtime Database once/event dispatch with try/catch (RejectedExecutionException), rejecting the pending promise (for get/once) or safely dropping late snapshot/events during teardown. Also reorders DatabaseQueryModule.invalidate() to clean up listeners before calling super.invalidate().

Reviewed by Cursor Bugbot for commit cfc0241. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces error handling for RejectedExecutionException in the sendOnSnapshotEvent methods within both ReactNativeFirebaseFirestoreCollectionModule and ReactNativeFirebaseFirestoreDocumentModule. By wrapping the task execution in a try-catch block, the code now gracefully handles snapshots that arrive after the module's executor has been shut down during teardown. I have no feedback to provide as there were no review comments to evaluate.

@github-actions

This comment was marked as outdated.

@github-actions github-actions Bot added the Stale label Apr 27, 2026
@mikehardy mikehardy removed the Stale label May 4, 2026
@mikehardy
Copy link
Copy Markdown
Collaborator

Thanks for your patience! I like this PR so much (and the genre of issue it exposes) that I did a quick scan over the whole codebase for similar patterns and turned up some extra instances in firestore and rtdb packages

I'm going to add those as follow-on commits in your branch on this PR so the single PR contains one conclusive fix for the genre of issue you discovered, and shepherd it to merge

christian-apollo and others added 3 commits May 3, 2026 20:13
…apshotEvent

When the Firestore native module is invalidated, snapshot listener callbacks
that are already queued on the main thread's Handler can still fire after
super.invalidate() has shut down the executor. This causes a fatal
RejectedExecutionException crash.

The existing invalidate() ordering (remove listeners before super.invalidate())
reduces but does not eliminate this race, because AsyncEventListener dispatches
via Handler.post() — the callback may already be enqueued before invalidation
begins.

Wrapping Tasks.call() in sendOnSnapshotEvent with a try-catch for
RejectedExecutionException is safe because:
- The module is being torn down; no JS listener will process the event
- The snapshot data is stale/irrelevant at this point
- This matches the standard pattern for handling executor shutdown races

Applies to both CollectionModule and DocumentModule.
…ecutors

Call super.invalidate() only after clearing query listeners so Firebase
callbacks are less likely to run against a shut-down TaskExecutorService,
matching the Firestore module invalidation order.
Wrap Tasks.call paths that run after Firebase callbacks or during bridge
teardown: Realtime Database once listeners and streaming handleDatabaseEvent,
Firestore query get, and Firestore transaction get document. Matches the
Firestore snapshot listener handling when TaskExecutorService is shut down.
@mikehardy mikehardy force-pushed the fix/firestore-rejected-execution-exception branch from 8f51379 to cfc0241 Compare May 4, 2026 01:14
@mikehardy mikehardy requested a review from russellwheatley May 4, 2026 01:14
@mikehardy mikehardy added the Workflow: Needs Review Pending feedback or review from a maintainer. label May 4, 2026
@mikehardy mikehardy removed the Workflow: Needs Review Pending feedback or review from a maintainer. label May 5, 2026
@mikehardy mikehardy merged commit d70520d into invertase:main May 5, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🐛] Firestore Android: RejectedExecutionException crash in sendOnSnapshotEvent during module invalidation

3 participants