timed-out.js
4.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
'use strict';
const net = require('net');
class TimeoutError extends Error {
constructor(threshold, event) {
super(`Timeout awaiting '${event}' for ${threshold}ms`);
this.name = 'TimeoutError';
this.code = 'ETIMEDOUT';
this.event = event;
}
}
const reentry = Symbol('reentry');
const noop = () => {};
module.exports = (request, delays, options) => {
/* istanbul ignore next: this makes sure timed-out isn't called twice */
if (request[reentry]) {
return;
}
request[reentry] = true;
let stopNewTimeouts = false;
const addTimeout = (delay, callback, ...args) => {
// An error had been thrown before. Going further would result in uncaught errors.
// See https://github.com/sindresorhus/got/issues/631#issuecomment-435675051
if (stopNewTimeouts) {
return noop;
}
// Event loop order is timers, poll, immediates.
// The timed event may emit during the current tick poll phase, so
// defer calling the handler until the poll phase completes.
let immediate;
const timeout = setTimeout(() => {
immediate = setImmediate(callback, delay, ...args);
/* istanbul ignore next: added in node v9.7.0 */
if (immediate.unref) {
immediate.unref();
}
}, delay);
/* istanbul ignore next: in order to support electron renderer */
if (timeout.unref) {
timeout.unref();
}
const cancel = () => {
clearTimeout(timeout);
clearImmediate(immediate);
};
cancelers.push(cancel);
return cancel;
};
const {host, hostname} = options;
const timeoutHandler = (delay, event) => {
request.emit('error', new TimeoutError(delay, event));
request.once('error', () => {}); // Ignore the `socket hung up` error made by request.abort()
request.abort();
};
const cancelers = [];
const cancelTimeouts = () => {
stopNewTimeouts = true;
cancelers.forEach(cancelTimeout => cancelTimeout());
};
request.once('error', cancelTimeouts);
request.once('response', response => {
response.once('end', cancelTimeouts);
});
if (delays.request !== undefined) {
addTimeout(delays.request, timeoutHandler, 'request');
}
if (delays.socket !== undefined) {
const socketTimeoutHandler = () => {
timeoutHandler(delays.socket, 'socket');
};
request.setTimeout(delays.socket, socketTimeoutHandler);
// `request.setTimeout(0)` causes a memory leak.
// We can just remove the listener and forget about the timer - it's unreffed.
// See https://github.com/sindresorhus/got/issues/690
cancelers.push(() => request.removeListener('timeout', socketTimeoutHandler));
}
if (delays.lookup !== undefined && !request.socketPath && !net.isIP(hostname || host)) {
request.once('socket', socket => {
/* istanbul ignore next: hard to test */
if (socket.connecting) {
const cancelTimeout = addTimeout(delays.lookup, timeoutHandler, 'lookup');
socket.once('lookup', cancelTimeout);
}
});
}
if (delays.connect !== undefined) {
request.once('socket', socket => {
/* istanbul ignore next: hard to test */
if (socket.connecting) {
const timeConnect = () => addTimeout(delays.connect, timeoutHandler, 'connect');
if (request.socketPath || net.isIP(hostname || host)) {
socket.once('connect', timeConnect());
} else {
socket.once('lookup', error => {
if (error === null) {
socket.once('connect', timeConnect());
}
});
}
}
});
}
if (delays.secureConnect !== undefined && options.protocol === 'https:') {
request.once('socket', socket => {
/* istanbul ignore next: hard to test */
if (socket.connecting) {
socket.once('connect', () => {
const cancelTimeout = addTimeout(delays.secureConnect, timeoutHandler, 'secureConnect');
socket.once('secureConnect', cancelTimeout);
});
}
});
}
if (delays.send !== undefined) {
request.once('socket', socket => {
const timeRequest = () => addTimeout(delays.send, timeoutHandler, 'send');
/* istanbul ignore next: hard to test */
if (socket.connecting) {
socket.once('connect', () => {
request.once('upload-complete', timeRequest());
});
} else {
request.once('upload-complete', timeRequest());
}
});
}
if (delays.response !== undefined) {
request.once('upload-complete', () => {
const cancelTimeout = addTimeout(delays.response, timeoutHandler, 'response');
request.once('response', cancelTimeout);
});
}
};
module.exports.TimeoutError = TimeoutError;