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