Repository navigation
drain event is unreliable when using cork/uncork with ServerResponse #60432
Description
Activity
From adding the following additional logging, I can confirm that the specific issue is that the
drainevent is not firing (andwritableNeedDrainis not resetting tofalse) despite the stream draining:function drainUncorked(target) { console.log(target.writableLength, target.writableHighWaterMark, target.writableNeedDrain); // existing implementation from above } const s = createServer(async (req, res) => { res.on('drain', () => console.log('got drain event')); const i = setInterval(() => console.log(res.writableLength, res.writableHighWaterMark, res.writableNeedDrain), 10); // existing handling code from above clearInterval(i); }
listening request 1 2 1000237 65536 true drain required: true 1000002 65536 true 1000002 65536 true 1000002 65536 true got drain event drain complete 3 4 1000100 65536 true drain required: true 0 65536 true <-- note: writableLength has returned to 0 but writableNeedDrain is still true and no drain event has fired 0 65536 true [repeats infinitely]A workaround for this issue is to use the callback of
writeinstead of listening fordrain:- target.once('drain', () => { ... }); + target.write('', () => { ... });
- added 2 commits that reference this issue
on Oct 27, 2025 github-actions commented
on May 26, 2026 on May 26, 2026 – with GitHub ActionsContributorMore actionsThis issue has been marked as stale due to 210 days of inactivity.
It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.- addedstaleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.Issues and PRs marked stale due to inactivity and scheduled for automatic closure.
on May 26, 2026 Looks like #60437 would have fixed this but tripped up on some test formatting and was closed / abandoned. @ThierryMT is it OK for me to pick up your changes and make the final tweaks requested by the maintainers? I see that your changes will still cleanly apply to the latest
main.- removedstaleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.Issues and PRs marked stale due to inactivity and scheduled for automatic closure.
on May 27, 2026 - added a commit that references this issue
on Jul 2, 2026 - added a commit that references this issue
on Jul 3, 2026 - added a commit that references this issue
on Jul 8, 2026 - added 3 commits that reference this issue
on Jul 21, 2026 - added a commit that references this issue
on Jul 30, 2026
Version
v24.10.0
Platform
Subsystem
No response
What steps will reproduce the bug?
This code snippet will occasionally hang while waiting for the second
drainevent on MacOS, and will reliably hang while waiting for the firstdrainevent on Linux:How often does it reproduce? Is there a required condition?
This reproduces intermittently on v24.10.0 on MacOS (tested: 3 failures in 30 attempts; ~10% failure rate), and reliably on v22.20.0 on Linux (specifically a Raspberry Pi running
Linux pi5 6.12.47+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.12.47-1+rpt1~bookworm (2025-09-16) aarch64 GNU/Linux)What is the expected behavior? Why is that the expected behavior?
When
writableNeedDrainistrue(and equivalently if.writereturnsfalse), there should always be adrainevent emitted once the stream has been consumed. This should apply regardless of whether thecorkfeature is being used (as long as the stream is uncorked once it needs to drain), and any listener registered whilewritableNeedDrainistrueshould be guaranteed to receive this drain event.What do you see instead?
on macOS, the code above frequently hangs at this point:
By using
curl -vvv localhost:8080instead of thefetchexample code, I can see that the response content is being drained successfully (i.e. the correct number of '4's are downloaded), so thedrainevent ought to be fired.Testing by adding additional delays surprisingly makes this more likely to fail. For example, this adapted version of
drainUncorkedwhich waits for an event loop between each command fails every time on the second drain on macOS:Additional information
Without
cork/uncork, the code always succeeds. I have also tested this on a raw socket and confirmed thedrainevent fires correctly there; this code succeeds every time:# run netcat in the background as a destination for the socket nc -l 8080Since this is unique to ServerResponse, I suspect it is related to the chunk merging behaviour from #50167 (and chunk merging is why I want to use
corkin the first place)