| '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); |
| })); |