Skip to content

http: emit error on aborted ServerResponse#63448

Open
trivikr wants to merge 3 commits into
nodejs:mainfrom
trivikr:http-response-emit-error-or-finish
Open

http: emit error on aborted ServerResponse#63448
trivikr wants to merge 3 commits into
nodejs:mainfrom
trivikr:http-response-emit-error-or-finish

Conversation

@trivikr
Copy link
Copy Markdown
Member

@trivikr trivikr commented May 20, 2026

This updates ServerResponse socket-close handling so an ended response that
never emitted finish is marked with ECONNRESET and emits error before
close when an error listener is present.

A regression test covers the case where the request socket is destroyed before
a deferred res.end(), which previously emitted only close.

Fixes: #50656


Assisted-by: openai:gpt-5.5

Emit an error when a ServerResponse is closed after end() but before
the finish event is emitted.

Fixes: nodejs#50656

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: openai:gpt-5.5
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels May 20, 2026
trivikr added 2 commits May 19, 2026 22:57
Expect ECONNRESET when an HTTP response pipeline is closed before the
response finish event is emitted.
Update HTTP tests that intentionally close the client connection to
expect ECONNRESET from ServerResponse.
@ronag ronag added the semver-major PRs that contain breaking changes and should be released in the next major version. label May 20, 2026
@ronag ronag requested a review from mcollina May 20, 2026 06:42
@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.03%. Comparing base (0b5b189) to head (a098f1e).
⚠️ Report is 9 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #63448      +/-   ##
==========================================
- Coverage   90.06%   90.03%   -0.03%     
==========================================
  Files         714      714              
  Lines      225918   225930      +12     
  Branches    42734    42735       +1     
==========================================
- Hits       203464   203422      -42     
- Misses      14232    14281      +49     
- Partials     8222     8227       +5     
Files with missing lines Coverage Δ
lib/_http_outgoing.js 95.81% <100.00%> (+0.01%) ⬆️
lib/_http_server.js 96.63% <100.00%> (+0.02%) ⬆️

... and 34 files with indirect coverage changes

🚀 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.

@pimterry
Copy link
Copy Markdown
Member

We need to link this up to #63249, which actively aims to match the current ServerResponse behaviour that this PR is now changing (neither erroring nor finishing if clients abort). These behaviours need to match.

Do we definitely want to change this though? In the referenced issue (#50656) back in 2023, @ronag said:

The current invariant for Writable streams is that they should always emit either 'error' or 'finish'.

But that's not generally true. There's plenty of counter-examples, most notably calling destroy() on any writables always emits close without error or finish. Similarly, we don't error or finish stdin if a child process cleanly exits:

const { spawn } = require('node:child_process');

const child = spawn('node', ['-e', 'process.exit(0)']);

child.stdin.on('finish', () => console.log('stdin: finish'));
child.stdin.on('error', () => console.log('stdin: error'));
child.stdin.on('close', () => console.log('stdin: close'));

In effect, closed && !errored && !writableFinished is our standard 'clean abort' check. This is sufficiently standard that we check for it explicitly here as part of stream.finished() - automatically turning any such clean abort into a ERR_STREAM_PREMATURE_CLOSE if you're actively waiting for finished as a promise.

In the specific HTTP case this is a very common occurrence (clients abort responses all the time) so this will produce a lot of errors. It's good that it's guarded just for listened events, but still it'll be a very visible behavioural change and it's hard to predict the results of that. Currently, AFAICT most implementations largely happily ignore this scenario unless they're doing something expensive, and they can already detect this manually if they want to.

I'm very open to exploring this more, we could certainly improve stream & HTTP client abort behaviour, but this isn't the existing invariant, and I can see arguments on it both ways so we shouldn't just accept it as given. If we really do want to change it, we should definitely change the HTTP/2 PR above before that's merged too, and then potentially look at changing lots of other Node APIs as well afterwards to try to make this invariant consistent.

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

Labels

http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. semver-major PRs that contain breaking changes and should be released in the next major version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTP Response doesn't always emit error or finish

4 participants