mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
93e0bf9abf
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>
123 lines
4.1 KiB
JavaScript
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() { }
|