Repository navigation
Conversation
A free socket with an idle timeout is normally destroyed by its timer. When the event loop is blocked long enough for that timeout to expire, the timer cannot run, and `addRequest()` can reuse the socket even though the server may already have closed the connection. This can cause the request to fail with `ECONNRESET`. Record when a socket enters the free pool and, before reuse, remove and destroy a free socket whose idle timeout has expired according to the timers' clock. Refs: nodejs#54293 Assisted-by: a closed-source coding agent Signed-off-by: dennev <dennev13@gmail.com>
Collaborator
|
Review requested:
|
Contributor
|
Welcome to Node.js, and thank you for your first contribution! Before review, please take a moment to read:
Please make sure every commit is signed off. For a first pull request, GitHub Actions require collaborator approval and Jenkins CI must be started by a collaborator or triager, so an initial wait is normal. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #67292 +/- ##
==========================================
- Coverage 90.69% 90.69% -0.01%
==========================================
Files 797 797
Lines 278787 279632 +845
Branches 53610 53781 +171
==========================================
+ Hits 252849 253604 +755
+ Misses 16310 16298 -12
- Partials 9628 9730 +102
🚀 New features to boost your workflow:
|
Collaborator
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.
When the event loop is blocked long enough for a free socket's idle timeout
to expire, its timeout callback cannot run. The next request can then reuse
that socket from
agent.freeSocketseven though the server may already haveclosed the connection, causing the request to fail with
ECONNRESET.This change addresses the timeout-expiry case discussed in #54293. It records
when a socket enters the free pool using the timers' clock
(
timersBinding.getLibuvNow()). InaddRequest(), expired candidates areremoved and destroyed until an unexpired socket is found or the pool is empty.
The clock call preserves the binding object as its receiver, as required by
the fast API call. The regression test also exercises an optimized
addRequest().Not covered by this change:
without a
timeout(http: Agent with keepAlive and no timeout ignores the server's Keep-Alive timeout hint #66734, http: respect Keep-Alive timeout hint when agent has no explicit timeout #66779)keep-alive race discussed in Network request fails after event loop is blocked for a few seconds (regression in v20) #54293)
'free'handler to a queued requestin a response's
'data'or'end'handler: the idle timestamp is recordedonly when the socket enters the pool, so time spent blocked before that
point is not counted
Testing: I personally ran the regression test on a release build in a Linux
container, and it passed. I also ran a reproduction based on #54293, with
server.keepAliveTimeout = 5000and a 6-second block between two requeststhrough
http.globalAgent. It fails withECONNRESETon v26.12.1 and succeedswith this change, using a new socket.
Agent-run validation confirmed that the regression test fails without this
change and passes with it. It covers LIFO and FIFO expiry, reuse before expiry,
unchanged reuse without an idle timeout,
maxSocketsaccounting after a block,and optimized
addRequest().Agent-run validation on Linux also included the HTTP/HTTPS and agent-related
tests, and the full
make -j12 test, all of which passed. The full-suite runused a release build in a container started with
--init.AI disclosure: Claude Code (Claude Opus 5.5) was used to investigate the cause,
write and review the code, test and documentation changes, and run the
agent-run validation described above. This description started from a draft
by the agent, which I rewrote. Before opening this pull request, Claude and
Codex (gpt-6.1-sol) reviewed the final change, the commit message and this
description.
I read the change and the new test myself and checked them against the code
paths described above.
Refs: #54293