-
Notifications
You must be signed in to change notification settings - Fork 30.4k
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
http2: add missing error handlers #19986
Conversation
Attach simple error handlers in http2/core.js on sockets where they seem to be missing to avoid the process from crashing. Fixes: nodejs#19978
lib/internal/http2/core.js
Outdated
@@ -868,6 +868,7 @@ class Http2Session extends EventEmitter { | |||
|
|||
if (!socket._handle || !socket._handle._externalStream) { | |||
socket = new StreamWrap(socket); | |||
socket.on('error', () => {}); |
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 don't think we should be swallowing the error on these. Those should be forwarded on so they can be properly handled.
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.
@jasnell agreed. Should I throw
something descriptive inside the function block? Just forwarding the same error further seems counterintuitive.
Perhaps something that'd make the failure (and why exactly it happened) more obvious?
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.
@mafintosh and @mcollina ... what do you think?
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 error should be forwarded to session
.
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.
by session
, I suppose you mean the current instance of Http2Session
itself, right? I inferred that from socket[kSession] = this
If that's the case, I hope the following code does what you expected:
if (!socket._handle || !socket._handle._externalStream) {
socket = new StreamWrap(socket);
- socket.on('error', () => {});
+ socket.on('error', (err) => {
+ this.emit('error', err);
+ });
}
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.
Can you add a couple of unit tests for this behavior?
lib/internal/http2/core.js
Outdated
@@ -2526,6 +2527,8 @@ function connectionListener(socket) { | |||
'If this is a HTTP request: The server was not ' + | |||
'configured with the `allowHTTP1` option or a ' + | |||
'listener for the `unknownProtocol` event.\n'); | |||
|
|||
socket.on('error', () => {}); |
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.
Can you move this to before the call to end()
?
lib/internal/http2/core.js
Outdated
@@ -868,6 +868,7 @@ class Http2Session extends EventEmitter { | |||
|
|||
if (!socket._handle || !socket._handle._externalStream) { | |||
socket = new StreamWrap(socket); | |||
socket.on('error', () => {}); |
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 error should be forwarded to session
.
lib/internal/http2/core.js
Outdated
@@ -2526,6 +2527,8 @@ function connectionListener(socket) { | |||
'If this is a HTTP request: The server was not ' + | |||
'configured with the `allowHTTP1` option or a ' + | |||
'listener for the `unknownProtocol` event.\n'); | |||
|
|||
socket.on('error', () => {}); |
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.
In addition to the other feedback, could we abstract this into const noop = () => {};
somewhere at the top level? Perhaps close to the emit
helper.
@apapirovski @mcollina I hope this looks good? @mcollina I will be adding the required tests by tonight. |
@mcollina 'use strict';
process.emitWarning(
'The http2 module is an experimental API.',
'ExperimentalWarning', undefined,
'See https://github.com/nodejs/http2'
);
const {
constants,
getDefaultSettings,
getPackedSettings,
getUnpackedSettings,
createServer,
createSecureServer,
connect,
Http2ServerRequest,
Http2ServerResponse
} = require('internal/http2/core');
module.exports = {
constants,
getDefaultSettings,
getPackedSettings,
getUnpackedSettings,
createServer,
createSecureServer,
connect,
Http2ServerResponse,
Http2ServerRequest
}; My point here: it neither exports Should I export the two, or should I try to trigger the above failures and errors using the currently exported |
Also, in ...
class Http2Session extends EventEmitter {
constructor(type, options, socket) {
super();
... However, in the docs Line 92 in 6376d43
type can hold.
Perhaps I could look for more stuff than just missing event arguments as a part of nodejs/help#877? |
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.
This is on its way but I don't think the current behaviour is quite correct.
@@ -868,6 +870,9 @@ class Http2Session extends EventEmitter { | |||
|
|||
if (!socket._handle || !socket._handle._externalStream) { | |||
socket = new StreamWrap(socket); | |||
socket.on('error', (err) => { |
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 not sure what the proper error handling would look like here. I think it might need to do more than emit
and it should also be aware of whether the socketOnError
already fired previously.
Maybe something like this would be more accurate:
const wrappedSocket = socket;
socket = new StreamWrap(wrappedSocket);
socket.on('error', socketOnError.bind(wrappedSocket));
This would avoid the JSStreamWrap propagating an already handled error from the underlying socket and emitting on the session twice.
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 also think this might need to handle socket.on('close')
in a similar manner. So something like:
socket.on('close', socketOnClose.bind(wrappedSocket));
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.
Proper error handling here is a simple .on('error', () => {}) as the underlying stream is already handled before hitting this switch
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.
/cc @ryzokuken
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.
Proper error handling here is a simple .on('error', () => {}) as the underlying stream is already handled before hitting this switch
That presumes that the wrap can't error itself...
(Which it can for example on line 56 in the JSStreamWrap itself, not to mention the fact that it inherits from Socket so there are likely other errors possible too.)
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.
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.
that will trigger the error twice, as the wrappedSocket already triggers socketOnClose
and socketOnError
.
I think it would be fine to trigger an error if the wrappedSocket is in objectMode immediately as that seems to be the only case where the streamwrap emits a non-forwarded error? And then socket.on('error', () => {})
the rest, as they are already handled
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.
Looking at the http2 code it seems ok to call socketOnError more than once as that simply calls destroy which should guard against that. The onClose method has a bit more going on so can't tell if that is safe to do there, but i don't think we need to forward that in either case?
so
const wrappedSocket = socket;
socket = new StreamWrap(wrappedSocket);
socket.on('error', socketOnError.bind(wrappedSocket));
?
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.
that will trigger the error twice, as the wrappedSocket already triggers socketOnClose and socketOnError.
It will just bail because socketOnError
removes the session assignment in a sync call. I'm also not sure that the error in objectMode
is the only one. The wrap is a socket in itself, it can throw other related errors.
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.
And I think you're probably right re: leaving out close
listener. So the thing posted above seems good to me.
ping @addaleax re: best ways to test the error handling on |
If it's not a part of the public API so we shouldn't really expose that type of information since it wouldn't be of any use and could be deceptive regarding its usage.
For the unknown protocol one, we could create a straightforward server, try to connect using http1 (via https) and do something like: server.on('connection', (socket) => {
socket.emit('error', new Error('connection error'));
}); then that either blows it up or doesn't. |
To test the streamwrap make a test that uses the createConnection api to return a stream that fails on the first write |
ping @addaleax |
@nodejs/http2 @nodejs/streams @addaleax PTAL |
This is waiting for a unit test to be written, see #19986 (comment) as a suggestion. |
@mcollina sorry that I totally forgot about this, will start working on the test. |
Ping @ryzokuken |
@jasnell I have lost a lot of context on this, but IIRC, there were a few disagreements regarding the nature of tests. I'll hit up @mafintosh over the course of the next week and try to get this one in, thanks! |
Ping again :-) |
Ping pong @ryzokuken :) |
Closing due to a very long inactivity. @ryzokuken please reopen or open a new PR in case you want to continue working on this. |
Attach simple error handlers in http2/core.js on sockets where they
seem to be missing to avoid the process from crashing.
Fixes: #19978
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passescc @mafintosh @nodejs/http2