• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2// Flags: --expose-internals
3const common = require('../common');
4const assert = require('assert');
5const { async_id_symbol } = require('internal/async_hooks').symbols;
6const async_hooks = require('async_hooks');
7const http = require('http');
8
9// Regression test for https://github.com/nodejs/node/issues/19859
10// Checks that an http.Agent emits a destroy for the old asyncId before calling
11// asyncReset()s when reusing a socket handle. The setup is nearly identical to
12// parallel/test-async-hooks-http-agent (which focuses on the assertion that
13// a fresh asyncId is assigned to the net.Socket instance).
14
15const destroyedIds = new Set();
16async_hooks.createHook({
17  destroy: common.mustCallAtLeast((asyncId) => {
18    destroyedIds.add(asyncId);
19  }, 1)
20}).enable();
21
22// Make sure a single socket is transparently reused for 2 requests.
23const agent = new http.Agent({
24  keepAlive: true,
25  keepAliveMsecs: Infinity,
26  maxSockets: 1
27});
28
29const server = http.createServer(common.mustCall((req, res) => {
30  req.once('data', common.mustCallAtLeast(() => {
31    res.writeHead(200, { 'Content-Type': 'text/plain' });
32    res.write('foo');
33  }));
34  req.on('end', common.mustCall(() => {
35    res.end('bar');
36  }));
37}, 2)).listen(0, common.mustCall(() => {
38  const port = server.address().port;
39  const payload = 'hello world';
40
41  // First request. This is useless except for adding a socket to the
42  // agent’s pool for reuse.
43  const r1 = http.request({
44    agent, port, method: 'POST'
45  }, common.mustCall((res) => {
46    // Remember which socket we used.
47    const socket = res.socket;
48    const asyncIdAtFirstRequest = socket[async_id_symbol];
49    assert.ok(asyncIdAtFirstRequest > 0, `${asyncIdAtFirstRequest} > 0`);
50    // Check that request and response share their socket.
51    assert.strictEqual(r1.socket, socket);
52
53    res.on('data', common.mustCallAtLeast(() => {}));
54    res.on('end', common.mustCall(() => {
55      // setImmediate() to give the agent time to register the freed socket.
56      setImmediate(common.mustCall(() => {
57        // The socket is free for reuse now.
58        assert.strictEqual(socket[async_id_symbol], -1);
59
60        // second request:
61        const r2 = http.request({
62          agent, port, method: 'POST'
63        }, common.mustCall((res) => {
64          assert.ok(destroyedIds.has(asyncIdAtFirstRequest));
65
66          // Empty payload, to hit the “right” code path.
67          r2.end('');
68
69          res.on('data', common.mustCallAtLeast(() => {}));
70          res.on('end', common.mustCall(() => {
71            // Clean up to let the event loop stop.
72            server.close();
73            agent.destroy();
74          }));
75        }));
76
77        // Schedule a payload to be written immediately, but do not end the
78        // request just yet.
79        r2.write(payload);
80      }));
81    }));
82  }));
83  r1.end(payload);
84}));
85