Skip to content

Commit a171dce

Browse files
committed
vm: fix async hook stack after microtask timeout
Assisted-by: Claude Code Signed-off-by: lazerg <lazerg2@gmail.com>
1 parent fbbf534 commit a171dce

2 files changed

Lines changed: 33 additions & 0 deletions

File tree

‎src/node_contextify.cc‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,8 @@ bool ContextifyScript::EvalMachine(Local<Context> context,
12861286
MaybeLocal<Value> result;
12871287
bool timed_out = false;
12881288
bool received_signal = false;
1289+
const uint32_t async_stack_length =
1290+
env->async_hooks()->fields()[AsyncHooks::kStackLength];
12891291
{
12901292
auto wd = timeout != -1 ? std::make_optional<Watchdog>(
12911293
env->isolate(), timeout, &timed_out)
@@ -1304,6 +1306,13 @@ bool ContextifyScript::EvalMachine(Local<Context> context,
13041306
if (!env->is_main_thread() && env->is_stopping())
13051307
return false;
13061308
env->isolate()->CancelTerminateExecution();
1309+
// A promise job terminated by the watchdog never runs its async hook
1310+
// `after` callback, so drop the contexts it left on the stack.
1311+
AsyncHooks* async_hooks = env->async_hooks();
1312+
while (async_hooks->fields()[AsyncHooks::kStackLength] >
1313+
async_stack_length) {
1314+
async_hooks->pop_async_context(env->execution_async_id());
1315+
}
13071316
// It is possible that execution was terminated by another timeout in
13081317
// which this timeout is nested, so check whether one of the watchdogs
13091318
// from this invocation is responsible for termination.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const async_hooks = require('async_hooks');
6+
const vm = require('vm');
7+
8+
async_hooks.createHook({ init() {} }).enable();
9+
10+
const executionAsyncId = async_hooks.executionAsyncId();
11+
const context = vm.createContext({}, { microtaskMode: 'afterEvaluate' });
12+
13+
assert.throws(() => {
14+
vm.runInContext(
15+
'Promise.resolve().then(() => { while (true); });',
16+
context,
17+
{ timeout: 5 }
18+
);
19+
}, {
20+
code: 'ERR_SCRIPT_EXECUTION_TIMEOUT',
21+
});
22+
23+
assert.strictEqual(async_hooks.executionAsyncId(), executionAsyncId);
24+
setImmediate(common.mustCall());

0 commit comments

Comments
 (0)