node/test/parallel/test-http-agent-reuse-drained-socket-only.js
ywave620 93e0bf9abf
http: reuse socket only when it is drained
Ensuring every request is assigned to a drained socket or nothing.
Because is has no benifit for a request to be attached to a non
drained socket and it prevents the request from being assigned to
a drained one, which might occur soon or already in the free pool
We achieve this by claiming a socket as free only when the socket
is drained.

PR-URL: https://github.com/nodejs/node/pull/43902
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
2022-07-27 10:09:06 +01:00

123 lines
4.1 KiB
JavaScript

'use strict';
const common = require('../common');
const assert = require('assert');
const http = require('http');
const net = require('net');
const agent = new http.Agent({
keepAlive: true,
maxFreeSockets: Infinity,
maxSockets: Infinity,
maxTotalSockets: Infinity,
});
const server = net.createServer({
pauseOnConnect: true,
}, (sock) => {
// Do not read anything from `sock`
sock.pause();
sock.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n');
});
server.listen(0, common.mustCall(() => {
sendFstReq(server.address().port);
}));
function sendFstReq(serverPort) {
const req = http.request({
agent,
host: '127.0.0.1',
port: serverPort,
}, (res) => {
res.on('data', noop);
res.on('end', common.mustCall(() => {
// Agent's socket reusing code is registered to process.nextTick(),
// and will be run after this function, make sure it take effect.
setImmediate(sendSecReq, serverPort, req.socket.localPort);
}));
});
// Make the `req.socket` non drained, i.e. has some data queued to write to
// and accept by the kernel. In Linux and Mac, we only need to call `req.end(aLargeBuffer)`.
// However, in Windows, the mechanism of acceptance is loose, the following code is a workaround
// for Windows.
/**
* https://docs.microsoft.com/en-US/troubleshoot/windows/win32/data-segment-tcp-winsock says
*
* Winsock uses the following rules to indicate a send completion to the application
* (depending on how the send is invoked, the completion notification could be the
* function returning from a blocking call, signaling an event, or calling a notification
* function, and so forth):
* - If the socket is still within SO_SNDBUF quota, Winsock copies the data from the application
* send and indicates the send completion to the application.
* - If the socket is beyond SO_SNDBUF quota and there's only one previously buffered send still
* in the stack kernel buffer, Winsock copies the data from the application send and indicates
* the send completion to the application.
* - If the socket is beyond SO_SNDBUF quota and there's more than one previously buffered send
* in the stack kernel buffer, Winsock copies the data from the application send. Winsock doesn't
* indicate the send completion to the application until the stack completes enough sends to put
* back the socket within SO_SNDBUF quota or only one outstanding send condition.
*/
req.on('socket', () => {
req.socket.on('connect', () => {
// Print tcp send buffer information
console.log(process.report.getReport().libuv.filter((handle) => handle.type === 'tcp'));
const dataLargerThanTCPSendBuf = Buffer.alloc(1024 * 1024 * 64, 0);
req.write(dataLargerThanTCPSendBuf);
req.uncork();
if (process.platform === 'win32') {
assert.ok(req.socket.writableLength === 0);
}
req.write(dataLargerThanTCPSendBuf);
req.uncork();
if (process.platform === 'win32') {
assert.ok(req.socket.writableLength === 0);
}
req.end(dataLargerThanTCPSendBuf);
assert.ok(req.socket.writableLength > 0);
});
});
}
function sendSecReq(serverPort, fstReqCliPort) {
// Make the second request, which should be sent on a new socket
// because the first socket is not drained and hence can not be reused
const req = http.request({
agent,
host: '127.0.0.1',
port: serverPort,
}, (res) => {
res.on('data', noop);
res.on('end', common.mustCall(() => {
setImmediate(sendThrReq, serverPort, req.socket.localPort);
}));
});
req.on('socket', common.mustCall((sock) => {
assert.notStrictEqual(sock.localPort, fstReqCliPort);
}));
req.end();
}
function sendThrReq(serverPort, secReqCliPort) {
// Make the third request, the agent should reuse the second socket we just made
const req = http.request({
agent,
host: '127.0.0.1',
port: serverPort,
}, noop);
req.on('socket', common.mustCall((sock) => {
assert.strictEqual(sock.localPort, secReqCliPort);
process.exit(0);
}));
}
function noop() { }