-
-
Notifications
You must be signed in to change notification settings - Fork 935
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Customize timeouts and generally improve the whole thing (#534)
- Refactor timed-out to optimistically defer errors until after the poll phase of the current event loop tick. - Timeouts begin and end when their respective phase of the lifecycle begins and ends. - Timeouts result in a `got.TimeoutError`
- Loading branch information
1 parent
8cccd8a
commit da4f236
Showing
8 changed files
with
401 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,131 @@ | ||
'use strict'; | ||
const net = require('net'); | ||
const {TimeoutError} = require('./errors'); | ||
|
||
// Forked from https:/floatdrop/timed-out | ||
const reentry = Symbol('reentry'); | ||
|
||
module.exports = function (req, delays) { | ||
if (req.timeoutTimer) { | ||
return req; | ||
function addTimeout(delay, callback, ...args) { | ||
// 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); | ||
if (immediate.unref) { | ||
// Added in node v9.7.0 | ||
immediate.unref(); | ||
} | ||
}, | ||
delay | ||
); | ||
timeout.unref(); | ||
return () => { | ||
clearTimeout(timeout); | ||
clearImmediate(immediate); | ||
}; | ||
} | ||
|
||
module.exports = function (req, options) { | ||
if (req[reentry]) { | ||
return; | ||
} | ||
req[reentry] = true; | ||
const {gotTimeout: delays, host, hostname} = options; | ||
const timeoutHandler = (delay, event) => { | ||
req.abort(); | ||
req.emit('error', new TimeoutError(delay, event, options)); | ||
}; | ||
const cancelers = []; | ||
const cancelTimeouts = () => { | ||
cancelers.forEach(cancelTimeout => cancelTimeout()); | ||
}; | ||
|
||
const host = req._headers ? (' to ' + req._headers.host) : ''; | ||
req.on('error', cancelTimeouts); | ||
req.once('response', response => { | ||
response.once('end', cancelTimeouts); | ||
}); | ||
|
||
function throwESOCKETTIMEDOUT() { | ||
req.abort(); | ||
const e = new Error('Socket timed out on request' + host); | ||
e.code = 'ESOCKETTIMEDOUT'; | ||
req.emit('error', e); | ||
if (delays.request !== undefined) { | ||
const cancelTimeout = addTimeout( | ||
delays.request, | ||
timeoutHandler, | ||
'request' | ||
); | ||
cancelers.push(cancelTimeout); | ||
} | ||
|
||
function throwETIMEDOUT() { | ||
req.abort(); | ||
const e = new Error('Connection timed out on request' + host); | ||
e.code = 'ETIMEDOUT'; | ||
req.emit('error', e); | ||
if (delays.socket !== undefined) { | ||
req.setTimeout( | ||
delays.socket, | ||
() => { | ||
timeoutHandler(delays.socket, 'socket'); | ||
} | ||
); | ||
} | ||
if (delays.lookup !== undefined && !req.socketPath && !net.isIP(hostname || host)) { | ||
req.once('socket', socket => { | ||
if (socket.connecting) { | ||
const cancelTimeout = addTimeout( | ||
delays.lookup, | ||
timeoutHandler, | ||
'lookup' | ||
); | ||
cancelers.push(cancelTimeout); | ||
socket.once('lookup', cancelTimeout); | ||
} | ||
}); | ||
} | ||
|
||
if (delays.connect !== undefined) { | ||
req.timeoutTimer = setTimeout(throwETIMEDOUT, delays.connect); | ||
req.once('socket', socket => { | ||
if (socket.connecting) { | ||
const timeConnect = () => { | ||
const cancelTimeout = addTimeout( | ||
delays.connect, | ||
timeoutHandler, | ||
'connect' | ||
); | ||
cancelers.push(cancelTimeout); | ||
return cancelTimeout; | ||
}; | ||
if (req.socketPath || net.isIP(hostname || host)) { | ||
socket.once('connect', timeConnect()); | ||
} else { | ||
socket.once('lookup', () => { | ||
socket.once('connect', timeConnect()); | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
if (delays.request !== undefined) { | ||
req.requestTimeoutTimer = setTimeout(() => { | ||
clear(); | ||
|
||
if (req.connection.connecting) { | ||
throwETIMEDOUT(); | ||
if (delays.send !== undefined) { | ||
req.once('socket', socket => { | ||
const timeRequest = () => { | ||
const cancelTimeout = addTimeout( | ||
delays.send, | ||
timeoutHandler, | ||
'send' | ||
); | ||
cancelers.push(cancelTimeout); | ||
return cancelTimeout; | ||
}; | ||
if (socket.connecting) { | ||
socket.once('connect', () => { | ||
req.once('upload-complete', timeRequest()); | ||
}); | ||
} else { | ||
throwESOCKETTIMEDOUT(); | ||
req.once('upload-complete', timeRequest()); | ||
} | ||
}, delays.request); | ||
} | ||
|
||
// Clear the connection timeout timer once a socket is assigned to the | ||
// request and is connected. | ||
req.on('socket', socket => { | ||
// Socket may come from Agent pool and may be already connected. | ||
if (!socket.connecting) { | ||
connect(); | ||
return; | ||
} | ||
|
||
socket.once('connect', connect); | ||
}); | ||
|
||
function clear() { | ||
if (req.timeoutTimer) { | ||
clearTimeout(req.timeoutTimer); | ||
req.timeoutTimer = null; | ||
} | ||
}); | ||
} | ||
|
||
function connect() { | ||
clear(); | ||
|
||
if (delays.socket !== undefined) { | ||
// Abort the request if there is no activity on the socket for more | ||
// than `delays.socket` milliseconds. | ||
req.setTimeout(delays.socket, throwESOCKETTIMEDOUT); | ||
} | ||
|
||
req.on('response', res => { | ||
res.on('end', () => { | ||
// The request is finished, cancel request timeout. | ||
clearTimeout(req.requestTimeoutTimer); | ||
}); | ||
if (delays.response !== undefined) { | ||
req.once('upload-complete', () => { | ||
const cancelTimeout = addTimeout( | ||
delays.response, | ||
timeoutHandler, | ||
'response' | ||
); | ||
cancelers.push(cancelTimeout); | ||
req.once('response', cancelTimeout); | ||
}); | ||
} | ||
|
||
return req.on('error', clear); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.