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

src: unconsume stream fix #11015

Closed
wants to merge 1 commit into from
Closed

Conversation

Kasher
Copy link
Contributor

@Kasher Kasher commented Jan 26, 2017

When emitting a connection event on a httpServer, the function connectionListener is called.
Then, a new parser is created, and consume method is called on the
socket's externalStream. However, if this stream was already consumed and unconsumed,
the process crashes with a cpp assert from the Consume method in
stream_base.h. This commit makes sure that no SIGABRT will be raised
and the process will stay alive (after emitting the socket).

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

src

Issue: #11017

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. http_parser Issues and PRs related to the HTTP Parser dependency or the http_parser binding. lts-watch-v6.x labels Jan 26, 2017
@Kasher Kasher changed the title src: unconsme stream fix src: unconsume stream fix Jan 26, 2017
@Kasher Kasher force-pushed the stream_base_unconsume branch from 91c2f70 to bc8ea9d Compare January 26, 2017 13:04
@Fishrock123
Copy link
Contributor

R= @indutny probably?

@Fishrock123 Fishrock123 requested a review from indutny January 26, 2017 16:07
@indutny
Copy link
Member

indutny commented Jan 26, 2017

I think we'd rather want to handle it from JS. We should mark stream as previously consumed and fallback to slow .on('data') case.

Does this make sense?

@Kasher
Copy link
Contributor Author

Kasher commented Jan 26, 2017

@indutny - Thanks for your response.
May I ask why is it better to handle this from JS?
I guess it would add some state on the stream itself, and it may be easily misused in the future (if someone uses stream in other scenarios)...... I believe that when calling to "unconsume" and resetting the stream's callbacks (in node_http_parser.cc), it should reset the stream's entire state, and make it ready to be re-consumed.
But, again, I'm not too familiar with the considerations or difficulties you had while writing this code, so please correct me if I'm wrong 👍

@indutny
Copy link
Member

indutny commented Jan 26, 2017

@Kasher this C++ API needs some serious renovation and this is a separate question, in my opinion.

I suggested to do it in JS, because this bug will still manifest itself if anyone would try to emit('connection', req.connection). Fixing it in JS will make your test case work as well as many other possible variations.

@Kasher
Copy link
Contributor Author

Kasher commented Jan 26, 2017

@indutny - Cool. I got it.
Just one last question before I'm implementing this, in order to make sure I got it correctly.
I saw in node_http_parser.cc::Consume that in addition to calling "Consume" on the stream, some callbacks are being set (allocation callback and read callback). Those callbacks are then called with a context, which is the instance of the parser object.
If we remove the call to Consume in _http_server.js::connectionListener, those callbacks won't ever be set with the right context. If, however, we call to Consume, the process will crash because of the assertion in StreamBase.

How should I implement your suggestion? Should I keep the same parser for the socket in _http_server.js, and never free\unconsume it (which means some changes in the function onParserExecuteCommon in _http_server.js)?
Or, should I just skip the call to Consume in _http_server.js::connectionListener if the stream was consumed already, and as a result the allocation callback and the read callback will be called with the wrong context (an instance of a different parser)?
Or, should we add a third c++ method that only sets that callbacks and doesn't call to StreamBase::Consume, and we'll call this method in _http_server.js::connectionListener if we detect that the stream was consumed?

Thanks a lot!!

@mscdex mscdex added the http Issues or PRs related to the http subsystem. label Jan 26, 2017
@indutny
Copy link
Member

indutny commented Jan 26, 2017

@Kasher there is a fallback mode there when it checks for _externalStream:

node/lib/_http_server.js

Lines 331 to 335 in a67a04d

var external = socket._handle._externalStream;
if (external) {
parser._consumed = true;
parser.consume(external);
}
. It would be best to check if the socket was ever consumed and never enter that clause if it was.

This won't affect context of callbacks as the attached http parser (if any) will still be alive until unconsume is called.

@Kasher
Copy link
Contributor Author

Kasher commented Jan 27, 2017

