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