• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2// Flags: --expose-internals
3const common = require('../common');
4const initHooks = require('./init-hooks');
5const { checkInvocations } = require('./hook-checks');
6const assert = require('assert');
7const { async_id_symbol } = require('internal/async_hooks').symbols;
8const http = require('http');
9
10// Checks that the async resource used in init in case of a reused handle
11// is not reused. Test is based on parallel\test-async-hooks-http-agent.js.
12
13const hooks = initHooks();
14hooks.enable();
15
16let asyncIdAtFirstReq;
17let asyncIdAtSecondReq;
18
19// Make sure a single socket is transparently reused for 2 requests.
20const agent = new http.Agent({
21  keepAlive: true,
22  keepAliveMsecs: Infinity,
23  maxSockets: 1
24});
25
26const server = http.createServer(common.mustCall((req, res) => {
27  req.once('data', common.mustCallAtLeast(() => {
28    res.writeHead(200, { 'Content-Type': 'text/plain' });
29    res.write('foo');
30  }));
31  req.on('end', common.mustCall(() => {
32    res.end('bar');
33  }));
34}, 2)).listen(0, common.mustCall(() => {
35  const port = server.address().port;
36  const payload = 'hello world';
37
38  // First request. This is useless except for adding a socket to the
39  // agent’s pool for reuse.
40  const r1 = http.request({
41    agent, port, method: 'POST'
42  }, common.mustCall((res) => {
43    // Remember which socket we used.
44    const socket = res.socket;
45    asyncIdAtFirstReq = socket[async_id_symbol];
46    assert.ok(asyncIdAtFirstReq > 0, `${asyncIdAtFirstReq} > 0`);
47    // Check that request and response share their socket.
48    assert.strictEqual(r1.socket, socket);
49
50    res.on('data', common.mustCallAtLeast(() => {}));
51    res.on('end', common.mustCall(() => {
52      // setImmediate() to give the agent time to register the freed socket.
53      setImmediate(common.mustCall(() => {
54        // The socket is free for reuse now.
55        assert.strictEqual(socket[async_id_symbol], -1);
56
57        // Second request. To re-create the exact conditions from the
58        // referenced issue, we use a POST request without chunked encoding
59        // (hence the Content-Length header) and call .end() after the
60        // response header has already been received.
61        const r2 = http.request({
62          agent, port, method: 'POST', headers: {
63            'Content-Length': payload.length
64          }
65        }, common.mustCall((res) => {
66          asyncIdAtSecondReq = res.socket[async_id_symbol];
67          assert.ok(asyncIdAtSecondReq > 0, `${asyncIdAtSecondReq} > 0`);
68          assert.strictEqual(r2.socket, socket);
69
70          // Empty payload, to hit the “right” code path.
71          r2.end('');
72
73          res.on('data', common.mustCallAtLeast(() => {}));
74          res.on('end', common.mustCall(() => {
75            // Clean up to let the event loop stop.
76            server.close();
77            agent.destroy();
78          }));
79        }));
80
81        // Schedule a payload to be written immediately, but do not end the
82        // request just yet.
83        r2.write(payload);
84      }));
85    }));
86  }));
87  r1.end(payload);
88}));
89
90
91process.on('exit', onExit);
92
93function onExit() {
94  hooks.disable();
95  hooks.sanityCheck();
96  const activities = hooks.activities;
97
98  // Verify both invocations
99  const first = activities.filter((x) => x.uid === asyncIdAtFirstReq)[0];
100  checkInvocations(first, { init: 1, destroy: 1 }, 'when process exits');
101
102  const second = activities.filter((x) => x.uid === asyncIdAtSecondReq)[0];
103  checkInvocations(second, { init: 1, destroy: 1 }, 'when process exits');
104
105  // Verify reuse handle has been wrapped
106  assert.strictEqual(first.type, second.type);
107  assert.ok(first.handle !== second.handle, 'Resource reused');
108  assert.ok(first.handle === second.handle.handle,
109            'Resource not wrapped correctly');
110}
111