Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http: emit ERR_STREAM_DESTROYED if the socket is destroyed #29227

Closed
wants to merge 3 commits into from

Conversation

mcollina
Copy link
Member

The pipeline utility assumes that a stream is going to error if it
could not be written to it. If an http.OutgoingMessage was piped after
the underlining socket had been destroyed, the whole pipeline would not
be teared down, resulting in a file descriptor and memory leak.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines

@nodejs-github-bot nodejs-github-bot added the http Issues or PRs related to the http subsystem. label Aug 20, 2019
@mcollina
Copy link
Member Author

cc @ronag @Trott

});

req.on('response', common.mustNotCall());
req.on('error', common.mustCall());
Copy link
Member

Choose a reason for hiding this comment

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

If I am not mistaken, this results in the ERR_STREAM_DESTROYED as well?

Suggested change
req.on('error', common.mustCall());
req.on('error', common.expectsError({
code: 'ERR_STREAM_DESTROYED'
}));

Copy link
Member

Choose a reason for hiding this comment

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

ping @mcollina Does that suggestion above seem good to you? (Seems good to me....)

Copy link
Member Author

Choose a reason for hiding this comment

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

That's going to be 'ECONNRESET' on my machine.

callback(err);
}
if (this.listenerCount('error') > 0 && this[kErrored] === undefined) {
this[kErrored] = true;
Copy link
Member

Choose a reason for hiding this comment

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

Is there a specific reason to silence all errors besides the first one?

Copy link
Member

Choose a reason for hiding this comment

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

@BridgeAR: that's how streams do it, see destroy.js.

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason is that this will normally be called 4 times in case this condition is met, as a response is composed of 4 chunks.

Copy link
Member

@ronag ronag left a comment

Choose a reason for hiding this comment

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

Should probably also apply the kErrored logic got writeAfterEndNT, and maybe _implicitHeader and pipe. Maybe refactored out into a helper along the lines of errorOrDestroy?

if (callback) {
callback(err);
}
if (this.listenerCount('error') > 0 && this[kErrored] === undefined) {
Copy link
Member

Choose a reason for hiding this comment

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

What's the reason for the listener count check? We don't do this for streams?

Copy link
Member

Choose a reason for hiding this comment

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

nit, !this[kErrored]

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason is not to break a lot of tests, and keep this semver-minor.

Copy link
Member

Choose a reason for hiding this comment

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

Could you add a comment for that at least? Then maybe we can "fix" it in a semver-major in the future?

@@ -56,6 +57,7 @@ const HIGH_WATER_MARK = getDefaultHighWaterMark();
const { CRLF, debug } = common;

const kIsCorked = Symbol('isCorked');
const kErrored = Symbol('errored');
Copy link
Member

Choose a reason for hiding this comment

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

nit kErrorEmitted to more closely follow streams

Copy link
Member

@ronag ronag Aug 20, 2019

Choose a reason for hiding this comment

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

Should we initialize kErrored in the constructor? Or will that unnecessarily increase object size?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not adding that to the constructor exactly because of that. I can add it if we think it's worth adding, it's not a big deal anyway.

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good either way to me. Just checking if it was intentional.

}
if (this.listenerCount('error') > 0 && this[kErrored] === undefined) {
this[kErrored] = true;
this.emit('error', err);
Copy link
Member

@ronag ronag Aug 20, 2019

Choose a reason for hiding this comment

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

I believe this should be emitted in next tick? That's what we want in stream?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe same thing with callback?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm actually removing calling the callback here. It's not needed to fix the actual issue, and it can be done in a later step.

Copy link
Member Author

Choose a reason for hiding this comment

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

We cannot remove the callback or something else is going to fail. Essentially if we want to emit error here, we need to call the callback synchronously first :/.

Copy link
Member

@ronag ronag Aug 20, 2019

Choose a reason for hiding this comment

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

Something else, as in a test? Add another comment for future semver-major?

@mcollina
Copy link
Member Author

Should probably also apply the kErrored logic got writeAfterEndNT, and maybe _implicitHeader and pipe. Maybe refactored out into a helper along the lines of errorOrDestroy?

I'm not sure about that. The reason I added kErrored was because otherwise we would have 4 'error' events instead of 1 for normal operations.

@ronag
Copy link
Member

ronag commented Aug 20, 2019

I'm not sure about that.

Could you explain a little further? In Writable we only emit one error? The only reason (as far as I understand) that this is not a Writable is performance concerns. So in my opinion this should behave as similar as possible without affecting performance.

@mcollina
Copy link
Member Author

The overall reason for not doing that in this PR is to keep it as strictly semver-minor as possible, so it can be backported.

@ronag
Copy link
Member

ronag commented Aug 20, 2019

The overall reason for not doing that in this PR is to keep it as strictly semver-minor as possible, so it can be backported.

Sounds reasonable. I'm willing to make a semver-major PR in the future to make it more stream-like.

lib/_http_outgoing.js Show resolved Hide resolved
@Trott
Copy link
Member

Trott commented Aug 23, 2019

Rebased and force-pushed to get rid of conflict.

@nodejs-github-bot
Copy link
Collaborator

@mcollina mcollina force-pushed the http-outgoing-destroyed branch 2 times, most recently from e2f7aec to d2cfed1 Compare August 23, 2019 07:08
@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot
Copy link
Collaborator

@mcollina
Copy link
Member Author

CI is not flaky, windows is failing :/

Mismatched <anonymous> function calls. Expected exactly 2, actual 1.
    at Object.mustCall (c:\workspace\node-test-binary-windows-2\test\common\index.js:337:10)
    at Server.<anonymous> (c:\workspace\node-test-binary-windows-2\test\parallel\test-http-destroyed-socket-write2.js:52:26)
    at Object.onceWrapper (events.js:297:20)
    at Server.emit (events.js:209:13)
    at emitListeningNT (net.js:1262:10)
    at processTicksAndRejections (internal/process/task_queues.js:77:11)

@BridgeAR BridgeAR force-pushed the master branch 2 times, most recently from 8ae28ff to 2935f72 Compare May 31, 2020 12:19
@jasnell
Copy link
Member

jasnell commented Jul 7, 2020

Ping @mcollina and @ronag

@jasnell jasnell added the stalled Issues and PRs that are stalled. label Jul 7, 2020
@mcollina
Copy link
Member Author

mcollina commented Jul 8, 2020

@ronag if you would like to take this over please do.

@mcollina mcollina requested a review from a team as a code owner August 10, 2020 16:08
@aduh95 aduh95 added stalled Issues and PRs that are stalled. and removed stalled Issues and PRs that are stalled. labels Oct 19, 2020
@github-actions
Copy link
Contributor

This issue/PR was marked as stalled, it will be automatically closed in 30 days. If it should remain open, please leave a comment explaining why it should remain open.

@mcollina
Copy link
Member Author

@ronag wdyt? Should we revive this or should I close it?

@ronag
Copy link
Member

ronag commented Oct 23, 2020

@ronag wdyt? Should we revive this or should I close it?

@mcollina I'm on vacation next month so I hope to cleanup some of these. Thanks for the ping!

@ronag ronag self-assigned this Dec 27, 2020
@ronag
Copy link
Member

ronag commented Dec 29, 2020

@mcollina: I think this PR is no longer making correct assumptions. Streams do not emit ERR_STREAM_DESTROYED (at least not anymore) and will only provide it as an error to the callback.

If the underlying socket is prematurely destroyed (without error) that should already error the OutgoingMessage. Though, I'm not sure whether that is actually the case for ServerResponse?

What we should probably fix is:

  • Make sure the callback is invoked with ERR_STREAM_DESTROYED.
  • Have pipeline check during invocation whether any of the streams already is .destroyed and error the pipeline with ERR_STREAM_DESTROYED.

I would recommend we close this PR and make an issue for the callback and pipeline issue. Thoughts?

@ronag ronag removed their assignment Dec 29, 2020
@mcollina
Copy link
Member Author

I'll close this. Could you open the issue?

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. stalled Issues and PRs that are stalled.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants