diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index aef7f3e6ec88e4..ce1fe0848a7d88 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -1217,6 +1217,13 @@ Emitted when a stream is created on the client. Emitted when a stream is started on the client. +`http2.client.stream.error` + +* `stream` {ClientHttp2Stream} +* `error` {Error} + +Emitted when an error occurs during the processing of a stream on the client. + #### Modules > Stability: 1 - Experimental diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index ce9f81bebc2494..47c0cb4a9a85cc 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -187,6 +187,7 @@ const { _connectionListener: httpConnectionListener } = http; const dc = require('diagnostics_channel'); const onClientStreamCreatedChannel = dc.channel('http2.client.stream.created'); const onClientStreamStartChannel = dc.channel('http2.client.stream.start'); +const onClientStreamErrorChannel = dc.channel('http2.client.stream.error'); let debug = require('internal/util/debuglog').debuglog('http2', (fn) => { debug = fn; @@ -2424,6 +2425,14 @@ class Http2Stream extends Duplex { setImmediate(() => { session[kMaybeDestroy](); }); + if (err && + session[kType] === NGHTTP2_SESSION_CLIENT && + onClientStreamErrorChannel.hasSubscribers) { + onClientStreamErrorChannel.publish({ + stream: this, + error: err, + }); + } callback(err); } // The Http2Stream can be destroyed if it has closed and if the readable diff --git a/test/parallel/test-diagnostics-channel-http2-client-stream-error.js b/test/parallel/test-diagnostics-channel-http2-client-stream-error.js new file mode 100644 index 00000000000000..14e745c0b305c2 --- /dev/null +++ b/test/parallel/test-diagnostics-channel-http2-client-stream-error.js @@ -0,0 +1,46 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +// This test ensures that the built-in HTTP/2 diagnostics channels are reporting +// the diagnostics messages for the 'http2.client.stream.error' channel when +// an error occurs during the processing of a ClientHttp2Stream. + +const assert = require('assert'); +const dc = require('diagnostics_channel'); +const http2 = require('http2'); +const { Duplex } = require('stream'); + +dc.subscribe('http2.client.stream.error', common.mustCall(({ stream, error }) => { + // Since ClientHttp2Stream is not exported from any module, this just checks + // if the stream is an instance of Duplex and the constructor name is + // 'ClientHttp2Stream'. + assert.ok(stream instanceof Duplex); + assert.strictEqual(stream.constructor.name, 'ClientHttp2Stream'); + assert.strictEqual(stream.closed, true); + assert.strictEqual(stream.destroyed, true); + + assert.ok(error); + assert.strictEqual(error.code, 'ABORT_ERR'); + assert.strictEqual(error.name, 'AbortError'); +})); + +const server = http2.createServer(); +server.listen(0, common.mustCall(() => { + const port = server.address().port; + const client = http2.connect(`http://localhost:${port}`); + + const ac = new AbortController(); + const stream = client.request({}, { signal: ac.signal }); + ac.abort(); + + stream.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ABORT_ERR'); + assert.strictEqual(err.name, 'AbortError'); + + client.close(); + server.close(); + })); +}));