@indutny Thanks again for your help.
Maybe I'm missing something, but I'm having trouble understanding how will your suggestion work.
As I wrote before, and as you can see from the following image:

My 'connect' handler is being called from the function onParserExecuteCommon in _http_server.js :

server.emit(eventName, req, socket, bodyHead);

A few lines before my handler is called, there is a call to unconsume on the attached http-parser:

unconsume(parser, socket);

Hence, when my connect handler is called, there is no http-parser attached to the socket. If I remove the call to consume as you suggested, no parser will be attached at all, and no callbacks will be set to the socket's _externalStream.
Do you suggest to remove the call to unconsume as well, so the parser will still be attached? This will result in a refactor in the onParserExecuteCommon method, since I guess we shouldn't execute the following lines as well:

node/lib/_http_server.js

Lines 450 to 456 in a67a04d

socket.removeListener('data', state.onData);
socket.removeListener('end', state.onEnd);
socket.removeListener('close', state.onClose);
unconsume(parser, socket);
parser.finish();
freeParser(parser, req, null);
parser = null;

Did I miss something?

Thanks a lot, your help is really appreciated!!!

@indutny
Copy link
Member

indutny commented Jan 27, 2017

@kosher consume() is just one way to attach socket, the fallback is to attach socketOnData event listener. I suggest that consume() should not be called if the socket was ever previously consumed (even if it is not consumed right now).

@Kasher
Copy link
Contributor Author

Kasher commented Jan 27, 2017

@indutny Thanks again.
I've updated my commit, and I would really appreciate any other comments 👍

Copy link
Member

@indutny indutny left a comment

Choose a reason for hiding this comment

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

Few style nits, otherwise looks good!

parser._consumed = true;
parser.consume(external);
// We only consume the socket if it has never been consumed before.
if (!socket._handle._consumed) {
Copy link
Member

Choose a reason for hiding this comment

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

Minor style nit:

var external = socket._handle._externalStream;
if (!socket._handle._consumed && external) {
  ...
}

Less indent - awesome 😉

@Kasher Kasher force-pushed the stream_base_unconsume branch from cd3f670 to 1701267 Compare January 28, 2017 10:37
When emitting a 'connection' event on a httpServer, the function connectionListener is called.
Then, a new parser is created, and 'consume' method is called on the
socket's externalStream. However, if this stream was already consumed and unconsumed,
the process crashes with a cpp assert from the 'Consume' method in
stream_base.h. This commit makes sure that no SIGABRT will be raised
and the process will stay alive (after emitting the socket).
@joyeecheung
Copy link
Member

Looks like it's ready for a CI: https://ci.nodejs.org/job/node-test-pull-request/6103/

@Kasher
Copy link
Contributor Author

Kasher commented Jan 31, 2017

@indutny @joyeecheung

The linux tester failed for fedora24 - 32bit on the test sequential/test-child-process-pass-fd
Do you think it is related to my changes?
I've tried to run this test like 10 times, and it always passes (I'm testing it on ubuntu 14.04).
This test did pass for fedora24 - 64bit though.

So, just to verify - is this by any chance a known issue? If it isn't - I'll download the appropriate iso and create a VM to test this.

Thanks.

@joyeecheung
Copy link
Member

Yeah it's a known issue(#11041), also the arm failure is actually not a failure, it's related to some infra issue so it's actually green in the webpage. I'll say CI is green for this PR.

@Kasher
Copy link
Contributor Author

Kasher commented Jan 31, 2017

@joyeecheung Awesome, thanks!

jasnell pushed a commit that referenced this pull request Feb 2, 2017
When emitting a 'connection' event on a httpServer, the function
connectionListener is called. Then, a new parser is created, and
'consume' method is called on the socket's externalStream. However,
if this stream was already consumed and unconsumed, the process
crashes with a cpp assert from the 'Consume' method in stream_base.h.
This commit makes sure that no SIGABRT will be raised and the process
will stay alive (after emitting the socket).

PR-URL: #11015
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
@jasnell
Copy link
Member

jasnell commented Feb 2, 2017

