diff --git a/src/async-wrap.cc b/src/async-wrap.cc index 0ea6c64a8be582..2f84f867b7c54b 100644 --- a/src/async-wrap.cc +++ b/src/async-wrap.cc @@ -142,9 +142,13 @@ static void DestroyIdsCb(uv_idle_t* handle) { uv_idle_stop(handle); Environment* env = Environment::from_destroy_ids_idle_handle(handle); - HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); + + AsyncWrap::RunDestroyCbs(env); +} + +void AsyncWrap::RunDestroyCbs(Environment* env) { Local fn = env->async_hooks_destroy_function(); TryCatch try_catch(env->isolate()); @@ -164,8 +168,6 @@ static void DestroyIdsCb(uv_idle_t* handle) { FatalException(env->isolate(), try_catch); } } - - env->destroy_ids_list()->clear(); } diff --git a/src/async-wrap.h b/src/async-wrap.h index fa37ea13a65993..dfe5a558957e01 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -111,6 +111,8 @@ class AsyncWrap : public BaseObject { static bool EmitBefore(Environment* env, double id); static bool EmitAfter(Environment* env, double id); + static void RunDestroyCbs(Environment* env); + inline ProviderType provider_type() const; inline double get_id() const; diff --git a/src/node.cc b/src/node.cc index 60fba9ad2fa553..0c1d97fdef0214 100644 --- a/src/node.cc +++ b/src/node.cc @@ -4531,6 +4531,8 @@ inline int Start(Isolate* isolate, IsolateData* isolate_data, } while (more == true); } + AsyncWrap::RunDestroyCbs(&env); + env.set_trace_sync_io(false); const int exit_code = EmitExit(&env); diff --git a/test/parallel/test-async-hooks-close-during-destroy.js b/test/parallel/test-async-hooks-close-during-destroy.js new file mode 100644 index 00000000000000..724fe8c77e46a0 --- /dev/null +++ b/test/parallel/test-async-hooks-close-during-destroy.js @@ -0,0 +1,38 @@ +'use strict'; +// Test that async ids that are added to the destroy queue while running a +// `destroy` callback are handled correctly. + +const common = require('../common'); +const assert = require('assert'); +const net = require('net'); +const async_hooks = require('async_hooks'); + +const initCalls = new Set(); +let destroyTcpWrapCallCount = 0; +let srv2; + +async_hooks.createHook({ + init: common.mustCallAtLeast((id, provider, triggerId) => { + if (provider === 'TCPWRAP') + initCalls.add(id); + }, 2), + destroy: common.mustCallAtLeast((id) => { + if (!initCalls.has(id)) return; + + switch (destroyTcpWrapCallCount++) { + case 0: + // Trigger the second `destroy` call. + srv2.close(); + break; + case 2: + assert.fail('More than 2 destroy() invocations'); + break; + } + }, 2) +}).enable(); + +// Create a server to trigger the first `destroy` callback. +net.createServer().listen(0).close(); +srv2 = net.createServer().listen(0); + +process.on('exit', () => assert.strictEqual(destroyTcpWrapCallCount, 2)); diff --git a/test/parallel/test-async-hooks-top-level-clearimmediate.js b/test/parallel/test-async-hooks-top-level-clearimmediate.js new file mode 100644 index 00000000000000..f667c3ca337816 --- /dev/null +++ b/test/parallel/test-async-hooks-top-level-clearimmediate.js @@ -0,0 +1,27 @@ +'use strict'; + +// Regression test for https://github.com/nodejs/node/issues/13262 + +const common = require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); + +let seenId, seenResource; + +async_hooks.createHook({ + init: common.mustCall((id, provider, triggerId, resource) => { + seenId = id; + seenResource = resource; + assert.strictEqual(provider, 'Immediate'); + assert.strictEqual(triggerId, 1); + }), + before: common.mustNotCall(), + after: common.mustNotCall(), + destroy: common.mustCall((id) => { + assert.strictEqual(seenId, id); + }) +}).enable(); + +const immediate = setImmediate(common.mustNotCall()); +assert.strictEqual(immediate, seenResource); +clearImmediate(immediate);