Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: nodejs/node
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: aa6838c25203e997847531421b752c62e88f5763
Choose a base ref
...
head repository: nodejs/node
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: c81b1dff65e427cbbb89bcb1b4ac5e5a0cc34b51
Choose a head ref
  • 3 commits
  • 4 files changed
  • 1 contributor

Commits on Sep 21, 2025

  1. test: testcase demonstrating issue 59541

    Check that we lose the execution flow in the outer context, upon
    resolving a promise created in in the inner context.
    
    PR-URL: #59801
    Fixes: #59541
    Refs: https://issues.chromium.org/issues/441679231
    Refs: https://groups.google.com/g/v8-dev/c/YIeRg8CUNS8/m/rEQdFuNZAAAJ
    Refs: https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
    Reviewed-By: Anna Henningsen <anna@addaleax.net>
    ericrannaud authored and nodejs-github-bot committed Sep 21, 2025
    Configuration menu
    Copy the full SHA
    1d8df51 View commit details
    Browse the repository at this point in the history
  2. vm: "afterEvaluate", evaluate() return a promise from the outer context

    Consider the default context A with a microtask queue QA, and a
    context B with its own microtask queue QB.
    
    Context B is constructed with vm.createContext(..., {microtaskMode:
    "afterEvaluate"}). The evaluation in context B can be performed via
    vm.Script or vm.SourceTextModule.
    
    The standard (https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob)
    dictates that, when resolving a {promise} with {resolution}, from any
    context, the {then} method on {promise} should be called within a task
    enqueued on the microtask queue from the context associated with {then}.
    
    Specifically, after evaluating a script or module in context B, any
    promises created within B, if later resolved within A, will result in a
    task to be enqueued back onto QB, even long after we are done evaluating
    any code within B.
    
    This creates a challenge for users of node:vm in "afterEvaluate" mode.
    In ContextifyScript::EvalMachine() and in ModuleWrap::Evaluate(), we
    only drain the microtask queue QB a single time after running the script
    or evaluating the module. After that point, the queue will not be
    drained unless another script or module is evaluated in the same
    context.
    
    In the following scenario, prior to this patch, the log statement will
    not be printed:
    
    	const microtaskMode = "afterEvaluate";
    	const context = vm.createContext({}, {microtaskMode});
    	const source = "";
    	const module = new vm.SourceTextModule(source, {context});
    	await module.link(() => null);
    	await module.evaluate();
    	console.log("NOT PRINTED");
    
    Within `evaluate()`, there is this `await` statement:
    
    	await this[kWrap].evaluate(timeout, breakOnSigint)
    
    Since the promise returned by ModuleWrap::Evaluate() is the top-level
    capability for {module}, a promise created within B, V8 will enqueue a
    task on QB. But since this is after the PerformCheckpoint() call in
    ModuleWrap::Evaluate(), the task in QB is never run. In the meantime,
    since QA is empty, the Node process simply exits (with a warning about
    the unsettled promise, if it happened to be a top-level await).
    
    While being unable to do `await module.evaluate()` is clearly a problem,
    more generally, it is intended that in "afterEvaluate" mode, promises
    created in the inner context cannot make progress if, and until, the
    microtask queue of the inner context is checkpointed.
    
    Therefore, to address this issue, the fix is narrow:
    
    When the module has its own microtask queue, i.e. in "afterEvaluate"
    mode, the inner-context promise returned by
    v8::SourceTextModule::Evaluate() is first resolved to an outer-context
    promise, then we checkpoint the microtask queue of the inner context,
    then we return the outer-context promise we just built.
    
    This ensures that in the statement `await this[kWrap].evaluate(...)`,
    the promise returned can be resolved within the outer context, without
    involving the microtask queue in the inner context.
    
    Fixes: #59541
    Refs: https://issues.chromium.org/issues/441679231
    Refs: https://groups.google.com/g/v8-dev/c/YIeRg8CUNS8/m/rEQdFuNZAAAJ
    PR-URL: #59801
    Refs: https://tc39.es/ecma262/#sec-newpromiseresolvethenablejob
    Reviewed-By: Anna Henningsen <anna@addaleax.net>
    ericrannaud authored and nodejs-github-bot committed Sep 21, 2025
    Configuration menu
    Copy the full SHA
    cb5c8ec View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    c81b1df View commit details
    Browse the repository at this point in the history
Loading