Repository navigation
tests: test/server starters keep their listener from the pick to the bind (Refs #1104) - #1139
Merged
Merged
Conversation
…t (Refs #1104) The seven starters that pick a port, close it and call Start (startServerConfig761, startServerConfig421, bodyServer889, start889, startServer870, startGlobalChainServer836, startServer964) pick through listenForStart1104 (behaviour unchanged), and TestStartersForcedOntoOnePortEndOnPortsOfTheirOwn1104 starts two of each at once on the same port: both end on it in 96 of 105 subtests (-race -count=5), one of the two servers failing to bind it behind the other's /ping.
startServerConfig761, the starter most of the package's tests use, goes through startPickedPing1104: the picked listener is handed to the server, so the port is never free between the pick and the bind and /ping can only be answered by the server that owns it. The ring-ENOMEM retry (and a taken port) is startRetrying1104's.
…their listener (Refs #1104) The two starters whose setup (Server.Use) must come before /ping go through startPickedPing1104.
…er964 keep their listener (Refs #1104) The remaining four starters that picked a port, closed it and called Start go through startPickedPing1104.
|
Caution Review failedThe pull request is closed.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
… test starts its pair as subtests (Refs #1104) epoll, io_uring and adaptive close the supplied listener inside Listen and rebind with SO_REUSEPORT, so on them StartWithListener shortens the window and does not close it; two servers can still end on one port, with no bind error. Say so in startPicked1104's comment and in the forced-port test, with the measured numbers. The test ran its pair of starters on bare goroutines, whose t.Fatal ends only the goroutine. They are parallel subtests now, held up until both are started, and a start that fails is reported by its own subtest.
…ner (Refs #1104) Both picked a port, closed it and had StartWithContext bind it, and waited for /ping, which the other server of a colliding pair answers. They hand the listener to StartWithListenerAndContext now (startPickedCtx1104), which shares StartWithContext's shutdown (listenUntilCancelled). The forced-port test covers both.
It picked a port, closed it and had StartWithContext bind it on std, then waited for /ping, which a colliding test's server would answer. It hands the listener to StartWithListenerAndContext, which std serves on directly.
…1104) It picked a port, closed it and had StartWithContext bind it, then waited for /ping. It hands the listener to StartWithListenerAndContext: adaptive gives it to the epoll fallback when the io_uring start engine is refused before Listen. On a native engine this shortens the window; it does not close it.
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.
Flakes lane, package G: the eleven
test/serverstarters that still picked a port, closed it and calledStart(). Refs #1104Method: commit 1 forces each starter onto one picked port and fails; commits 2 to 4 move seven starters onto
startPicked1104(#1135), commit 5 corrects what the PR claimed about native engines, commits 6, 7 and 8 move four more. One commit per family so each can be dropped, with these dependencies: commit 2 (family 1) definesstartPickedPing1104andping1104, which commits 3 and 4 use, so drop 3 or 4 alone, or 2 together with them; and commit 6 addsstartPickedCtx1104and its two rows to the commit-1 test and needs commit 5 for thetimeimport of that test file (drop 5 and 6 together, or neither). Tests only.What the hand-off guarantees: the std engine only.
startPicked1104hands the picked listener to the server. Only std serves on that listener, so only on std is the port never free between the pick and the bind. epoll, io_uring and adaptive close the supplied listener insideListenand rebind its address withSO_REUSEPORT; there the hand-off shortens the window to the close and the rebind, and two native servers that both win the port both bind it, with no error, and share it (so the address-in-use retry never fires). I measured it with the forced-port test on epoll: two servers ended on one port in 2 of 630 subtests in one run and in some of another run of 630 (not counted), and in none of three other runs (105, 630, 630); io_uring: 1 of 630 in two of three runs of 630; the review of this PR saw epoll 3 of 105, io_uring 2 of 210, adaptive 0 of 210. The remaining window is a few microseconds insideListeninstead of the milliseconds of a server construction, and I did not close it (an in-process lock around pick-to-ready would have to be taken by every picker of the package,net.Listen(":0")calls included, which is not a few lines). The forced-port test is therefore a std test, and its comment andstartPicked1104's say so.The hole
#1135 closed it for the SSE 855, h2upgrade-matrix and start-shutdown starters: a starter that picks a free port, closes it and has
Startbind it can be given a port another test of the process picked at the same moment; one server binds it, the other'sStartfails withaddress already in use, and the loser's readiness probe (GET /ping) is answered by the WINNER, so the starter reports success and the failure surfaces only later (the CI log ofTestStalledSSEEndsWithShutdown855/std/h2c/budgethas the two servers starting on127.0.0.1:387970.5 ms apart). Eleven more starters had the same shape and the same probe; none has failed on it so far.Starters moved
startServerConfig761(used by most of the package's tests)large_response_linux_test.gostartServerConfig421auto_head_options_engines_421_linux_test.gostartGlobalChainServer836h2_stream_context_836_linux_test.gobodyServer889bodylimit_default_889_linux_test.gostart889chunked_promotion_889_linux_test.gostartServerSrv870(behindstartServer870)detect_split_first_segment_870_linux_test.gostartServer964h2upgrade_disabled_964_linux_test.gostartServer760(behindstartDrainServer806; the 760 and 806 drain tests,adaptive_iouring_busy_poll_811)shutdown_send_drain_linux_test.gostartH2PoolServer759(the h2 pool and 884 gate tests)shutdown_h2_pool_linux_test.gorunStdCancelBudgetCasestd_cancel_shutdown_timeout_test.goTestServerAdaptiveStartsOnEpollWhenIOUringRefused886iouring_async_cancel_refusal_886_linux_test.goFamilies 1 to 3 go through
startPickedPing1104(new, a thin wrapper ofstartPicked1104for the/ping-readiness starters, with the Shutdown-and-wait cleanup they all had) andping1104(registers/ping; each starter keeps its own route order,Usebefore routes where it needs it). The listener is handed to the server (StartWithListener; see the std-only note above). The ring-ENOMEM retry isstartRetrying1104's; it also retries a taken port, ten times, which the old loops of these starters did not (they retried a ring ENOMEM only).Family 4 stops its server by cancelling the context it starts under, so it goes through
startPickedCtx1104, which callsStartWithListenerAndContext: that andStartWithContextboth end inlistenUntilCancelled(server.go), the same shutdown path.runStdCancelBudgetCase(std) keeps its listener and callsStartWithListenerAndContextdirectly.Found in the review of this PR (picking starters with a
/pingprobe); the 886 test is moved, the drain-order starter is not:shutdown_drain_order_linux_test.go,runDrainOrderCase): its three modes are the entry points under test (StartWithContextwith a direct Shutdown,StartWithContextwith a cancel,StartWithListener), and its per-try goroutine records the order Start returns in; moving the twoStartWithContextmodes onto a listener entry point would change what they test. TheStartWithListenermode already keeps its listener.TestServerAdaptiveStartsOnEpollWhenIOUringRefused886: in commit 8 (adaptive hands the supplied listener to the epoll fallback when the io_uring start engine is refused beforeListen, so the test still tests the fallback;-race -count=10: 10 of 10 pass, no skip). Like every native arm it stays exposed to the window insideListen(see above).server_sync_goexit_848's twoStart()sites, below.These are the picking starters I found by grep and by the review; a starter that picks in a way neither finds may still exist.
Not moved:
server_sync_goexit_848's twoStart()sites (the brief listed:491). They are deliberate:setup-failurestarts a server on a port a plain listener HOLDS (the held listener is the premise, so there is no pick-close-bind race), andlost-listenerpicks a port, closes it and takes it back with a plain listener while the server is paused, on a fixedAddr. Neither probes/ping. Converting them would remove what the tests are about.Evidence
TestStartersForcedOntoOnePortEndOnPortsOfTheirOwn1104(commit 1; commit 6 adds the rows ofstartServer760andstartH2PoolServer759;runStdCancelBudgetCaseis a whole test of its own and has none) starts two of each starter at once on the same picked port (the hooklistenForStart1104: the first picker is given the port, a picker that finds it held gets a fresh one), three rounds per starter, std engine. The two starts run as parallel subtests (the starters callt.Fatal, which must end only their own subtest), each held up until the other is up.golang:1.27.3,--cpus 2, 8 MiB memlock,-race -count=5:Start)Starterror arrives before its probe succeeds)Family 4 has no failing-first. The
startServer760andstartH2PoolServer759rows pass on the old code too: those starters picked withnet.Listendirectly and never consulted the forced-port hook (commit 1 wired the hook into the seven starters only), so the two rows are green before and after. For these two the PR is the conversion alone, covered by the existing tests of those files (below); the rows run throughlistenForStart1104now, but a later revert of the starters to a plain pick would leave them green, so they do not guard it.runStdCancelBudgetCasehas no row at all.The pair needs two parallel test slots (
-parallelof at least 2): with-parallel 1each round waits 30 s for the other subtest and then fails with a clear message. No CI step uses-parallel.Touched files,
-race -count=2, same container shape (8 MiB memlock): all 16 top-level tests ofshutdown_send_drain,shutdown_h2_pool,std_cancel_shutdown_timeout,adaptive_iouring_busy_poll_811,shutdown_iouring_gate_884and the forced-port test pass, no skip, 210 s.Whole package once,
go test -race -count=1 -timeout 50m ./test/server, same container shape:okin 691 s; the only skips areTestZonedPeerAddressIsStdsOnNativeEngines890andTestZonedListenAddressServesOnEveryEngine885(no link-local address in the container; both are in the CI skip allow-list and untouched). No test was added that skips. That whole-package run was on the seven-starter head (commit 4); this round's commits were run as listed above.go vetis clean;golangci-lint --new-from-rev=origin/mainis clean under the CI pin (v2.14); a local v2.15 also flagscase <-done:/case <-srv.startDone:on a channel of error (errcheck) at the cleanups, the patternmainalready has inh2upgrade_matrix_889.Differences to know about:
chunked_promotion's starter retried a ring ENOMEM withstrings.Contains(..., "cannot allocate memory"); it now usesisRingENOMEM, which also takesio_uring not available on this system(the probe's own ring met the same ENOMEM), so on a host with no io_uring its io_uring arms wait the 30 s window before failing instead of failing at once (the other starters already behaved that way).Release note
None (tests only).
Findings, not fixed here
waitEngineBusy1104(tests: test/server start retry takes a fresh port; the shutdown gate test waits for the engine (Refs #1104) #1135) stays: it is still needed for conns in the kernel accept queue when the listener is closed at cancel (the kernel resets them), whatever the gate fix does.Listenand rebind withSO_REUSEPORT, with no owner check beyond the kernel's same-UID rule, so two native servers can end on one port without an error. A fix would be in the engines or in a per-server token in/ping(asstart_shutdown_returnhas); not done here.test/integrationhas its own starters (tests: test/integration starters retry the io_uring ring-setup ENOMEM (Refs #1104) #1132) with a different shape.