diff --git a/src/module_wrap.cc b/src/module_wrap.cc index aa6b3bc303f..9b7354adfca 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -791,6 +791,8 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo& args) { bool timed_out = false; bool received_signal = false; + const uint32_t async_stack_length = + realm->env()->async_hooks()->fields()[AsyncHooks::kStackLength]; MaybeLocal result; { auto wd = timeout != -1 @@ -853,6 +855,13 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo& args) { if (timed_out || received_signal) { if (!realm->env()->is_main_thread() && realm->env()->is_stopping()) return; isolate->CancelTerminateExecution(); + // A promise job terminated by the watchdog never runs its async hook + // `after` callback, so drop the contexts it left on the stack. + AsyncHooks* async_hooks = realm->env()->async_hooks(); + while (async_hooks->fields()[AsyncHooks::kStackLength] > + async_stack_length) { + async_hooks->pop_async_context(realm->env()->execution_async_id()); + } // It is possible that execution was terminated by another timeout in // which this timeout is nested, so check whether one of the watchdogs // from this invocation is responsible for termination. diff --git a/src/node_contextify.cc b/src/node_contextify.cc index 838a65c1ff3..11939e4002b 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -1286,6 +1286,8 @@ bool ContextifyScript::EvalMachine(Local context, MaybeLocal result; bool timed_out = false; bool received_signal = false; + const uint32_t async_stack_length = + env->async_hooks()->fields()[AsyncHooks::kStackLength]; { auto wd = timeout != -1 ? std::make_optional( env->isolate(), timeout, &timed_out) @@ -1304,6 +1306,13 @@ bool ContextifyScript::EvalMachine(Local context, if (!env->is_main_thread() && env->is_stopping()) return false; env->isolate()->CancelTerminateExecution(); + // A promise job terminated by the watchdog never runs its async hook + // `after` callback, so drop the contexts it left on the stack. + AsyncHooks* async_hooks = env->async_hooks(); + while (async_hooks->fields()[AsyncHooks::kStackLength] > + async_stack_length) { + async_hooks->pop_async_context(env->execution_async_id()); + } // It is possible that execution was terminated by another timeout in // which this timeout is nested, so check whether one of the watchdogs // from this invocation is responsible for termination. diff --git a/test/parallel/test-async-hooks-vm-module-timeout.js b/test/parallel/test-async-hooks-vm-module-timeout.js new file mode 100644 index 00000000000..ecfe2f2e31a --- /dev/null +++ b/test/parallel/test-async-hooks-vm-module-timeout.js @@ -0,0 +1,23 @@ +// Flags: --experimental-vm-modules +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); +const vm = require('vm'); + +async_hooks.createHook({ init() {} }).enable(); + +(async () => { + const context = vm.createContext({}, { microtaskMode: 'afterEvaluate' }); + const module = new vm.SourceTextModule( + 'Promise.resolve().then(() => { while (true); });', + { context } + ); + await module.link(common.mustNotCall()); + + await assert.rejects(module.evaluate({ timeout: 5 }), { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', + }); + setImmediate(common.mustCall()); +})().then(common.mustCall()); diff --git a/test/parallel/test-async-hooks-vm-timeout.js b/test/parallel/test-async-hooks-vm-timeout.js new file mode 100644 index 00000000000..4b2261ea1b0 --- /dev/null +++ b/test/parallel/test-async-hooks-vm-timeout.js @@ -0,0 +1,24 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const async_hooks = require('async_hooks'); +const vm = require('vm'); + +async_hooks.createHook({ init() {} }).enable(); + +const executionAsyncId = async_hooks.executionAsyncId(); +const context = vm.createContext({}, { microtaskMode: 'afterEvaluate' }); + +assert.throws(() => { + vm.runInContext( + 'Promise.resolve().then(() => { while (true); });', + context, + { timeout: 5 } + ); +}, { + code: 'ERR_SCRIPT_EXECUTION_TIMEOUT', +}); + +assert.strictEqual(async_hooks.executionAsyncId(), executionAsyncId); +setImmediate(common.mustCall());