|
| 1 | +// Flags: --expose-gc --expose-internals |
| 2 | +'use strict'; |
| 3 | +const common = require('../common'); |
| 4 | +const http = require('http'); |
| 5 | +const async_hooks = require('async_hooks'); |
| 6 | +const makeDuplexPair = require('../common/duplexpair'); |
| 7 | + |
| 8 | +// Regression test for https://github.com/nodejs/node/issues/30122 |
| 9 | +// When a domain is attached to an http Agent’s ReusedHandle object, that |
| 10 | +// domain should be kept alive through the ReusedHandle and that in turn |
| 11 | +// through the actual underlying handle. |
| 12 | + |
| 13 | +// Consistency check: There is a ReusedHandle being used, and it emits events. |
| 14 | +// We also use this async hook to manually trigger GC just before the domain’s |
| 15 | +// own `before` hook runs, in order to reproduce the bug above (the ReusedHandle |
| 16 | +// being collected and the domain with it while the handle is still alive). |
| 17 | +const checkInitCalled = common.mustCall(); |
| 18 | +const checkBeforeCalled = common.mustCallAtLeast(); |
| 19 | +let reusedHandleId; |
| 20 | +async_hooks.createHook({ |
| 21 | + init(id, type, triggerId, resource) { |
| 22 | + if (resource.constructor.name === 'ReusedHandle') { |
| 23 | + reusedHandleId = id; |
| 24 | + checkInitCalled(); |
| 25 | + } |
| 26 | + }, |
| 27 | + before(id) { |
| 28 | + if (id === reusedHandleId) { |
| 29 | + global.gc(); |
| 30 | + checkBeforeCalled(); |
| 31 | + } |
| 32 | + } |
| 33 | +}).enable(); |
| 34 | + |
| 35 | +// We use a DuplexPair rather than TLS sockets to keep the domain from being |
| 36 | +// attached to too many objects that use strong references (timers, the network |
| 37 | +// socket handle, etc.) and wrap the client side in a JSStreamSocket so we don’t |
| 38 | +// have to implement the whole _handle API ourselves. |
| 39 | +const { serverSide, clientSide } = makeDuplexPair(); |
| 40 | +const JSStreamSocket = require('internal/js_stream_socket'); |
| 41 | +const wrappedClientSide = new JSStreamSocket(clientSide); |
| 42 | + |
| 43 | +// Consistency check: We use asyncReset exactly once. |
| 44 | +wrappedClientSide._handle.asyncReset = |
| 45 | + common.mustCall(wrappedClientSide._handle.asyncReset); |
| 46 | + |
| 47 | +// Dummy server implementation, could be any server for this test... |
| 48 | +const server = http.createServer(common.mustCall((req, res) => { |
| 49 | + res.writeHead(200, { |
| 50 | + 'Content-Type': 'text/plain' |
| 51 | + }); |
| 52 | + res.end('Hello, world!'); |
| 53 | +}, 2)); |
| 54 | +server.emit('connection', serverSide); |
| 55 | + |
| 56 | +// HTTP Agent that only returns the fake connection. |
| 57 | +class TestAgent extends http.Agent { |
| 58 | + createConnection = common.mustCall(() => wrappedClientSide) |
| 59 | +} |
| 60 | +const agent = new TestAgent({ keepAlive: true, maxSockets: 1 }); |
| 61 | + |
| 62 | +function makeRequest(cb) { |
| 63 | + const req = http.request({ agent }, common.mustCall((res) => { |
| 64 | + res.resume(); |
| 65 | + res.on('end', cb); |
| 66 | + })); |
| 67 | + req.end(''); |
| 68 | +} |
| 69 | + |
| 70 | +// The actual test starts here: |
| 71 | + |
| 72 | +const domain = require('domain'); |
| 73 | +// Create the domain in question and a dummy “noDomain” domain that we use to |
| 74 | +// avoid attaching new async resources to the original domain. |
| 75 | +const d = domain.create(); |
| 76 | +const noDomain = domain.create(); |
| 77 | + |
| 78 | +d.run(common.mustCall(() => { |
| 79 | + // Create a first request only so that we can get a “re-used” socket later. |
| 80 | + makeRequest(common.mustCall(() => { |
| 81 | + // Schedule the second request. |
| 82 | + setImmediate(common.mustCall(() => { |
| 83 | + makeRequest(common.mustCall(() => { |
| 84 | + // The `setImmediate()` is run inside of `noDomain` so that it doesn’t |
| 85 | + // keep the actual target domain alive unnecessarily. |
| 86 | + noDomain.run(common.mustCall(() => { |
| 87 | + setImmediate(common.mustCall(() => { |
| 88 | + // This emits an async event on the reused socket, so it should |
| 89 | + // run the domain’s `before` hooks. |
| 90 | + // This should *not* throw an error because the domain was garbage |
| 91 | + // collected too early. |
| 92 | + serverSide.end(); |
| 93 | + })); |
| 94 | + })); |
| 95 | + })); |
| 96 | + })); |
| 97 | + })); |
| 98 | +})); |
0 commit comments