Skip to content

fix: skip drain dispatch for clientTtl-evicted clients in PoolBase - #5677

Merged
mcollina merged 1 commit into
nodejs:mainfrom
vwong:fix/client-closed-error-on-client-ttl
Sep 14, 2026
Merged

mcollina merged 1 commit into
nodejs:mainfrom
vwong:fix/client-closed-error-on-client-ttl

Conversation

@vwong

@vwong vwong commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Problem

When a Pool is configured with clientTtl, evicted clients can still emit drain events after being closed — typically when an in-flight request completes after the client has been removed from the pool. kOnDrain has no guard against this and will attempt to dispatch queued pool items to the closed client, throwing ClientClosedError.

The race window is narrow in low-latency environments (same-region) but widens significantly with cross-region TLS connection times (hundreds of milliseconds), making the error reliably reproducible under load.

Sequence that triggers the bug:

  1. Pool has connections: 1. Client C1 is active, its clientTtl expires.
  2. A new request arrives → kGetDispatcher evicts C1 (client.close(), kClosed = true), creates C2.
  3. C2 starts a TLS handshake. While it's connecting, further requests fill the pool queue (kQueue), because C2 has kNeedDrain = true and the connections limit is reached.
  4. C1 finishes an in-flight response → resume() → emitDrain() → C1.emit('drain').
  5. kOnDrain(C1) dequeues an item from kQueue and calls C1.dispatch() → ClientClosedError.

Fix

Return early in kOnDrain when the client is already closed or destroyed. The queued items remain in kQueue and are correctly drained when C2 connects and emits its own drain event.

Why connections: 2 doesn't prevent this alone

The drain listener is added in kAddClient and bound to the specific client instance. kRemoveClient removes the client from kClients but never removes the listener. So even with a second connection available, the stale listener on C1 still fires and attempts to dispatch to C1.

@codecov-commenter

codecov-commenter commented Aug 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.45%. Comparing base (dd85997) to head (0c26a12).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5677      +/-   ##
==========================================
+ Coverage   93.43%   93.45%   +0.02%     
==========================================
  Files         110      110              
  Lines       38733    38780      +47     
==========================================
+ Hits        36190    36242      +52     
+ Misses       2543     2538       -5     

☔ View full report in Codecov by Harness.
📢 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.

@mcollina
mcollina merged commit 2a783e7 into nodejs:main Sep 14, 2026
38 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 14, 2026
This was referenced Sep 21, 2026
penberg added a commit to tursodatabase/turso that referenced this pull request Oct 14, 2026
…vascript' from app/dependabot

