Skip to content

Commit 5ac4fb8

Browse files
authored
Fix goaway (#3835)
* fix goaway Signed-off-by: Matteo Collina <[email protected]> * fixup Signed-off-by: Matteo Collina <[email protected]> * linting Signed-off-by: Matteo Collina <[email protected]> * cleanup Signed-off-by: Matteo Collina <[email protected]> * fixup Signed-off-by: Matteo Collina <[email protected]> --------- Signed-off-by: Matteo Collina <[email protected]>
1 parent 216ec7b commit 5ac4fb8

File tree

3 files changed

+40
-22
lines changed

3 files changed

+40
-22
lines changed

lib/core/request.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ class Request {
130130
}
131131

132132
this.completed = false
133-
134133
this.aborted = false
135134

136135
this.upgrade = upgrade || null
@@ -272,6 +271,7 @@ class Request {
272271
this.onFinally()
273272

274273
assert(!this.aborted)
274+
assert(!this.completed)
275275

276276
this.completed = true
277277
if (channels.trailers.hasSubscribers) {

lib/dispatcher/client-h2.js

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -184,18 +184,19 @@ function onHttp2SessionEnd () {
184184
* @param {number} errorCode
185185
*/
186186
function onHttp2SessionGoAway (errorCode) {
187-
// We cannot recover, so best to close the session and the socket
187+
// TODO(mcollina): Verify if GOAWAY implements the spec correctly:
188+
// https://datatracker.ietf.org/doc/html/rfc7540#section-6.8
189+
// Specifically, we do not verify the "valid" stream id.
190+
188191
const err = this[kError] || new SocketError(`HTTP/2: "GOAWAY" frame received with code ${errorCode}`, util.getSocketInfo(this[kSocket]))
189192
const client = this[kClient]
190193

191194
client[kSocket] = null
192195
client[kHTTPContext] = null
193196

194-
if (this[kHTTP2Session] !== null) {
195-
this[kHTTP2Session].close()
196-
this[kHTTP2Session].destroy(err)
197-
this[kHTTP2Session] = null
198-
}
197+
// this is an HTTP2 session
198+
this.close()
199+
this[kHTTP2Session] = null
199200

200201
util.destroy(this[kSocket], err)
201202

@@ -321,13 +322,15 @@ function writeH2 (client, request) {
321322
util.errorRequest(client, request, err)
322323

323324
if (stream != null) {
325+
// Some chunks might still come after abort,
326+
// let's ignore them
327+
stream.removeAllListeners('data')
328+
324329
// On Abort, we close the stream to send RST_STREAM frame
325330
stream.close()
326-
// We delay the destroy to allow the stream to send the RST_STREAM frame
327-
queueMicrotask(() => util.destroy(stream, err))
331+
328332
// We move the running index to the next request
329-
client[kQueue][client[kRunningIdx]++] = null
330-
client[kPendingIdx] = client[kRunningIdx]
333+
client[kOnError](err)
331334
client[kResume]()
332335
}
333336

@@ -356,7 +359,7 @@ function writeH2 (client, request) {
356359
// We disabled endStream to allow the user to write to the stream
357360
stream = session.request(headers, { endStream: false, signal })
358361

359-
if (stream.id && !stream.pending) {
362+
if (!stream.pending) {
360363
request.onUpgrade(null, null, stream)
361364
++session[kOpenStreams]
362365
client[kQueue][client[kRunningIdx]++] = null
@@ -463,26 +466,31 @@ function writeH2 (client, request) {
463466
// for those scenarios, best effort is to destroy the stream immediately
464467
// as there's no value to keep it open.
465468
if (request.aborted) {
469+
stream.removeAllListeners('data')
466470
return
467471
}
468472

469473
if (request.onHeaders(Number(statusCode), parseH2Headers(realHeaders), stream.resume.bind(stream), '') === false) {
470474
stream.pause()
471475
}
476+
})
472477

473-
stream.on('data', (chunk) => {
474-
if (request.onData(chunk) === false) {
475-
stream.pause()
476-
}
477-
})
478+
stream.on('data', (chunk) => {
479+
if (request.onData(chunk) === false) {
480+
stream.pause()
481+
}
478482
})
479483

480484
stream.once('end', (err) => {
485+
stream.removeAllListeners('data')
481486
// When state is null, it means we haven't consumed body and the stream still do not have
482487
// a state.
483488
// Present specially when using pipeline or stream
484489
if (stream.state?.state == null || stream.state.state < 6) {
485-
request.onComplete([])
490+
// Do not complete the request if it was aborted
491+
if (!request.aborted) {
492+
request.onComplete([])
493+
}
486494

487495
client[kQueue][client[kRunningIdx]++] = null
488496
client[kResume]()
@@ -503,6 +511,7 @@ function writeH2 (client, request) {
503511
})
504512

505513
stream.once('close', () => {
514+
stream.removeAllListeners('data')
506515
session[kOpenStreams] -= 1
507516
if (session[kOpenStreams] === 0) {
508517
session.unref()

test/http2.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,12 +1621,21 @@ test('#3753 - Handle GOAWAY Gracefully', async (t) => {
16211621
'x-my-header': 'foo'
16221622
}
16231623
}, (err, response) => {
1624-
if (i === 9 || i === 8) {
1625-
t.strictEqual(err?.message, 'HTTP/2: "GOAWAY" frame received with code 0')
1626-
t.strictEqual(err?.code, 'UND_ERR_SOCKET')
1624+
if (err) {
1625+
t.strictEqual(err.message, 'HTTP/2: "GOAWAY" frame received with code 0')
1626+
t.strictEqual(err.code, 'UND_ERR_SOCKET')
16271627
} else {
1628-
t.ifError(err)
16291628
t.strictEqual(response.statusCode, 200)
1629+
;(async function () {
1630+
let body
1631+
try {
1632+
body = await response.body.text()
1633+
} catch (err) {
1634+
t.strictEqual(err.code, 'UND_ERR_SOCKET')
1635+
return
1636+
}
1637+
t.strictEqual(body, 'hello world')
1638+
})()
16301639
}
16311640
})
16321641
}

0 commit comments

Comments
 (0)