-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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: suppress data event if req aborted #13260
Conversation
Failed on Linux-One: https://ci.nodejs.org/job/node-test-commit-linuxone/6210/nodes=rhel72-s390x/console not ok 461 parallel/test-http-abort-stream-end
---
duration_ms: 0.108
severity: fail
stack: |-
assert.js:60
throw new errors.AssertionError({
^
AssertionError [ERR_ASSERTION]: got data after abort
at IncomingMessage.<anonymous> (/data/iojs/build/workspace/node-test-commit-linuxone/nodes/rhel72-s390x/test/parallel/test-http-abort-stream-end.js:46:7)
at emitOne (events.js:115:13)
at IncomingMessage.emit (events.js:210:7)
at IncomingMessage.Readable.read (_stream_readable.js:462:10)
at flow (_stream_readable.js:833:34)
at resume_ (_stream_readable.js:815:3)
at _combinedTickCallback (internal/process/next_tick.js:102:11)
at process._tickCallback (internal/process/next_tick.js:161:9) It may be challenging to get this to work on all platforms, but who knows, let's see how the rest of the CI results are when it's done running.... |
Lots of failures on Linux. The pattern seems to be that it passes on older variants (CentOS 5, Fedora 22, Ubuntu 12-04) but fails on newer variants (CentOS 6 and 7; Fedora 23, 24, and 25; Ubuntu 14-04 and 16-04). |
@Trott Thanks for the investigation. My next step is to use Ubuntu 14-04 or Ubuntu 16-4 to recreate the failure. Or do you have any suggestion? |
If you're able to do that, then that seems like a good way to go. /cc @nodejs/testing in case anyone else has additional suggestions or ideas about what the issue is. |
@Trott I can only use docker version at this moment. I don't know if it could recreate the failure. However, that's the only thing I can try now (it's holiday and the lab is shutdown for power maintenance |
I am able to recreate the failure in ubuntu 16.04 docker env. However, need time to trace the IncomingMessage/Stream/Readable. Currently, in ubuntu16.04, the ReadableState for IncomingMessage is: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer:
BufferList {
head: { data: <Buffer 78 32 38 35>, next: [Object] },
tail: { data: <Buffer 78 35 31 31>, next: null },
length: 227 },
length: 908,
pipes: null,
pipesCount: 0,
flowing: true,
ended: false,
endEmitted: false,
reading: true,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: false,
decoder: null,
encoding: null } In OSX, the ReadableState is: ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: BufferList { head: null, tail: null, length: 0 },
length: 0,
pipes: null,
pipesCount: 0,
flowing: true,
ended: false,
endEmitted: false,
reading: false,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
destroyed: false,
defaultEncoding: 'utf8',
awaitDrain: 0,
readingMore: true,
decoder: null,
encoding: null } Need time to figure out why they are different. |
dc81531
to
24fd5ed
Compare
@Trott the problem is in some platforms, the data are received after calling request.abort(). Therefore, I added extra checking in |
24fd5ed
to
0897a46
Compare
CI 2: https://ci.nodejs.org/job/node-test-commit/10206/ EDIT: CI is green |
lib/_stream_readable.js
Outdated
@@ -458,6 +458,9 @@ Readable.prototype.read = function(n) { | |||
endReadable(this); | |||
} | |||
|
|||
//drop data if _dumped flag exists |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
- //drop data if _dumped flag exists
+ // Drop data if _dumped flag exists.
So at the moment your Git author name and email address are set to:
People usually choose to use their full names for commits. To set your name globally you can do: git config --global user.name "Yihong Wang" To change the author for a single commit you can do: git commit --amend --author="Yihong Wang <[email protected]>"
git push --force-with-lease If you don't want to have your name against this commit, that's fine too. |
cc/ @nodejs/streams |
0897a46
to
377817d
Compare
Thanks @gibfahn I updated the commit and also my name. |
377817d
to
3714927
Compare
Nice! Since this has evolved from a test fix to an actual fix for http/streams on some platforms, the commit title (and PR title) should probably be updated to something like: |
3714927
to
5e0bdb7
Compare
lib/_stream_readable.js
Outdated
@@ -458,6 +458,9 @@ Readable.prototype.read = function(n) { | |||
endReadable(this); | |||
} | |||
|
|||
// Drop data if _dumped flag exists |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the _dumped
flag only exists in http
, maybe mention that in the comment too so that it's clear that this bit of code is explicitly for http
requests and what _dumped
means.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
modified the comment and saying:
// Drop data if '_dumped' flag exists. This is for IncomingMessage.
// When ClientRequest.abort() is called, the '_dumped' flag of IncomingMessage
// is set to 'true'. Then ClientRequest shouldn't receive any
// 'data' event.
/cc @nodejs/streams @nodejs/http |
5e0bdb7
to
3077166
Compare
@Trott thanks for the comments. I updated the commit/pr title and revised the comments in code. |
lib/_stream_readable.js
Outdated
@@ -458,6 +458,12 @@ Readable.prototype.read = function(n) { | |||
endReadable(this); | |||
} | |||
|
|||
// Drop data if '_dumped' flag exists. This is for IncomingMessage. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to be nit pick here, but maybe http.IncomingMessage
just to make it clear that it's http
-specific?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing! and I updated the comments. :-)
5a348e1
to
af1ecc5
Compare
lib/_stream_readable.js
Outdated
// When http.ClientRequest.abort() is called, the '_dumped' flag of | ||
// http.IncomingMessage is set to 'true'. Then http.ClientRequest | ||
// shouldn't receive any 'data' event. | ||
if (this._dumped === true) ret = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really -1 with this change, as it introduces more coupling between http and streams. Can you move this in http.IncomingMessage? If it is not possible, we should think about adding some public facing API to support this usecase. Where is this._dumped written?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_dumped
is written from here: lib/_http_incoming.js and it's also been used here: lib/_http_common.js. I am also curious about when this flag: _dumped
is introduced. Could this flag be replaced by any of the flag in ReadableState
? or move this flag into the ReadableState
?
If we really need this, couldn't Both of those solutions would avoid the need to make changes to |
04a6791
to
b62e37a
Compare
@mcollina Based on your suggestion, I modified my change as following:
@mscdex This also reduce the efforts to handle the buffered data. No need to monkey patching the |
lib/_http_incoming.js
Outdated
@@ -315,6 +315,8 @@ IncomingMessage.prototype._dump = function _dump() { | |||
if (!this._dumped) { | |||
this._dumped = true; | |||
this.resume(); | |||
// Flush the existing buffered data | |||
this._readableState._flush(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I doubt this will go over well either since there is a recent movement to avoid reaching directly into _*State
stream properties from outside streams.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. However, inside the http.IncomingMessage
, it still access _*State
in a few spots. Maybe the movement could do them all together later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok if you move the code you put in _flush()
here, if it is needed help solve a bug.
At the same time, it's better to open a feature request for a new method in the stream API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the general idea is to at least avoid incorporating new direct uses of _*State
. Kicking the can down the road doesn't seem like a good idea IMHO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mscdex I do not want to rush adding a new method to an already complex API to fix a bug either. I'm ok in adding this, just not as a bug fix, as it will need to be reasoned upon for a bit. If this can wait, I'm ok following that path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mcollina and @mscdex Thanks for the comments. If the access of _*State
would be removed, I guess, at least there should be new APIs in Readable
for those _*State
access. @mcollina Should I move the _flush()
to Readable
. The flush like API may be needed in some other cases. I think this is related to request.abort()
. Currently, it doesn't work in some platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we just do not remove the 'data'
handlers in _dump()
:
+ this.removeAllListeners('data');
this.resume();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mcollina yes, it works. However, it won't work for the code that call req.abort()
and followed by a res.on('data', cb);
. Without flush the buffered data or explicitly suppress the 'data' event , there is the chance that someone who registers the 'data' event later gets notifications. Personally, I prefer the flush approach. It not only solve the issue but also reduce the efforts to handle the buffered data if we only suppress 'data' event. I turned on the debug trace and found it spent many event loops in handling the buffered data if we only suppress 'data' event. Flush the buffered has better performance.
For removeAllListeners('data')
, I am fine with the change if it impacts less.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the probability of someone adding a 'data'
handler after calling req.abort()
is pretty low.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
b62e37a
to
d0bbdfb
Compare
lib/_http_incoming.js
Outdated
@@ -314,6 +314,9 @@ function _addHeaderLine(field, value, dest) { | |||
IncomingMessage.prototype._dump = function _dump() { | |||
if (!this._dumped) { | |||
this._dumped = true; | |||
// If there is buffered data , it may trigger 'data' event. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nits:
s/data ,/data,/
s/trigger 'data' event/trigger 'data' events/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mscdex updated. Thanks..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. I think we can merge this semver-minor, but with no backport (we might want to revise that). We also need a CITGM run:
https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/846/
Re-enable test-http-abort-stream-end and put it into parallel category. Use system random port when calling server.listen() and fix eslint errors. After calling request.abort(), in order to avoid the buffered data to trigger the 'data' event, explicitly remove 'data' event listeners.
d0bbdfb
to
a3c64e2
Compare
@yhwang , @mcollina I killed https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/nodes=osx1010/846/ since it was stuck (and also uninformative because of #13305) |
I've taken the liberty to rerun CITGM rebased on the |
CITGM looks clean. |
LGTM |
Landed as 716e9e0 |
Re-enable test-http-abort-stream-end and put it into parallel category. Use system random port when calling server.listen() and fix eslint errors. After calling request.abort(), in order to avoid the buffered data to trigger the 'data' event, explicitly remove 'data' event listeners. PR-URL: #13260 Reviewed-By: Brian White <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
@mcollina should this be backported to Node 6? |
Yes, after the usual period in current, maybe even a bit more. |
Thanks for the contribution, @yhwang! 🎉 |
Thank you all and I will open a new feature request in the stream to flush the buffered data. My pleasure to work with you all! |
Re-enable test-http-abort-stream-end and put it into parallel category. Use system random port when calling server.listen() and fix eslint errors. After calling request.abort(), in order to avoid the buffered data to trigger the 'data' event, explicitly remove 'data' event listeners. PR-URL: #13260 Reviewed-By: Brian White <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
Re-enable test-http-abort-stream-end and put it into parallel
category. Use system random port when calling server.listen()
and fix eslint errors.
Since request.abort() turns on the '_dumped' flag in the
IncomingMessage, Readable.prototype.read() needs to check
the '_dumped' flag before calling the emit('data').
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
test, streams