From a59fac415ac013a48b1d514837628a5cf81d6878 Mon Sep 17 00:00:00 2001 From: Szymon Marczak <36894700+szmarczak@users.noreply.github.com> Date: Sun, 11 Apr 2021 22:12:12 +0200 Subject: [PATCH] Fix hanging promise on HTTP/2 timeout Fixes #1492 --- package.json | 2 +- source/core/index.ts | 5 +++++ source/core/options.ts | 13 ++++++++++++- test/timeout.ts | 14 ++++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ad739ab52..e5fd8c266 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "cacheable-request": "^7.0.1", "decompress-response": "^6.0.0", "get-stream": "^6.0.0", - "http2-wrapper": "^2.0.1", + "http2-wrapper": "^2.0.3", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" diff --git a/source/core/index.ts b/source/core/index.ts index a5041a579..ac8078db6 100644 --- a/source/core/index.ts +++ b/source/core/index.ts @@ -873,6 +873,11 @@ export default class Request extends Duplex implements RequestEvents { timer(request); + if (this.options.http2) { + // Unset stream timeout, as the `timeout` option was used only for connection timeout. + request.setTimeout(0); + } + this._cancelTimeouts = timedOut(request, timeout, url as URL); const responseEventName = options.cache ? 'cacheableResponse' : 'response'; diff --git a/source/core/options.ts b/source/core/options.ts index 21fc900a8..861c34450 100644 --- a/source/core/options.ts +++ b/source/core/options.ts @@ -711,6 +711,16 @@ const cloneInternals = (internals: typeof defaultInternals): typeof defaultInter return result; }; +const getHttp2TimeoutOption = (internals: typeof defaultInternals): number | undefined => { + const delays = [internals.timeout.socket, internals.timeout.connect, internals.timeout.lookup, internals.timeout.request, internals.timeout.secureConnect].filter(delay => typeof delay === 'number') as number[]; + + if (delays.length > 0) { + return Math.min(...delays); + } + + return undefined; +}; + const descriptor = { _unixOptions: { value: undefined, @@ -2119,7 +2129,8 @@ export default class Options { maxHeaderSize: internals.maxHeaderSize, localAddress: internals.localAddress, headers: internals.headers, - createConnection: internals.createConnection + createConnection: internals.createConnection, + timeout: internals.http2 ? getHttp2TimeoutOption(internals) : undefined }; } diff --git a/test/timeout.ts b/test/timeout.ts index 152b6280f..c44c7decd 100644 --- a/test/timeout.ts +++ b/test/timeout.ts @@ -763,3 +763,17 @@ test('timeouts are emitted ASAP', async t => { t.true(error.timings.phases.total! < (timeout + marginOfError)); }); + +test('http2 timeout', async t => { + await t.throwsAsync(got('https://123.123.123.123', { + timeout: { + request: 1 + }, + http2: true, + retry: { + calculateDelay: ({computedValue}) => computedValue ? 1 : 0 + } + }), { + code: 'ETIMEDOUT' + }); +});