Bumps [undici](https://github.com/nodejs/undici) from 8.10.0 to 8.11.2.
<details> <summary>Release notes</summary> <p><em>Sourced from <a
href="https://github.com/nodejs/undici/releases">undici's
releases</a>.</em></p> <blockquote> <h2>v8.11.2</h2> <h2>What's
Changed</h2> <ul> <li>fix: close rejected HTTP/2 WebSocket handshake
streams by <a
href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a hre
f="https://redirect.github.com/nodejs/undici/pull/5873">nodejs/undici#58
73</a></li> <li>fix: avoid reconnecting aborted requests by <a
href="https://github.com/Junaid-PK"><code>@​Junaid-PK</code></a> in <a h
ref="https://redirect.github.com/nodejs/undici/pull/5846">nodejs/undici#
5846</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a
href="https://github.com/Junaid-PK"><code>@​Junaid-PK</code></a> made
their first contribution in <a href="https://redirect.github.com/nodejs/
undici/pull/5846">nodejs/undici#5846</a></li> </ul> <p><strong>Full
Changelog</strong>: <a href="https://github.com/nodejs/undici/compare/v8
.11.1...v8.11.2">nodejs/undici@v8.11.1...v8.1
1.2</a></p> <h2>v8.11.1</h2> <h2>What's Changed</h2> <ul> <li>deslopify
websocket test by <a
href="https://github.com/KhafraDev"><code>@​KhafraDev</code></a> in <a h
ref="https://redirect.github.com/nodejs/undici/pull/5851">nodejs/undici#
5851</a></li> <li>Revert &quot;fix: preserve HTTP/2 for legacy fetch
consumers&quot; by <a
href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a hre
f="https://redirect.github.com/nodejs/undici/pull/5860">nodejs/undici#58
60</a></li> <li>fix websocketstream bugs by <a
href="https://github.com/KhafraDev"><code>@​KhafraDev</code></a> in <a h
ref="https://redirect.github.com/nodejs/undici/pull/5863">nodejs/undici#
5863</a></li> <li>fix hang on 3xx response with large body by <a
href="https://github.com/KhafraDev"><code>@​KhafraDev</code></a> in <a h
ref="https://redirect.github.com/nodejs/undici/pull/5850">nodejs/undici#
5850</a></li> <li>docs: warn about buffering response bodies by <a
href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a hre
f="https://redirect.github.com/nodejs/undici/pull/5872">nodejs/undici#58
72</a></li> <li>test: cover native fetch gzip decoding through the
legacy bridge by <a
href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a hre
f="https://redirect.github.com/nodejs/undici/pull/5874">nodejs/undici#58
74</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://g
ithub.com/nodejs/undici/compare/v8.11.0...v8.11.1">https://github.com/no
dejs/undici/compare/v8.11.0...v8.11.1</a></p> <h2>v8.11.0</h2>
<h2>What's Changed</h2> <ul> <li>fix: complete upgrade diagnostics
lifecycle by <a
href="https://github.com/BridgeAR"><code>@​BridgeAR</code></a> in <a hre
f="https://redirect.github.com/nodejs/undici/pull/5761">nodejs/undici#57
61</a></li> <li>build(deps): bump step-security/harden-runner from
2.19.4 to 2.21.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a href="https://redirect.github.com/nodejs/undici/pull/5753">nodejs/
undici#5753</a></li> <li>fix(decompress): join a repeated content-
encoding header by <a
href="https://github.com/luantaraschi"><code>@​luantaraschi</code></a>
in <a href="https://redirect.github.com/nodejs/undici/pull/5664">nodejs/
undici#5664</a></li> <li>fix(cookies): keep a cookie named
<strong>proto</strong> in getCookies by <a
href="https://github.com/luantaraschi"><code>@​luantaraschi</code></a>
in <a href="https://redirect.github.com/nodejs/undici/pull/5663">nodejs/
undici#5663</a></li> <li>build(deps-dev): bump jest from 30.5.0 to
30.5.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a href="https://redirect.github.com/nodejs/undici/pull/5784">nodejs/
undici#5784</a></li> <li>build(deps): bump got from 15.1.0 to 16.0.0 in
/benchmarks by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a href="https://redirect.github.com/nodejs/undici/pull/5785">nodejs/
undici#5785</a></li> <li>fix: selectively re-enable SIMD for ppc64 by <a
href="https://github.com/richardlau"><code>@​richardlau</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5776">nodejs/undici
#5776</a></li> <li>use private fields for brand checks by <a
href="https://github.com/KhafraDev"><code>@​KhafraDev</code></a> in <a h
ref="https://redirect.github.com/nodejs/undici/pull/5792">nodejs/undici#
5792</a></li> <li>fix(h2): do not unref a session with open streams by
<a href="https://github.com/kjsik11"><code>@​kjsik11</code></a> in <a hr
ef="https://redirect.github.com/nodejs/undici/pull/5782">nodejs/undici#5
782</a></li> <li>fix(h2): avoid pool fan-out during ALPN negotiation by
<a href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5799">nodejs/undici
#5799</a></li> <li>fix issue <a
href="https://redirect.github.com/nodejs/undici/issues/5715">#5715</a>
by <a href="https://github.com/KhafraDev"><code>@​KhafraDev</code></a>
in <a href="https://redirect.github.com/nodejs/undici/pull/5805">nodejs/
undici#5805</a></li> <li>ci: extend macOS Node.js workflow timeout by <a
href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a hre
f="https://redirect.github.com/nodejs/undici/pull/5775">nodejs/undici#57
75</a></li> <li>fix(env-http-proxy-agent): NO_PROXY wildcard semantics
by <a href="https://github.com/mcollina"><code>@​mcollina</code></a> in
<a href="https://redirect.github.com/nodejs/undici/pull/5777">nodejs/und
ici#5777</a></li> <li>fix(h2): do not replay a request whose response
already started after GOAWAY by <a
href="https://github.com/kjsik11"><code>@​kjsik11</code></a> in <a href=
"https://redirect.github.com/nodejs/undici/pull/5788">nodejs/undici#5788
</a></li> <li>test: skip HTTP/2 churn test on Node.js 25 on all
platforms by <a
href="https://github.com/kjsik11"><code>@​kjsik11</code></a> in <a href=
"https://redirect.github.com/nodejs/undici/pull/5790">nodejs/undici#5790
</a></li> <li>fix(proxy-agent): preserve repeated iterable headers by <a
href="https://github.com/GiHoon1123"><code>@​GiHoon1123</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5779">nodejs/undici
#5779</a></li> <li>docs(errors): document ProxyConnectionError
(UND_ERR_PRX_CONN) by <a href="https://github.com/kilisamemarisaaa"><cod
e>@​kilisamemarisaaa</code></a> in <a href="https://redirect.github.com/
nodejs/undici/pull/5781">nodejs/undici#5781</a></li> <li>dataurl parsing
improvements + update WPT by <a
href="https://github.com/KhafraDev"><code>@​KhafraDev</code></a> in <a h
ref="https://redirect.github.com/nodejs/undici/pull/5812">nodejs/undici#
5812</a></li> <li>fix(decompress): treat inherited Object.prototype
names as unsupported encodings by <a href="https://github.com/NgoQuocVie
t2001"><code>@​NgoQuocViet2001</code></a> in <a href="https://redirect.g
ithub.com/nodejs/undici/pull/5807">nodejs/undici#5807</a></li>
<li>test(h2): stabilize http2-connection by <a
href="https://github.com/ViniciusDev26"><code>@​ViniciusDev26</code></a>
in <a href="https://redirect.github.com/nodejs/undici/pull/5801">nodejs/
undici#5801</a></li> <li>fix: preserve HTTP/2 for non-upgrade legacy
requests by <a
href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a hre
f="https://redirect.github.com/nodejs/undici/pull/5811">nodejs/undici#58
11</a></li> <li>fix: skip drain dispatch for clientTtl-evicted clients
in PoolBase by <a
href="https://github.com/vwong"><code>@​vwong</code></a> in <a href="htt
ps://redirect.github.com/nodejs/undici/pull/5677">nodejs/undici#5677</a>
</li> <li>chore: update tests blocking release by <a
href="https://github.com/vwong"><code>@​vwong</code></a> in <a href="htt
ps://redirect.github.com/nodejs/undici/pull/5828">nodejs/undici#5828</a>
</li> <li>build(deps): bump uWebSockets.js from v20.68.0 to v20.70.0 in
/benchmarks by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a href="https://redirect.github.com/nodejs/undici/pull/5824">nodejs/
undici#5824</a></li> <li>fix(deduplicate): settle requests when composed
with retry by <a
href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a hre
f="https://redirect.github.com/nodejs/undici/pull/5817">nodejs/undici#58
17</a></li> <li>fix(cache): do not report aborted on successful cache
hits by <a
href="https://github.com/Yahiro025"><code>@​Yahiro025</code></a> in <a h
ref="https://redirect.github.com/nodejs/undici/pull/5819">nodejs/undici#
5819</a></li> </ul> <!-- raw HTML omitted --> </blockquote> <p>...
(truncated)</p> </details> <details> <summary>Commits</summary> <ul>
<li><a href="nodejs/undici@7e016ad7e5bd6540170
069a9a28b1e633b29664f"><code>7e016ad</code></a> Bumped v8.11.2 (<a href=
"https://redirect.github.com/nodejs/undici/issues/5886">#5886</a>)</li>
<li><a href="nodejs/undici@fcdd5a615ab49beda87
aafe14ad30106cfe3b0d5"><code>fcdd5a6</code></a> fix: skip reconnecting
for aborted requests (<a href="https://redirect.github.com/nodejs/undici
/issues/5846">#5846</a>)</li> <li><a href="https://github.com/nodejs/und
ici/commit/dd28c5c55f4a25f7c441fd4365b1191f96c3b8bf"><code>dd28c5c</code
></a> fix: close rejected HTTP/2 WebSocket handshake streams (<a href="h
ttps://redirect.github.com/nodejs/undici/issues/5873">#5873</a>)</li>
<li><a href="nodejs/undici@5e0a2f8e02be8f1155f
eab2c52f55731d3bb84a4"><code>5e0a2f8</code></a> Bumped v8.11.1 (<a href=
"https://redirect.github.com/nodejs/undici/issues/5871">#5871</a>)</li>
<li><a href="nodejs/undici@2b728989ac5b9d247ec
a2094bb264ced76f7cef6"><code>2b72898</code></a> test: cover native fetch
gzip decoding through the legacy bridge (<a href="https://redirect.githu
b.com/nodejs/undici/issues/5874">#5874</a>)</li> <li><a href="https://gi
thub.com/nodejs/undici/commit/636bf515c0c8497dd0ca68917e4e38577c27669e">
<code>636bf51</code></a> docs: warn about buffering response bodies (<a
href="https://redirect.github.com/nodejs/undici/issues/5872">#5872</a>)<
/li> <li><a href="nodejs/undici@8145595e221779
980d91ff611f14e5a984661388"><code>8145595</code></a> fix hang on 3xx
response with large body (<a href="https://redirect.github.com/nodejs/un
dici/issues/5850">#5850</a>)</li> <li><a href="https://github.com/nodejs
/undici/commit/f3cbe486706da6163b4ca226022e74df73168dd5"><code>f3cbe48</
code></a> fix websocketstream bugs (<a href="https://redirect.github.com
/nodejs/undici/issues/5863">#5863</a>)</li> <li><a href="https://github.
com/nodejs/undici/commit/1d4ea0a883bf33275d00c4db55377892180b2747"><code
>1d4ea0a</code></a> Revert &quot;fix: preserve HTTP/2 for legacy fetch
consumers&quot; (<a href="https://redirect.github.com/nodejs/undici/issu
es/5860">#5860</a>)</li> <li><a href="https://github.com/nodejs/undici/c
ommit/77d7b3f8dd2d8114b686e32d8d28bd3dde06bc33"><code>77d7b3f</code></a>
deslopify websocket test (<a href="https://redirect.github.com/nodejs/un
dici/issues/5851">#5851</a>)</li> <li>Additional commits viewable in <a
href="https://github.com/nodejs/undici/compare/v8.10.0...v8.11.2">compar
e view</a></li> </ul> </details> <br />

[![Dependabot compatibility score](https://dependabot-
badges.githubapp.com/badges/compatibility_score?dependency-
name=undici&package-manager=npm_and_yarn&previous-version=8.10.0&new-
version=8.11.2)](https://docs.github.com/en/github/managing-security-
vulnerabilities/about-dependabot-security-updates#about-compatibility-
scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end)

---

<details> <summary>Dependabot commands and options</summary> <br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/tursodatabase/turso/network/alerts).

</details>

Closes #9861
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.

4 participants