BUILD_AS_WORKER: onmessage is installed too late under MODULARIZE, and is installed even on the main thread

オープン
#27,625 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
55/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
javascript, wasm

調査の方向性

src/build_as_worker.js から始め、MODULARIZE 下でその IIFE がどのように出力され、呼び出されるかを追跡します。これには、issue で説明されている worker の bootstrap または --pre-js パスも含まれます。メインスレッド上での動作と、factory の実行前に送信されたメッセージについて確認します。非 worker ページが onmessage ハンドラーを保持し、factory 前のメッセージが保持されて初期化後に配信されれば完了です。

索引モデルが issue の本文から書いたものです。

説明

src/build_as_worker.js wraps everything in an IIFE that installs the worker's onmessage:

(() => {
  var messageBuffer = null, buffer = 0;

  function flushMessages() { ... }
  function messageResender() { ... }

  onmessage = (msg) => {
    // if main has not yet been called (mem init file, other async things), buffer messages
    if (!runtimeInitialized) {
      if (!messageBuffer) {
        messageBuffer = [];
        setTimeout(messageResender, 100);
      }
      messageBuffer.push(msg);
      return;
    }
    ...
  }
})();

Two problems follow from when and where that IIFE runs. They are independent, so I have described both here and am happy to split them.

1. The handler is installed even when the module is not running in a worker

The IIFE runs in whatever environment loads the module, and assigns onmessage unconditionally. A module linked with -sBUILD_AS_WORKER that also runs on the main thread therefore overwrites window.onmessage, taking over message delivery for the whole page.

That is not a quiet takeover. Any message the page receives from another source now reaches this handler, which either aborts:

var func = Module['_' + msg.data['funcName']];
if (!func) abort('invalid worker function to call: ' + msg.data['funcName']);

or throws a TypeError outright, if msg.data is not an object with a funcName (a plain postMessage('...') from an embedding page or another library, for instance).

A guard at the top of the IIFE seems like the right fix:

if (!ENVIRONMENT_IS_WORKER) return;
2. Under MODULARIZE, messages that arrive before the factory runs are dropped

With -sMODULARIZE the IIFE lives inside the module factory, which the application invokes whenever it is ready. A worker, however, starts receiving messages the moment it is created — so there is a window between "worker script began executing" and "factory was called" during which these messages arrive.

The existing buffering does not cover that window. messageBuffer only starts collecting from inside the handler this IIFE installs, so it protects the interval between the factory running and runtimeInitialized — not the earlier one.

The natural workaround is to install a small buffering onmessage in the worker bootstrap (or via --pre-js) that collects messages until the factory is called. That does not work either: this IIFE overwrites onmessage and starts from messageBuffer = null, so everything the earlier handler collected is discarded.

The net effect is that a MODULARIZE'd BUILD_AS_WORKER module silently loses any message sent before the application gets around to instantiating it — with no way for the application to bridge the gap.

What we ship locally is for the IIFE to adopt a pre-existing buffer if one is present:

var messageBuffer = typeof workerMessageBuffer != 'undefined' ? workerMessageBuffer : null, buffer = 0;
// Upstream only starts the resender from inside its own handler, so an adopted
// buffer would otherwise never be flushed.
if (messageBuffer) setTimeout(messageResender, 100);

The bootstrap then just pushes into workerMessageBuffer until the factory is instantiated, and the module drains it. This has been running in production for us for a while.

I am raising this as an issue rather than sending the patch straight away, because the second half proposes a convention (workerMessageBuffer) that emscripten does not currently define, and you may well prefer a different shape — an explicit Module option, or having build_as_worker.js emit its own pre-factory bootstrap. Happy to send a PR for whichever you would like, and #1 can go on its own regardless.

Related, in that they are all variations of "the generated worker's onmessage is not reachable by the application": #20192, #11962, #8854.

Version

emscripten 6.0.3; the same code is on main as of today.

主要言語
C++
スター
27.8k
フォーク
3.6k
平均マージ
1日 3時間
マージ済み PR(30日)
179

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

emscripten-core/emscripten のほかの issue

emscripten-core/emscripten の issue をすべて見る

似ている issue

C++ の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。