11cb0ef41Sopenharmony_ci// Flags: --expose-gc --expose-internals 21cb0ef41Sopenharmony_ci'use strict'; 31cb0ef41Sopenharmony_ciconst common = require('../common'); 41cb0ef41Sopenharmony_ciconst http = require('http'); 51cb0ef41Sopenharmony_ciconst async_hooks = require('async_hooks'); 61cb0ef41Sopenharmony_ciconst makeDuplexPair = require('../common/duplexpair'); 71cb0ef41Sopenharmony_ci 81cb0ef41Sopenharmony_ci// Regression test for https://github.com/nodejs/node/issues/30122 91cb0ef41Sopenharmony_ci// When a domain is attached to an http Agent’s ReusedHandle object, that 101cb0ef41Sopenharmony_ci// domain should be kept alive through the ReusedHandle and that in turn 111cb0ef41Sopenharmony_ci// through the actual underlying handle. 121cb0ef41Sopenharmony_ci 131cb0ef41Sopenharmony_ci// Consistency check: There is a ReusedHandle being used, and it emits events. 141cb0ef41Sopenharmony_ci// We also use this async hook to manually trigger GC just before the domain’s 151cb0ef41Sopenharmony_ci// own `before` hook runs, in order to reproduce the bug above (the ReusedHandle 161cb0ef41Sopenharmony_ci// being collected and the domain with it while the handle is still alive). 171cb0ef41Sopenharmony_ciconst checkInitCalled = common.mustCall(); 181cb0ef41Sopenharmony_ciconst checkBeforeCalled = common.mustCallAtLeast(); 191cb0ef41Sopenharmony_cilet reusedHandleId; 201cb0ef41Sopenharmony_ciasync_hooks.createHook({ 211cb0ef41Sopenharmony_ci init(id, type, triggerId, resource) { 221cb0ef41Sopenharmony_ci if (resource.constructor.name === 'ReusedHandle') { 231cb0ef41Sopenharmony_ci reusedHandleId = id; 241cb0ef41Sopenharmony_ci checkInitCalled(); 251cb0ef41Sopenharmony_ci } 261cb0ef41Sopenharmony_ci }, 271cb0ef41Sopenharmony_ci before(id) { 281cb0ef41Sopenharmony_ci if (id === reusedHandleId) { 291cb0ef41Sopenharmony_ci global.gc(); 301cb0ef41Sopenharmony_ci checkBeforeCalled(); 311cb0ef41Sopenharmony_ci } 321cb0ef41Sopenharmony_ci } 331cb0ef41Sopenharmony_ci}).enable(); 341cb0ef41Sopenharmony_ci 351cb0ef41Sopenharmony_ci// We use a DuplexPair rather than TLS sockets to keep the domain from being 361cb0ef41Sopenharmony_ci// attached to too many objects that use strong references (timers, the network 371cb0ef41Sopenharmony_ci// socket handle, etc.) and wrap the client side in a JSStreamSocket so we don’t 381cb0ef41Sopenharmony_ci// have to implement the whole _handle API ourselves. 391cb0ef41Sopenharmony_ciconst { serverSide, clientSide } = makeDuplexPair(); 401cb0ef41Sopenharmony_ciconst JSStreamSocket = require('internal/js_stream_socket'); 411cb0ef41Sopenharmony_ciconst wrappedClientSide = new JSStreamSocket(clientSide); 421cb0ef41Sopenharmony_ci 431cb0ef41Sopenharmony_ci// Consistency check: We use asyncReset exactly once. 441cb0ef41Sopenharmony_ciwrappedClientSide._handle.asyncReset = 451cb0ef41Sopenharmony_ci common.mustCall(wrappedClientSide._handle.asyncReset); 461cb0ef41Sopenharmony_ci 471cb0ef41Sopenharmony_ci// Dummy server implementation, could be any server for this test... 481cb0ef41Sopenharmony_ciconst server = http.createServer(common.mustCall((req, res) => { 491cb0ef41Sopenharmony_ci res.writeHead(200, { 501cb0ef41Sopenharmony_ci 'Content-Type': 'text/plain' 511cb0ef41Sopenharmony_ci }); 521cb0ef41Sopenharmony_ci res.end('Hello, world!'); 531cb0ef41Sopenharmony_ci}, 2)); 541cb0ef41Sopenharmony_ciserver.emit('connection', serverSide); 551cb0ef41Sopenharmony_ci 561cb0ef41Sopenharmony_ci// HTTP Agent that only returns the fake connection. 571cb0ef41Sopenharmony_ciclass TestAgent extends http.Agent { 581cb0ef41Sopenharmony_ci createConnection = common.mustCall(() => wrappedClientSide); 591cb0ef41Sopenharmony_ci} 601cb0ef41Sopenharmony_ciconst agent = new TestAgent({ keepAlive: true, maxSockets: 1 }); 611cb0ef41Sopenharmony_ci 621cb0ef41Sopenharmony_cifunction makeRequest(cb) { 631cb0ef41Sopenharmony_ci const req = http.request({ agent }, common.mustCall((res) => { 641cb0ef41Sopenharmony_ci res.resume(); 651cb0ef41Sopenharmony_ci res.on('end', cb); 661cb0ef41Sopenharmony_ci })); 671cb0ef41Sopenharmony_ci req.end(''); 681cb0ef41Sopenharmony_ci} 691cb0ef41Sopenharmony_ci 701cb0ef41Sopenharmony_ci// The actual test starts here: 711cb0ef41Sopenharmony_ci 721cb0ef41Sopenharmony_ciconst domain = require('domain'); 731cb0ef41Sopenharmony_ci// Create the domain in question and a dummy “noDomain” domain that we use to 741cb0ef41Sopenharmony_ci// avoid attaching new async resources to the original domain. 751cb0ef41Sopenharmony_ciconst d = domain.create(); 761cb0ef41Sopenharmony_ciconst noDomain = domain.create(); 771cb0ef41Sopenharmony_ci 781cb0ef41Sopenharmony_cid.run(common.mustCall(() => { 791cb0ef41Sopenharmony_ci // Create a first request only so that we can get a “re-used” socket later. 801cb0ef41Sopenharmony_ci makeRequest(common.mustCall(() => { 811cb0ef41Sopenharmony_ci // Schedule the second request. 821cb0ef41Sopenharmony_ci setImmediate(common.mustCall(() => { 831cb0ef41Sopenharmony_ci makeRequest(common.mustCall(() => { 841cb0ef41Sopenharmony_ci // The `setImmediate()` is run inside of `noDomain` so that it doesn’t 851cb0ef41Sopenharmony_ci // keep the actual target domain alive unnecessarily. 861cb0ef41Sopenharmony_ci noDomain.run(common.mustCall(() => { 871cb0ef41Sopenharmony_ci setImmediate(common.mustCall(() => { 881cb0ef41Sopenharmony_ci // This emits an async event on the reused socket, so it should 891cb0ef41Sopenharmony_ci // run the domain’s `before` hooks. 901cb0ef41Sopenharmony_ci // This should *not* throw an error because the domain was garbage 911cb0ef41Sopenharmony_ci // collected too early. 921cb0ef41Sopenharmony_ci serverSide.end(); 931cb0ef41Sopenharmony_ci })); 941cb0ef41Sopenharmony_ci })); 951cb0ef41Sopenharmony_ci })); 961cb0ef41Sopenharmony_ci })); 971cb0ef41Sopenharmony_ci })); 981cb0ef41Sopenharmony_ci})); 99