fix(gateway): switching the listen address on the same port takes effect live - #188
Merged
Merged
Conversation
…ect live Changing `bind` between `all` and one interface (or back) failed on Linux: the new listener was bound before the old one gave up the port, and Linux does not let 0.0.0.0:8788 and 127.0.0.1:8788 listen side by side. The bind failed with "already in use by another program", the old listener stayed, and only a restart applied the setting. The pre-save check in the control plane said the same, so the app could not even save it. When a new address fails with EADDRINUSE on the port of a listener that is going away anyway, that listener now gives up the port first: axum stops accepting and drops the socket (a wrapper around the listener reports that moment), then the new addresses are bound. Requests in flight are unaffected, since their connections outlive the listening socket; new connections see a gap of milliseconds. If the new address still cannot be bound, the old one is bound back and the failure is reported as before. Every other case keeps "bind the new one first". The pre-save check no longer reports our own listener as another program: an EADDRINUSE on a port we listen on, overlapping one of our addresses, is left to the switch, which reports a real failure. Tests switch all → 127.0.0.1 → all → 127.0.0.1 on one port, keep a slow request alive across the handover, take the old address back when the new one is really taken, and check the pre-save answer. Three of them fail on Linux without this change (run in Docker); macOS allows the overlap and never showed the problem. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
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.
Changing
listen.gateway.bindbetweenalland one interface (or back) failed on Linux: the new listener was bound before the old one released the port, and Linux does not let0.0.0.0:8788and127.0.0.1:8788listen side by side. The result wasgw.listen.port_taken("already in use by another program"), the old listener kept serving, and only a restart applied the setting. The control plane's pre-save check (tw_gateway::listen::check) gave the same answer, so the app could not save the setting at all. Found while testing the server deployment in #183.Change
EADDRINUSEon the port of one of our listeners that is going away anyway, that listener gives up the port first: stop accepting, wait until axum has actually dropped the socket (a smallListenerwrapper,Tracked, reports that moment), then bind the new addresses. In-flight requests are unaffected (their connections outlive the listening socket); new connections see a gap of milliseconds.ListenChanged { error }status path.check()no longer reports our own listener as another program: anEADDRINUSEon an address overlapping one we listen on is left to the switch, which reports real failures.ListenerExt::tap_io, the one custom-listener path axum givesConnectInfo<SocketAddr>for.Tests (
tw-gateway/tests/hotreload.rs)[::1]:pheld by another socket while switching from0.0.0.0:p): the old one is taken back andgw.listen.port_takenreported.On Linux (Docker, rust:1-bookworm) three of these fail on
mainand all pass with the change (ran 3×). macOS allows the overlap, so it never showed there.The remote control listener in #187 already handles this case (it releases the old listener, awaiting its accept task, and binds back on failure), so it needs no change.
🤖 Generated with Claude Code