Skip to content

fix: avoid premature cleanup of dispatcher in Agent#5034

Open
bienzaaron wants to merge 4 commits intonodejs:mainfrom
bienzaaron:main
Open

fix: avoid premature cleanup of dispatcher in Agent#5034
bienzaaron wants to merge 4 commits intonodejs:mainfrom
bienzaaron:main

Conversation

@bienzaaron
Copy link
Copy Markdown

@bienzaaron bienzaaron commented Apr 16, 2026

This relates to...

#5022

Changes

The fixes in #4223 and #4425 introduce a bug where keep-alive connections are sometimes not correctly reused after the first disconnection for a dispatcher.

The issue is a race between disconnect and new dispatches. After a response with Connection: close or after the connection reaches its request limit, the socket disconnects. If a new request has already been dispatched to the same dispatcher but has not yet been processed into a new connection, the disconnect handler can incorrectly treat the dispatcher as unused and call close() on it.

As far as I can tell, this is still a graceful failure (new request doesn't fail), but it does result in a new connection being used. If new requests are continually dispatched like this, then connections are never reused after the first one disconnects. Delaying the next dispatched request to the next event loop iteration (e.g. via setTimeout(cb, 0)) or longer results in the connection being reused correctly.

Features

N/A

Bug Fixes

#5022

Breaking Changes and Deprecations

N/A

Status

Comment thread lib/dispatcher/agent.js
Comment on lines +100 to +102
if (dispatcher[kConnected] > 0 || dispatcher[kBusy]) {
return
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key difference is here - we check active connections as well as kBusy:

get [kBusy] () {
return Boolean(
this[kHTTPContext]?.busy(null) ||
(this[kSize] >= (getPipelining(this) || 1)) ||
this[kPending] > 0
)
}

If we have either active connections or the dispatcher is busy, we don't close it.

This lets us clean up the connection-tracking bookkeeping and just use information already in the dispatcher.

Comment thread lib/dispatcher/agent.js
this[kOrigins].delete(origin)
let hasOrigin = false
for (const client of this[kClients].values()) {
if (client[kUrl].origin === dispatcher[kUrl].origin) {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slightly updated logic here, again to reduce bookkeeping -- access origin from client[kUrl].origin instead of storing it

}

if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) {
if (client[kMaxRequests] && ++socket[kCounter] >= client[kMaxRequests]) {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slightly unrelated to this PR, but I cherry-picked f68bd53 from #5033 so that my tests pass (otherwise, the number of requests per connection would be off by one).

@bienzaaron bienzaaron changed the title fix: avoid premature cleanup of keep-alive connections fix: avoid premature cleanup of dispatcher in Agent Apr 16, 2026
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.03%. Comparing base (bc0a19c) to head (f0927a8).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5034      +/-   ##
==========================================
- Coverage   93.03%   93.03%   -0.01%     
==========================================
  Files         110      110              
  Lines       35793    35791       -2     
==========================================
- Hits        33301    33297       -4     
- Misses       2492     2494       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants