Skip to content
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

http: remove stale timeout listeners #9440

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/_http_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,13 @@ function tickOnSocket(req, socket) {
socket.on('close', socketCloseListener);

if (req.timeout) {
socket.once('timeout', () => req.emit('timeout'));
const emitRequestTimeout = () => req.emit('timeout');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not to derail this PR which is desperately needed, but isn't this section very similar to the existing ClientRequest.prototype.setTimeout method? Would just calling req.setTimeout(req.timeout) at this point achieve almost the same behavior?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought along the same lines when I found this bug. The code implementing this feature didn't look like I expected, but I just wanted to make a small, conceptually simple fix to get it merged as quickly as possible.

It think your suggestion would work. What does @evanlucas say?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reused your unit test code and took a stab at the approach described above in #9831

The primary difference is that any timeout set applies from the time a socket is assigned to a request, i.e. it includes socket pre-connect timeouts. In existing code, if a timeout is set via ClientRequest.prototype.setTimeout, it is "reapplied" and starts counting only from the time the socket connects. Perhaps this would count as an API change.

In other HTTP libraries, the connect-timeout and the transaction-timeout are two separate parameters, e.g. in curl https://curl.haxx.se/libcurl/c/CURLOPT_CONNECTTIMEOUT.html and https://curl.haxx.se/libcurl/c/CURLOPT_TIMEOUT.html

socket.once('timeout', emitRequestTimeout);
req.once('response', (res) => {
res.once('end', () => {
socket.removeListener('timeout', emitRequestTimeout);
});
});
}
req.emit('socket', socket);
}
Expand Down
44 changes: 44 additions & 0 deletions test/parallel/test-http-client-timeout-option-listeners.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';
const common = require('../common');
const http = require('http');
const assert = require('assert');

const agent = new http.Agent({ keepAlive: true });

const server = http.createServer((req, res) => {
res.end('');
});

const options = {
agent,
method: 'GET',
port: undefined,
host: common.localhostIPv4,
path: '/',
timeout: common.platformTimeout(100)
};

server.listen(0, options.host, common.mustCall(() => {
options.port = server.address().port;
doRequest(common.mustCall((numListeners) => {
assert.strictEqual(numListeners, 1);
doRequest(common.mustCall((numListeners) => {
assert.strictEqual(numListeners, 1);
server.close();
agent.destroy();
}));
}));
}));

function doRequest(cb) {
http.request(options, common.mustCall((response) => {
const sockets = agent.sockets[`${options.host}:${options.port}:`];
assert.strictEqual(sockets.length, 1);
const socket = sockets[0];
const numListeners = socket.listeners('timeout').length;
response.resume();
response.once('end', common.mustCall(() => {
process.nextTick(cb, numListeners);
}));
})).end();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd write the test in simple callback style. Less chance of accidentally swallowing errors and easier to debug because you can simply assert.strictEqual(sockets.length, 1) instead of calling reject().