nodejs / nodejs/node

proposal for shared microtask queues

未关闭
#65,555 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

feature request vm
主要语言
JavaScript
星标
122k
派生
37.3k
平均合并
4 天 2 小时
30 天内合并 PR
283

描述

#34023 added microtaskMode: 'afterEvaluate', which gives a context its own microtask queue and drains it after evaluation. There was discussion about giving authors broader control over the queue, but that was ultimately left out as beyond what users seemed to need.

However, the HTML Standard is riddled with requirements that need this kind of control. For example, HTML assigns a microtask queue to each event loop. Its clean up after running script algorithm then requires:

  • Finishing a script removes its realm execution context from the stack.
  • If the stack is then empty, the event loop performs a microtask checkpoint.

A Window and its same-agent iframe have separate realms but share an event loop. If they are represented by separate vm.Contexts, they therefore need one shared microtask queue.

Currently there's just not enough control over when queues are drained:

  • Default contexts share Node's queue, but there is no public API to synchronously drain it.
  • afterEvaluate drains synchronously, but gives every context a separate queue.

Let's try to explore this with code.

const vm = require('node:vm');

function run(options, drain = () => {}) {
  const trace = [];
  const record = (entry) => trace.push(entry);
  const window = vm.createContext({ record }, options);
  const iframe = vm.createContext({ record }, options);

  vm.runInContext(`
    const pending = new Promise((resolve) => {
      globalThis.resolve = resolve;
    });
    pending.then(() => {
      record('win-rxn');
      Promise.resolve().then(() => record('win-follow-up'));
    });
  `, window);

  vm.runInContext(`
    const pending = new Promise((resolve) => {
      globalThis.resolve = resolve;
    });
    pending.then(() => record('iframe-rxn'));
  `, iframe);

  window.resolveIframe = iframe.resolve;
  vm.runInContext('resolve(); resolveIframe();', window);
  drain(iframe);

  return trace;
}

console.log(run());
// []

console.log(run(
  { microtaskMode: 'afterEvaluate' },
  (iframe) => vm.runInContext('', iframe),
));
// ['win-rxn', 'win-follow-up', 'iframe-rxn']

The HTML model instead requires:

['win-rxn', 'iframe-rxn', 'win-follow-up']

With default contexts, both reactions are placed on Node's shared microtask queue in the required order, but there is no supported public API for performing the required checkpoint before run() returns. That is why the first call returns [].

With afterEvaluate, returning from the Window evaluation immediately drains the Window's private queue to exhaustion. The Window reaction therefore runs its follow-up before the iframe's private queue can be drained:

['win-rxn', 'win-follow-up', 'iframe-rxn']

Even if the iframe's private queue could be drained before the Window's, that would merely reverse the problem:

['iframe-rxn', 'win-rxn', 'win-follow-up']

Node's public vm therefore cannot perform this synchronous shared-queue checkpoint. Well, it isn't entirely impossible to make this example pass: process._tickCallback() does it. I initially thought that might be an acceptable hack for my own code, but it's deprecated and, after trying to build an actual event loop around it, looks DOA as a general solution because it’s a club when I needed a sewing needle (it drains Node’s shared nextTick and microtask queues, including work unrelated to the checkpoint).

My proposal is to expose a queue that multiple contexts can share and let authors be responsible for the draining. Something like:

const queue = vm.createMicrotaskQueue();

console.log(run(
  { microtaskQueue: queue },
  () => queue.runMicrotasks(),
));
// ['win-rxn', 'iframe-rxn', 'win-follow-up']

I do have a working prototype dreamed up with AI assist, but Node/V8 embedding internals are honestly outside my working knowledge.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从这里显示的 node:vm 入口点开始:createContext、runInContext、microtaskMode,以及提议的 createMicrotaskQueue API。查看链接的原型,并将其行为与提供的排序示例进行比较,包括 process._tickCallback() 的限制。当达成一致的公共设计能够提供共享队列的同步检查点,且不会清空无关工作时,即视为完成。

由索引模型根据 Issue 内容生成。

评估

技术栈
javascript, node.js
领域
api, backend
Issue 类型
功能
难度
5/5
预计耗时
一周以上
活跃度
活跃
描述清晰度
需要澄清
新手友好度
30/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。