fix(firestore, android): catch RejectedExecutionException in sendOnSnapshotEvent#8974
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
There was a problem hiding this comment.
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.
This comment was marked as outdated.
This comment was marked as outdated.
|
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 |
…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.
8f51379 to
cfc0241
Compare
Summary
Fixes [🐛] Firestore Android: RejectedExecutionException crash in sendOnSnapshotEvent during module invalidation #8973
Wrap
Tasks.call()insendOnSnapshotEventwith a try-catch forRejectedExecutionExceptionin bothReactNativeFirebaseFirestoreCollectionModuleandReactNativeFirebaseFirestoreDocumentModulePrevents a fatal crash when a Firestore snapshot callback (already enqueued on the main looper via
AsyncEventListener) fires after module invalidation has terminated the executorContext
The existing
invalidate()correctly removes listeners before callingsuper.invalidate(). ButAsyncEventListenerdispatches viaHandler.post()— if a snapshot event is already in the Handler queue before invalidation starts, it will still runsendOnSnapshotEventafter 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
Note
Low Risk
Low risk: adds defensive
RejectedExecutionExceptionhandling 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 transactiongetDocument, and Realtime Databaseonce/event dispatch withtry/catch (RejectedExecutionException), rejecting the pending promise (forget/once) or safely dropping late snapshot/events during teardown. Also reordersDatabaseQueryModule.invalidate()to clean up listeners before callingsuper.invalidate().Reviewed by Cursor Bugbot for commit cfc0241. Bugbot is set up for automated code reviews on this repo. Configure here.