React: add flow control to reactive readers - #145
Draft
rjhuijsman wants to merge 4 commits into
Draft
Conversation
Keeps a client of a reactive read on the latest state even when the state changes faster than the client can consume the updates. Before this change the backend produced a response per state change and pushed it at the client regardless of whether the client had consumed the previous one, so responses queued up in the gRPC stream or in the browser's websocket buffer: a client that couldn't keep up worked its way through every state it had missed, and fell arbitrarily far behind while still receiving a steady stream of (old) responses. `React.Query` now stamps every response with a `query_response_id` and produces no further response until the client acknowledges that ID. Acknowledgements travel back over the websocket that carries the responses when there is one -- a `QueryRequest` with `acknowledge_query_response_id` set, which costs no extra round trip -- and over the new unary `AcknowledgeQueryResponse` RPC otherwise. Between an acknowledgement and the next response the backend catches up to the _latest_ state, so a slow client skips the states it missed instead of replaying them. - A client only acknowledges a response that carries an ID, so a frontend built from this version keeps working against an older backend, which sends none. - Transitive reactive reads acknowledge only once the response they hold has been used, so a reader that reads through another reader also gets the latest state rather than the next one in line. BREAKING CHANGE: a frontend that doesn't acknowledge responses receives one reactive response and then never updates again, so frontends must be redeployed alongside (or before) this backend change. TESTED: `//tests/reboot:reactivity_test_py` gains `test_skip_to_latest` and `test_transitive_skip_to_latest`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FMgyLgRtZXNAerHpSguK9k
Stops a mutation from staying pending forever now that the backend skips states. Before this commit the generated React client compared each query response only against the oldest of the idempotency keys it was expecting. That was enough while every state change produced its own response, but a response now reports the aggregated idempotency keys of every state the backend skipped on its way to the state that response reflects, so a single response can be the only word a client ever gets about several of its mutations -- and the ones it didn't compare stayed `pending` forever. Observe every expected mutation whose idempotency key the response reports, rather than only the oldest one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FMgyLgRtZXNAerHpSguK9k
Covers the failure that got the first attempt at reactive read flow control reverted: an update that arrives while the browser is too busy to consume it must not be lost, or the page shows old data forever. The app under test blocks the browser's main thread for a second on every response it renders, so each round of state changes this test makes lands on a client that cannot process it and has not yet asked for it. The browser must still end each round showing the last state written in that round. The test runs against a local Envoy both with TLS, where responses arrive over an HTTP stream and acknowledgements are requests of their own, and without, where responses and acknowledgements share one websocket. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FMgyLgRtZXNAerHpSguK9k
Current Aviator status
This pull request is currently open (not queued). How to mergeTo merge this PR, comment
See the real-time status of this PR on the
Aviator webapp.
Use the Aviator Chrome Extension
to see the status of your PR within GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Keeps clients of a reactive read on the latest state even when the state changes faster than they can consume it. Before this change the backend produced a response per state change and pushed it at the client regardless of whether the client had consumed the previous one, so responses queued up in the gRPC stream or in the browser's websocket buffer; a client that couldn't keep up worked its way through every state it had missed and fell arbitrarily far behind while still receiving a steady stream of (old) responses.
React.Querynow stamps every response with aquery_response_idand produces no further response until the client acknowledges that ID. Acknowledgements travel back over the websocket that carries the responses when there is one — aQueryRequestwithacknowledge_query_response_idset, which costs no extra round trip — and over the new unaryAcknowledgeQueryResponseRPC otherwise. Between an acknowledgement and the next response the backend catches up to the latest state, so a slow client skips the states it missed instead of replaying them.BREAKING CHANGE: a frontend that doesn't acknowledge responses receives one reactive response and then never updates again, so frontends must be redeployed alongside (or before) this backend change. A frontend from this version does keep working against an older backend, since it only acknowledges responses that carry an ID.