Skip to content

fix(firestore): record pending target requests at stream-start time#9762

Open
ShaneBreazeale wants to merge 4 commits intofirebase:mainfrom
ShaneBreazeale:fix/firestore-pending-target-bookkeeping
Open

fix(firestore): record pending target requests at stream-start time#9762
ShaneBreazeale wants to merge 4 commits intofirebase:mainfrom
ShaneBreazeale:fix/firestore-pending-target-bookkeeping

Conversation

@ShaneBreazeale
Copy link
Copy Markdown

Move the pendingResponses bookkeeping for restored targets from
onWatchStreamOpen into startWatchStream, closing the timing window
where a rapid unlisten/listen cycle (e.g. React 19 StrictMode
double-invoking effects) could fire before the stream opens and
leave the counter in an inconsistent state.

Adds 4 spec tests covering:

  • unlisten/listen after stream reconnect
  • multiple unlisten/listen cycles after reconnect
  • unlisten during stream startup before open
  • multi-target reconnect with one target cycling

Fixes #9729

…arget responses

React 19 StrictMode double-invokes effects, causing listen/unlisten/re-listen
sequences that can result in more server acks than pending requests. The
previous hardAssert permanently poisoned the Firestore SDK instance. Now we
clamp pendingResponses to 0 and log a warning instead.

Fixes firebase#9729
@ShaneBreazeale ShaneBreazeale requested review from a team as code owners March 25, 2026 22:59
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 25, 2026

🦋 Changeset detected

Latest commit: e63809b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@firebase/firestore Patch
firebase Patch
@firebase/firestore-compat Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@google-cla
Copy link
Copy Markdown

google-cla bot commented Mar 25, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical race condition in Firestore's watch stream management, particularly affecting environments like React 19 StrictMode. By shifting the timing of pending target request bookkeeping, it prevents inconsistencies that could arise from rapid listen/unlisten cycles during stream startup or reconnection. This ensures the SDK remains stable and functional even under aggressive component lifecycle behaviors, significantly improving resilience and preventing application crashes.

Highlights

  • Improved Watch Stream Bookkeeping: Introduced a new mechanism (pendingTargetsForWatchOpen) to track pending target requests at the start of a watch stream, preventing race conditions during rapid listen/unlisten cycles.
  • Refactored Pending Request Recording: Moved the recording of pending target requests from onWatchStreamOpen to startWatchStream to ensure accurate state management from the earliest possible point in the stream's lifecycle.
  • Graceful Error Handling: Modified recordTargetResponse to log a warning and clamp pendingResponses to zero instead of crashing the SDK when an unexpected number of target responses are received, improving resilience in environments like React StrictMode.
  • Enhanced Test Coverage: Added comprehensive unit and spec tests to validate the fix across various scenarios involving stream reconnection, rapid listen/unlisten cycles, and multiple targets, ensuring the stability and correctness of the watch stream logic.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

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 resolves an issue (#9729) where the Firestore SDK could crash in environments like React StrictMode due to an incorrect count of pending watch stream responses. The changes introduce a mechanism to pre-record pending target requests during stream startup and modify how target responses are handled, preventing negative pendingResponses from causing a fatal assertion. A new sendWatchMessage function is added, and onWatchStreamOpen is updated to manage target states more robustly. Comprehensive unit and integration tests have been added to validate these changes. There is an opportunity to refactor sendWatchMessage and sendWatchRequest to extract common logic and improve maintainability.

Comment thread packages/firestore/src/remote/remote_store.ts
Move the pendingResponses bookkeeping for restored targets from
onWatchStreamOpen into startWatchStream, closing the timing window
where a rapid unlisten/listen cycle (e.g. React 19 StrictMode
double-invoking effects) could fire before the stream opens and
leave the counter in an inconsistent state.

Adds 4 spec tests covering:
- unlisten/listen after stream reconnect
- multiple unlisten/listen cycles after reconnect
- unlisten during stream startup before open
- multi-target reconnect with one target cycling

Fixes firebase#9729
@ShaneBreazeale ShaneBreazeale force-pushed the fix/firestore-pending-target-bookkeeping branch from af4b7a4 to 46eb63e Compare March 25, 2026 23:05
@ShaneBreazeale ShaneBreazeale force-pushed the fix/firestore-pending-target-bookkeeping branch from d19066e to e63809b Compare March 27, 2026 09:18
@MarkDuckworth
Copy link
Copy Markdown
Contributor

Hi @ShaneBreazeale,

Thank you for putting this PR together and for proposing a solution to the pendingResponses < 0 assertion. I appreciate the effort to address this issue.

I spent some time reviewing the fix, which suggests that rapid listen and unlisten calls before onWatchStreamOpen fires are the root cause of the negative pendingResponses count. However, in the main branch, recordPendingTargetRequest is only called inside sendWatchRequest (and sendUnwatchRequest), which are only invoked after the watch stream is successfully opened.

If a target is listened to and then unlistened to before the stream opens, it is removed from the internal listenTargets map. No network requests are sent, and no pendingResponses increments/decrements occur. When the stream eventually opens, it only processes the targets remaining in the map at that moment. Therefore, this specific sequence cannot result in a negative counter in the current implementation.

If I'm misunderstanding your fix or your assessment of the problem, please let me know.

That said, we were able to get a reproduction of the ca9 assertion caused by React strict mode's double render. Fix is in progress #9842

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.

hardAssert throws on transient state, permanently poisons Firestore SDK instance (React 19 StrictMode)

2 participants