fix: revert idle socket validation to setTimeout(0) to prevent stall on idle event loop - #5606
fix: revert idle socket validation to setTimeout(0) to prevent stall on idle event loop#5606marceli1404 wants to merge 3 commits into
Conversation
…on idle event loop The change from setTimeout(0) to setImmediate() in nodejs#5499 introduced a regression where reusing an idle keep-alive connection could stall for ~500ms. The root cause is the interaction between setImmediate().unref() and socket.unref() in the idle state: when the socket is unref'd and the only remaining handle is an unref'd setImmediate, the event loop poll phase waits for its timeout before reaching the check phase. setTimeout(0) fires in the timers phase (before poll), avoiding this problem entirely at the cost of ~1ms timer resolution per reuse. Fixes nodejs#5600
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5606 +/- ##
=======================================
Coverage 93.49% 93.49%
=======================================
Files 110 110
Lines 38281 38303 +22
=======================================
+ Hits 35789 35812 +23
+ Misses 2492 2491 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Added a regression test for the idle socket reuse stall ( est/node-test/keep-alive-reuse.js). It makes 6 sequential requests on a single-connection pool with 50ms server delay and asserts each request completes within 200ms. The test passes on this branch. |
Replace wall-clock timing and artificial server delays with server connection/request event coordination. The test timeout catches the setImmediate poll-phase stall regression without flaky Date.now checks. Signed-off-by: Matteo Collina <hello@matteocollina.com>
mcollina
left a comment
There was a problem hiding this comment.
LGTM. The setTimeout(0) revert is the right fix for the idle keep-alive stall, and the regression test now drives progress via server connection/request events instead of wall-clock thresholds.
Summary
Reverts
scheduleIdleSocketValidationinclient-h1.jsfromsetImmediate()back tosetTimeout(0), along with the correspondingclearImmediate→clearTimeout.Background
PR #5499 changed
setTimeout(0)→setImmediate()to remove the ~1ms timer floor on idle socket validation. While that change performed well under sustained load (where the event loop is continuously busy), it causes up to ~500ms stalls when reusing an idle keep-alive socket on an otherwise idle event loop.Root Cause
When a keep-alive socket is reused and the event loop is otherwise idle:
resumeH1()unrefs the socket (socket.unref())scheduleIdleSocketValidationsets anunref()'dsetImmediatesetTimeout(0)fires in the timers phase (before poll), so the callback runs promptly regardless of poll timeout, at the cost of ~1ms timer resolution.Reproduction
Script in the issue shows sequential requests on a 1-connection pool taking 20ms, 458ms, 34ms, 51ms, 450ms, 464ms instead of ~13ms each.
Fix
This PR reverts the approach from #5499. The original motivation (removing the timer floor) is worthwhile, but it introduces an order-of-magnitude worse regression on idle event loops. A hybrid approach (check-phase scheduling with poll-timeout prevention) could be explored separately.
Fixes #5600