Landed in 3e3bfc5

@jasnell jasnell closed this Feb 2, 2017
italoacasas pushed a commit that referenced this pull request Feb 3, 2017
When emitting a 'connection' event on a httpServer, the function
connectionListener is called. Then, a new parser is created, and
'consume' method is called on the socket's externalStream. However,
if this stream was already consumed and unconsumed, the process
crashes with a cpp assert from the 'Consume' method in stream_base.h.
This commit makes sure that no SIGABRT will be raised and the process
will stay alive (after emitting the socket).

PR-URL: #11015
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
italoacasas pushed a commit to italoacasas/node that referenced this pull request Feb 14, 2017
When emitting a 'connection' event on a httpServer, the function
connectionListener is called. Then, a new parser is created, and
'consume' method is called on the socket's externalStream. However,
if this stream was already consumed and unconsumed, the process
crashes with a cpp assert from the 'Consume' method in stream_base.h.
This commit makes sure that no SIGABRT will be raised and the process
will stay alive (after emitting the socket).

PR-URL: nodejs#11015
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
krydos pushed a commit to krydos/node that referenced this pull request Feb 25, 2017
When emitting a 'connection' event on a httpServer, the function
connectionListener is called. Then, a new parser is created, and
'consume' method is called on the socket's externalStream. However,
if this stream was already consumed and unconsumed, the process
crashes with a cpp assert from the 'Consume' method in stream_base.h.
This commit makes sure that no SIGABRT will be raised and the process
will stay alive (after emitting the socket).

PR-URL: nodejs#11015
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
jasnell pushed a commit that referenced this pull request Mar 7, 2017
When emitting a 'connection' event on a httpServer, the function
connectionListener is called. Then, a new parser is created, and
'consume' method is called on the socket's externalStream. However,
if this stream was already consumed and unconsumed, the process
crashes with a cpp assert from the 'Consume' method in stream_base.h.
This commit makes sure that no SIGABRT will be raised and the process
will stay alive (after emitting the socket).

PR-URL: #11015
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
jasnell pushed a commit that referenced this pull request Mar 7, 2017
When emitting a 'connection' event on a httpServer, the function
connectionListener is called. Then, a new parser is created, and
'consume' method is called on the socket's externalStream. However,
if this stream was already consumed and unconsumed, the process
crashes with a cpp assert from the 'Consume' method in stream_base.h.
This commit makes sure that no SIGABRT will be raised and the process
will stay alive (after emitting the socket).

PR-URL: #11015
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
MylesBorins pushed a commit that referenced this pull request Mar 9, 2017
When emitting a 'connection' event on a httpServer, the function
connectionListener is called. Then, a new parser is created, and
'consume' method is called on the socket's externalStream. However,
if this stream was already consumed and unconsumed, the process
crashes with a cpp assert from the 'Consume' method in stream_base.h.
This commit makes sure that no SIGABRT will be raised and the process
will stay alive (after emitting the socket).

PR-URL: #11015
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
@MylesBorins MylesBorins mentioned this pull request Mar 9, 2017
MylesBorins pushed a commit that referenced this pull request Mar 9, 2017
When emitting a 'connection' event on a httpServer, the function
connectionListener is called. Then, a new parser is created, and
'consume' method is called on the socket's externalStream. However,
if this stream was already consumed and unconsumed, the process
crashes with a cpp assert from the 'Consume' method in stream_base.h.
This commit makes sure that no SIGABRT will be raised and the process
will stay alive (after emitting the socket).

PR-URL: #11015
Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
@MylesBorins MylesBorins mentioned this pull request Mar 9, 2017
pimterry added a commit to httptoolkit/mockttp that referenced this pull request Nov 30, 2017
Node 7 does not include nodejs/node#11015.
Without this fix, the process is killed when proxying a connection
through HTTPS. We could try to work around this on our end, but node 7
is officially unsupported, and it seems more sensible to simply drop
support here entirely too.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. http_parser Issues and PRs related to the HTTP Parser dependency or the http_parser binding. http Issues or PRs related to the http subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants