summaryrefslogtreecommitdiff
path: root/test/parallel/test-async-hooks-http-agent-destroy.js
blob: 637f2c511410e73bb8d667c4b4f2ba130e2813af (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
'use strict';
// Flags: --expose-internals
const common = require('../common');
const assert = require('assert');
const { async_id_symbol } = require('internal/async_hooks').symbols;
const async_hooks = require('async_hooks');
const http = require('http');

// Regression test for https://github.com/nodejs/node/issues/19859
// Checks that an http.Agent emits a destroy for the old asyncId before calling
// asyncReset()s when reusing a socket handle. The setup is nearly identical to
// parallel/test-async-hooks-http-agent (which focuses on the assertion that
// a fresh asyncId is assigned to the net.Socket instance).

const destroyedIds = new Set();
async_hooks.createHook({
  destroy: common.mustCallAtLeast((asyncId) => {
    destroyedIds.add(asyncId);
  }, 1)
}).enable();

// Make sure a single socket is transparently reused for 2 requests.
const agent = new http.Agent({
  keepAlive: true,
  keepAliveMsecs: Infinity,
  maxSockets: 1
});

const server = http.createServer(common.mustCall((req, res) => {
  req.once('data', common.mustCallAtLeast(() => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('foo');
  }));
  req.on('end', common.mustCall(() => {
    res.end('bar');
  }));
}, 2)).listen(0, common.mustCall(() => {
  const port = server.address().port;
  const payload = 'hello world';

  // First request. This is useless except for adding a socket to the
  // agent’s pool for reuse.
  const r1 = http.request({
    agent, port, method: 'POST'
  }, common.mustCall((res) => {
    // Remember which socket we used.
    const socket = res.socket;
    const asyncIdAtFirstRequest = socket[async_id_symbol];
    assert.ok(asyncIdAtFirstRequest > 0, `${asyncIdAtFirstRequest} > 0`);
    // Check that request and response share their socket.
    assert.strictEqual(r1.socket, socket);

    res.on('data', common.mustCallAtLeast(() => {}));
    res.on('end', common.mustCall(() => {
      // setImmediate() to give the agent time to register the freed socket.
      setImmediate(common.mustCall(() => {
        // The socket is free for reuse now.
        assert.strictEqual(socket[async_id_symbol], -1);

        // second request:
        const r2 = http.request({
          agent, port, method: 'POST'
        }, common.mustCall((res) => {
          assert.ok(destroyedIds.has(asyncIdAtFirstRequest));

          // Empty payload, to hit the “right” code path.
          r2.end('');

          res.on('data', common.mustCallAtLeast(() => {}));
          res.on('end', common.mustCall(() => {
            // Clean up to let the event loop stop.
            server.close();
            agent.destroy();
          }));
        }));

        // Schedule a payload to be written immediately, but do not end the
        // request just yet.
        r2.write(payload);
      }));
    }));
  }));
  r1.end(payload);
}));