Repository navigation
iouring: the shutdown gate closes idle and ended HTTP/1 conns and answers requests in progress, ring-wait ceiling, shutdown trials (v1.6.0, Refs #884) - #1123
Merged
Conversation
…ete HTTP/1 conns, detached streams, requests in progress) and the ring-wait ceiling (v1.6.0, Refs #884)
…ds on requests in progress; a ring wait that fails for good stops the worker (v1.6.0, Refs #884)
…nd the kernel send queue (v1.6.0, Refs #884)
…nd a re-armed multishot recv (v1.6.0, Refs #884 item 16)
…ng (v1.6.0, Refs #884 item 80)
… worker exists (v1.6.0, Refs #884 item 72)
…le close (v1.6.0, Refs #884)
|
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❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This was referenced Oct 26, 2026
# Conflicts: # internal/engine/epoll/poolwait_gate_885.go
FumingPower3925
marked this pull request as ready for review
October 27, 2026 00:32
FumingPower3925
added a commit
that referenced
this pull request
Oct 27, 2026
…the merged tree (Refs #890 item 87) Main 8936e89 (with #1123 and #1127) run through the job's logic in docker at CELERIS_MAX_IOURING_TIER=base: the iouring package gives 577 top-level PASS and 13 SKIP lines, the new one being TestListenOnAZonedLinkLocalServes885 (the container has no fe80:: address); root and test/server give 118 PASS and 46 SKIP lines, all allowed. The zoned test fails instead of skipping under CELERIS_REQUIRE_ZONED_PEER=1, as in the unit job, so both steps set it and the list stays as it was. min-pass floors 540 and 105.
This was referenced Oct 27, 2026
This was referenced Oct 28, 2026
FumingPower3925
added a commit
that referenced
this pull request
Oct 28, 2026
…ion: close and ends its conn (v1.6.0, Refs #885 #884) Engine.Shutdown raises an engine-wide flag (a loop that sees Listen's context cancelled raises it too), which each HTTP/1 conn's H1State carries (H1State.Shutdown). The response head builders read it where the head is built, so a handler already running when Shutdown began (a sync handler on the loop's thread, an Async-route handler on its goroutine) and a request that arrives in the 250 ms before the gate are answered with one Connection: close, and handleH1Request ends the conn after it (errConnectionClose: the bytes read behind the request are dropped, as net/http does not serve a pipelined request behind a response that closes its conn). A handler's own Connection header on such a response is dropped for the one close. 1xx heads (101 is a handshake), detached streams, hijacked conns and HTTP/2 are not touched. The gate's CloseAfter marks (#1116, #1123, #1138) are unchanged: both feed the one decision. std (net/http) already closes after such a response; the wire tests assert it. Server.Shutdown's godoc says what each engine does.
FumingPower3925
added a commit
that referenced
this pull request
Oct 29, 2026
…ion: close and ends its conn (v1.6.0, Refs #885 #884) Engine.Shutdown raises an engine-wide flag (a loop that sees Listen's context cancelled raises it too), which each HTTP/1 conn's H1State carries (H1State.Shutdown). The response head builders read it where the head is built, so a handler already running when Shutdown began (a sync handler on the loop's thread, an Async-route handler on its goroutine) and a request that arrives in the 250 ms before the gate are answered with one Connection: close, and handleH1Request ends the conn after it (errConnectionClose: the bytes read behind the request are dropped, as net/http does not serve a pipelined request behind a response that closes its conn). A handler's own Connection header on such a response is dropped for the one close. 1xx heads (101 is a handshake), detached streams, hijacked conns and HTTP/2 are not touched. The gate's CloseAfter marks (#1116, #1123, #1138) are unchanged: both feed the one decision. std (net/http) already closes after such a response; the wire tests assert it. Server.Shutdown's godoc says what each engine does.
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.
Refs #884
io_uring shutdown gate: the twin of epoll's #1116 (
closeIdleH1,refusesRecv,CloseAfter), plus the ring-wait ceiling and the test-only items. Verify-first: commit7117555is red on main (assertions, not compile errors); the fix is5e6b94c.Numbering. Item numbers count every checkbox of #884 (ticked or not), the numbering the lane brief used. #1099/#1056 count unchecked boxes, which gives different numbers today. Each item is quoted by its opening words so there is no ambiguity.
Items
refuseRecv..."Worker.ringWaitContinues... has no ceiling"w.sweepRetract()... discriminate"closeFDOwedgatecase udAcceptandcancelAccept..."73 (+ L3 follow-ups F1, F4)
Past the gate (250 ms into the pool wait or the send drain),
closeIdleH1(paced like the producer walk, at most 64 closes per pass) closes an HTTP/1 conn that is idle, holds nothing queued (user space, SEND/ZC in flight, kernel send queue via SIOCOUTQ), has no request in progress, and is a detached stream the gate ended (sync or Async route) or has sent no byte.gateReadsOnmakes handleRecv read a request in progress on (headers in pieces, a body arriving,recvIntoBody, protocol-decision bytes) instead of dropping it; it is answered withConnection: close(H1State.CloseAfter, written only on h1State the worker owns or under a successful detachMu TryLock).Decisions: idle close + read-on over
SHUT_RD; the grace stays 250 ms into the wait (as epoll), not at the cancel; thelastActivitycomplaint is covered by the idle close (the stamp is not moved); a pool wait with a no-deadline ctx and a stuck HTTP/2 handler is still unbounded (as std), only the HTTP/1 clients are released.Goes beyond epoll: for Async-promoted conns an idle conn is closed and a request in progress is read on (epoll's F2 leaves both to the end of the wait). Safety:
asyncInMu(input waiting, or goroutine alive and out of its park loop), thendetachMu.TryLock(free only between requests), then h1State is read under it; a failed TryLock means "in progress". The worker is the only feeder ofasyncInBuf.Known corner: a client that sends bytes after the gate on a conn the gate later closes gets an RST (close with unread bytes), as with any close(2) with unread data.
72
Ceiling 1 s of consecutive retried passes with an empty CQ ring, then Error log and
w.shutdown(). "Consecutive" means: the loop pass count (tickCounter, once per pass) moved by at most one since the last retry (a larger jump means a wait succeeded between), and no more than 250 ms (ringWaitRunGap) passed since the last retry (a park or a slow callback ends the run; review r1 m1). A retry that finds a completion waiting resets the run (deliberate reading of "no reaped completion": a ring that keeps delivering completions is never given up on). With one worker (memlock-capped) the ceiling stops the only worker, as any other ring failure does.16, 80, 71, 63
16:
TestShutdownDrainEndsALinkedRecvAfterItsSendCQE884,TestShutdownDrainEndsAReArmedMultishotRecv884; red with the drain disabled (M1). 80:TestParkedWorkerRetractsResidueOfACloseWithNothingInFlight884. What is proven: the retraction is needed for the state the test manufactures intestHookCheckTimeouts(red with it removed; the other two arms stay green as documented). What is NOT proven: that production reaches that state (no traffic path found), nor that the retraction is dead behind thecloseFDOwedgate. The item is not closed on either outcome. 71:TestLeaveGroupAfterExitClosesTheRingAndTheListener884,TestReportWorkerExitLeavesTheGroupBeforeTheFlag884(red 5/5 when leaveGroupAfterExit moves after the flag),TestGoexitedWorkerLeavesTheGroupBeforeItIsMarkedExited884; not done: the epoll half, CI single-worker coverage,hotpath-table.txt(a lane artifact).SHUT_RDremoval (M13) stays green on the new tests; the #848 lifecycle tests cover it. 63: known limit documented at the drain'scase udAcceptand incancelAccept.Review r1 (Opus, head 556a5f2) and what changed
gateReadsOnreadcs.h1Stateof a promoted conn before checkingasyncPromoted(data race with the dispatch goroutine's h2c switch, celeris#722). Fixed by the reorder;promotedInProgressalso returns false for a conn whose writes ended (writeEnded, an atomic). The search for the same mistake foundstopDetachedProducers(pre-existing, run by the gate pass) readingh1Statebefore its TryLock on a promoted conn: fixed the same way. Regression testsTestGateReadsOnNeverReadsH1StateOfAGoroutinesConn884andTestStopDetachedProducersNeverReadsH1StateOfAGoroutinesConn884fail under-racebefore the fix (the tests-first commit) and pass after; putting the read back above the check (mutant M14) is red.TestRingWaitContinuesDoesNotCarryARunAcrossALongPass884failed first.recvIntoBodyterm ofgateCloseablecannot be told fromHasPendingDataon the wire: the worker setsrecvIntoBodyonly whenNextRecvBufreturned a buffer, which needsbodyNeeded > 0, whichHasPendingDatareports. It is an equivalent mutant at the server level; held by the unit table, andTestRecvIntoBodyImpliesPendingData884pins the implication.TestGoexitedWorkerLeavesTheGroupBeforeItIsMarkedExited884is racy under its mutant (red 3 of 5);TestReportWorkerExitLeavesTheGroupBeforeTheFlag884is the deterministic guard (5 of 5), noted in the test.Findings, not fixed here (design extension on both engines, shared with epoll #1116)
Connection: closeis not guaranteed on every response after Shutdown (net/http marks every response after Shutdown as close): (a) every response written between the cancel and the gate (the first 250 ms); (b) a request read on a promoted conn whose goroutine is busy at every look (input waiting, or inside a handler:CloseAftercannot be written without the lock); (c) an undetected conn holding a protocol-decision prefix ("P", "PR"), which has no h1State to mark and is answered in the samehandleRecv. A keep-alive client that then sends its next request gets a dropped request and a FIN or RST. Follow-up for both engines: makeCloseAfteranatomic.Bool(or havehandleH1Requestconsult an engine shutting-down flag) and set it on every live HTTP/1 conn at the cancel.Open: item 16 trial (c), whether a kernel issues work after the descriptors close, and the keep-or-remove decision on the drain.
Evidence
f57a260+ the final test files),TestIOUringShutdownGateConns884fails in all 5 arms (pool-wait/send-drain x sync/async-route, adaptive-iouring): 6 idle conns, 2 silent conns, 4 detached streams (no FIN), the POST/headers-in-pieces requests (and POST on an Async route) unanswered, the idle Async conn, and the drained download left open. The ceiling tests fail on main (stuck ring retried for 5 s / 6 s).TestGatePassWaitsForTheKernelSendQueue884../internal/engine/iouring/...-race at 8 MiB memlock green;test/serverTestShutdown*/TestDrain* green; golangci-lint v2.14 clean.w.refuseRecv &&short-circuit; the pass runs only insidectx.Err() != nil;ringWaitContinuesruns only on a wait error or with the seam set. A/B ofBenchmarkIouringRecvShapesbetween main and this branch (6 runs each, laptop VM) overlaps within noise (10.4 to 26.7 us vs 11.1 to 25.6 us per op).For the reviewer
CloseAfter write sites and who owns h1State; the window between
gateCloseablereleasing its locks andcloseConn; the blockingasyncInMu.Lockin the pass (the goroutine can hold it across the application'sRouteAsync); ceiling false positives on 5.19 to 6.1 kernels; the SIOCOUTQ check counts unacknowledged bytes, so a stalled peer holds its conn to the end of the wait, as on epoll.