Conversation
A browser sends the mutations of a state over a websocket that has the state in its path, which is what routes it to the server that is authoritative for the state. That takes a websocket for every state that is being mutated, and browsers limit how many websockets can be open, to around 200 (255 in Chrome). This adds a websocket without a state in its path, that can be used for the mutations of any state: every `MutateRequest` says what state it is a mutation of, and so does every `MutateResponse`. It is routed to any server, which is fine because performing a mutation that was sent over a websocket has always meant calling the server that is authoritative for the state. The mutations of a state are performed, and responded to, in the order that they were sent, just like when they have a websocket of their own. The mutations of different states are performed concurrently, so that a mutation only ever waits for mutations of the same state. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WceTY5nXYmn6txE4haF8tw
Rather than a websocket for every state whose mutators are bound there is now one for every backend, that is opened once the first mutator is bound and closed once no state is used anymore. A state can not tell the difference: what it sends its mutations over looks like a websocket of its own, and because the backend responds to the mutations of a state in order it can still tell what mutation a response is for by counting. A backend from before this responds without saying what state its response is for, at which point we go back to a websocket for every state. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WceTY5nXYmn6txE4haF8tw
This branch has not been deployed
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.
This is on top of #202, and only the last two commits are new. It should be pointed at
mainonce #202 is merged.Problem
A browser sends the mutations of a state over a websocket that has the state in its path. With #202 that is a websocket for every state whose mutators are bound, rather than for every state, but browsers limit how many websockets can be open, to around 200 (255 in Chrome), so a page that mutates a lot of states can still run out of them. That is the case with TLS as well, see #202.
The first mutation of every state also has to wait for a websocket to be opened, which without RFC 8441 is a connection of its own.
Fix
Send the mutations of all states of a backend over one websocket.
The first commit adds that websocket to the backend, at
/__/reboot/websocket/mutate. EveryMutateRequestsays what state it is a mutation of, and so does everyMutateResponse:Without a state in its path the websocket is routed to any server, by the routing filter that we already have. That is fine because performing a mutation that was sent over a websocket has always meant calling the server that is authoritative for the state, see
react_mutate(). What is new is that the server being called is now often a different one.The second commit makes the generated React code use it. A state can not tell the difference: what it sends its mutations over looks like a websocket of its own, which is why the generated code barely changes. Because the backend responds to the mutations of a state in order, a state can still tell what mutation a response is for by counting.
The websocket is opened once the first mutator is bound, or called, and closed once no state is used anymore.
Compatibility
Trade-offs
Size
That is more than the 200 lines that I estimated, and it removes less, because the code for a websocket for a single state has to stay for older backends.
Testing
//tests/reboot/aio:react_websocket_mutate_test_pyuses the websocket through Envoy the way a browser does, against a backend with 4 servers, for 8 states, so that some of them are on a different server than the websocket. It also covers a state whose ID is percent-encoded and mutations that fail. A second test case uses mutations that take as long as the test wants them to, to show which mutations wait for which, and that they are cancelled when the websocket is closed.//tests/reboot/react/test_mutator_websocket:testnow has 12 cases, including that responses for different states can arrive in any order, that the websocket is opened again if it gets closed, and going back to a websocket for every state.//tests/reboot/react/..., including the Selenium tests with and without TLS,//tests/reboot/routing/...,//tests/reboot/routing_filter/...,//tests/reboot/aio/internals:health_servicer_tests_pyand//tests/reboot:greeter_generation_react_tests_pypass.test_mutations,test_idempotent_writer,test_websockets_connectionandtest_writer_abort_with_retrying_reactive_readerwith that disabled, and they pass.I have not tested this on a cluster, only against a local Envoy. What I would check there is that a websocket without a state in its path makes it through the Istio gateway to a pod, and a mutation of a state that is on a different pod.
🤖 Generated with Claude Code
https://claude.ai/code/session_01WceTY5nXYmn6txE4haF8tw