-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
http: destroy timeout socket by Agent #23752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,10 @@ const util = require('util'); | |
const EventEmitter = require('events'); | ||
const debug = util.debuglog('http'); | ||
const { async_id_symbol } = require('internal/async_hooks').symbols; | ||
const errors = require('internal/errors'); | ||
const { | ||
ERR_HTTP_SOCKET_TIMEOUT, | ||
} = errors.codes; | ||
|
||
// New Agent code. | ||
|
||
|
@@ -268,6 +272,24 @@ function installListeners(agent, s, options) { | |
} | ||
s.on('close', onClose); | ||
|
||
function onTimeout() { | ||
debug('CLIENT socket onTimeout after', s.timeout, 'ms'); | ||
const name = agent.getName(options); | ||
// If free socket timeout, must remove it from freeSockets list immediately | ||
// to prevent new requests from being sent through this timeout socket. | ||
if (agent.freeSockets[name] && agent.freeSockets[name].indexOf(s) !== -1) { | ||
s.destroy(); | ||
agent.removeSocket(s, options); | ||
debug('CLIENT free socket destroy'); | ||
} else if (s.listeners('timeout').length === 1) { | ||
// No req timeout handler, agent must destroy the socket. | ||
ronag marked this conversation as resolved.
Show resolved
Hide resolved
|
||
s.destroy(new ERR_HTTP_SOCKET_TIMEOUT()); | ||
agent.removeSocket(s, options); | ||
debug('CLIENT active socket destroy'); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think just doing: s.destroy(new ERR_HTTP_SOCKET_TIMEOUT()); should be enough now in master |
||
s.on('timeout', onTimeout); | ||
|
||
function onRemove() { | ||
// We need this function for cases like HTTP 'upgrade' | ||
// (defined by WebSockets) where we need to remove a socket from the | ||
|
@@ -276,6 +298,7 @@ function installListeners(agent, s, options) { | |
agent.removeSocket(s, options); | ||
s.removeListener('close', onClose); | ||
s.removeListener('free', onFree); | ||
s.removeListener('timeout', onTimeout); | ||
s.removeListener('agentRemove', onRemove); | ||
} | ||
s.on('agentRemove', onRemove); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
|
||
const common = require('../common'); | ||
const onGC = require('../common/ongc'); | ||
const assert = require('assert'); | ||
|
||
function serverHandler(req, res) { | ||
setTimeout(function() { | ||
|
@@ -38,6 +39,11 @@ function getall() { | |
req.setTimeout(10, function() { | ||
console.log('timeout (expected)'); | ||
}); | ||
req.on('error', (err) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a break change, now timeout request will emit |
||
// only allow Socket timeout error | ||
assert.strictEqual(err.code, 'ERR_SOCKET_TIMEOUT'); | ||
assert.strictEqual(err.message, 'Socket timeout'); | ||
}); | ||
|
||
count++; | ||
onGC(req, { ongc }); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
'use strict'; | ||
const common = require('../common'); | ||
const assert = require('assert'); | ||
const http = require('http'); | ||
|
||
const server = http.createServer(common.mustCall((req, res) => { | ||
server.close(); | ||
// do nothing, wait client socket timeout and close | ||
})); | ||
|
||
server.listen(0, common.mustCall(() => { | ||
const agent = new http.Agent({ timeout: 100 }); | ||
const req = http.get({ | ||
path: '/', | ||
port: server.address().port, | ||
timeout: 50, | ||
agent | ||
}, common.mustNotCall()) | ||
.on('error', common.mustCall((err) => { | ||
assert.strictEqual(err.message, 'socket hang up'); | ||
assert.strictEqual(err.code, 'ECONNRESET'); | ||
})); | ||
req.on('timeout', common.mustCall(() => { | ||
req.abort(); | ||
})); | ||
})); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
'use strict'; | ||
const common = require('../common'); | ||
const assert = require('assert'); | ||
const http = require('http'); | ||
|
||
const server = http.createServer(common.mustCall((req, res) => { | ||
server.close(); | ||
// do nothing, wait client socket timeout and close | ||
})); | ||
|
||
server.listen(0, common.mustCall(() => { | ||
let req; | ||
const timer = setTimeout(() => { | ||
req.abort(); | ||
fengmk2 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assert.fail('should not timeout here'); | ||
}, 100); | ||
|
||
const agent = new http.Agent({ timeout: 50 }); | ||
req = http.get({ | ||
path: '/', | ||
port: server.address().port, | ||
agent | ||
}, common.mustNotCall()) | ||
.on('error', common.mustCall((err) => { | ||
clearTimeout(timer); | ||
assert.strictEqual(err.message, 'Socket timeout'); | ||
assert.strictEqual(err.code, 'ERR_HTTP_SOCKET_TIMEOUT'); | ||
})); | ||
})); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
'use strict'; | ||
const common = require('../common'); | ||
const assert = require('assert'); | ||
const http = require('http'); | ||
|
||
const server = http.createServer(common.mustCallAtLeast((req, res) => { | ||
const content = 'Hello Agent'; | ||
res.writeHead(200, { | ||
'Content-Length': content.length.toString(), | ||
}); | ||
res.write(content); | ||
res.end(); | ||
}, 2)); | ||
|
||
server.listen(0, common.mustCall(() => { | ||
const agent = new http.Agent({ timeout: 100, keepAlive: true }); | ||
const req = http.get({ | ||
path: '/', | ||
port: server.address().port, | ||
agent | ||
}, common.mustCall((res) => { | ||
assert.strictEqual(res.statusCode, 200); | ||
res.resume(); | ||
res.on('end', common.mustCall()); | ||
})); | ||
|
||
const timer = setTimeout(() => { | ||
assert.fail('should not timeout here'); | ||
req.abort(); | ||
}, 1000); | ||
|
||
req.on('socket', common.mustCall((socket) => { | ||
// wait free socket become free and timeout | ||
socket.on('timeout', common.mustCall(() => { | ||
// free socket should be destroyed | ||
assert.strictEqual(socket.writable, false); | ||
// send new request will be fails | ||
clearTimeout(timer); | ||
const newReq = http.get({ | ||
path: '/', | ||
port: server.address().port, | ||
agent | ||
}, common.mustCall((res) => { | ||
// agent must create a new socket to handle request | ||
assert.notStrictEqual(newReq.socket, socket); | ||
assert.strictEqual(res.statusCode, 200); | ||
res.resume(); | ||
res.on('end', common.mustCall(() => server.close())); | ||
})); | ||
})); | ||
})); | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mcollina change to
ERR_HTTP_SOCKET_TIMEOUT
and add update onrequest.setTimeout()
.