Skip to content

Commit 7a92064

Browse files
authored
Fix timeout error firing even though it's cancelled (#2399)
1 parent f997bcb commit 7a92064

File tree

3 files changed

+26
-27
lines changed

3 files changed

+26
-27
lines changed

source/core/timed-out.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ export default function timedOut(request: ClientRequest, delays: Delays, options
5151
request[reentry] = true;
5252
const cancelers: Array<typeof noop> = [];
5353
const {once, unhandleAll} = unhandler();
54+
const handled: Map<string, boolean> = new Map<string, boolean>();
5455

5556
const addTimeout = (delay: number, callback: (delay: number, event: string) => void, event: string): (typeof noop) => {
5657
const timeout = setTimeout(callback, delay, delay, event) as unknown as NodeJS.Timeout;
5758

5859
timeout.unref?.();
5960

6061
const cancel = (): void => {
62+
handled.set(event, true);
6163
clearTimeout(timeout);
6264
};
6365

@@ -69,7 +71,13 @@ export default function timedOut(request: ClientRequest, delays: Delays, options
6971
const {host, hostname} = options;
7072

7173
const timeoutHandler = (delay: number, event: string): void => {
72-
request.destroy(new TimeoutError(delay, event));
74+
// Use setTimeout to allow for any cancelled events to be handled first,
75+
// to prevent firing any TimeoutError unneeded when the event loop is busy or blocked
76+
setTimeout(() => {
77+
if (!handled.has(event)) {
78+
request.destroy(new TimeoutError(delay, event));
79+
}
80+
}, 0);
7381
};
7482

7583
const cancelTimeouts = (): void => {

test/retry.ts

+12-14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import process from 'node:process';
21
import {EventEmitter} from 'node:events';
32
import {PassThrough as PassThroughStream} from 'node:stream';
43
import type {Socket} from 'node:net';
54
import http from 'node:http';
5+
import https from 'node:https';
66
import test from 'ava';
77
import is from '@sindresorhus/is';
88
import type {Handler} from 'express';
@@ -22,18 +22,16 @@ const handler413: Handler = (_request, response) => {
2222
response.end();
2323
};
2424

25-
const createSocketTimeoutStream = (): http.ClientRequest => {
26-
const stream = new PassThroughStream();
27-
// @ts-expect-error Mocking the behaviour of a ClientRequest
28-
stream.setTimeout = (ms, callback) => {
29-
process.nextTick(callback);
30-
};
31-
32-
// @ts-expect-error Mocking the behaviour of a ClientRequest
33-
stream.abort = () => {};
34-
stream.resume();
25+
const createSocketTimeoutStream = (url: string): http.ClientRequest => {
26+
if (url.includes('https:')) {
27+
return https.request(url, {
28+
timeout: 1,
29+
});
30+
}
3531

36-
return stream as unknown as http.ClientRequest;
32+
return http.request(url, {
33+
timeout: socketTimeout,
34+
});
3735
};
3836

3937
test('works on timeout', withServer, async (t, server, got) => {
@@ -57,7 +55,7 @@ test('works on timeout', withServer, async (t, server, got) => {
5755
}
5856

5957
knocks++;
60-
return createSocketTimeoutStream();
58+
return createSocketTimeoutStream(server.url);
6159
},
6260
})).body, 'who`s there?');
6361
});
@@ -93,7 +91,7 @@ test('setting to `0` disables retrying', async t => {
9391
return 0;
9492
},
9593
},
96-
request: () => createSocketTimeoutStream(),
94+
request: () => createSocketTimeoutStream('https://example.com'),
9795
}), {
9896
instanceOf: TimeoutError,
9997
message: `Timeout awaiting 'socket' for ${socketTimeout}ms`,

test/timeout.ts

+5-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import process from 'node:process';
22
import {EventEmitter} from 'node:events';
3-
import stream, {PassThrough as PassThroughStream} from 'node:stream';
3+
import stream from 'node:stream';
44
import {pipeline as streamPipeline} from 'node:stream/promises';
55
import http from 'node:http';
6+
import https from 'node:https';
67
import net from 'node:net';
78
import getStream from 'get-stream';
89
import test from 'ava';
@@ -90,17 +91,9 @@ test.serial('socket timeout', async t => {
9091
limit: 0,
9192
},
9293
request() {
93-
const stream = new PassThroughStream();
94-
// @ts-expect-error Mocking the behaviour of a ClientRequest
95-
stream.setTimeout = (ms, callback) => {
96-
process.nextTick(callback);
97-
};
98-
99-
// @ts-expect-error Mocking the behaviour of a ClientRequest
100-
stream.abort = () => {};
101-
stream.resume();
102-
103-
return stream as unknown as http.ClientRequest;
94+
return https.request('https://example.com', {
95+
timeout: 0,
96+
});
10497
},
10598
}),
10699
{

0 commit comments

Comments
 (0)