summaryrefslogtreecommitdiff
path: root/test/parallel/test-http-agent-reuse-drained-socket-only.js
blob: 2bd53f40edaaf32142d0e16bea92bb96e88e3d10 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
'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() { }