为什么 MessagePort.postMessage 会导致 Firefox 崩溃?

Why is MessagePort.postMessage crashing Firefox?

我提交了 a crash report to Firefox,但我也想确保我没有写错或规范禁止的内容。

在下面的代码片段中:

如标题所述,这段代码几乎每次都会让我的 Firefox 崩溃。我尝试发送不同数量的缓冲区,似乎分水岭大约在 ~170 个缓冲区。具体来说,我的印象是在 171 之前,Firefox 不会崩溃,但是在 171-174 之间事情变得很奇怪(因为 window 变得没有响应,工作人员没有任何反馈)并且在 175 时它总是崩溃.

我的代码有误还是这是 Firefox bug/limitation?

Chrome,Edge 和 Safari 似乎都可以接受代码。

addEventListener("load", () => {
  const workerSrc = document.getElementById("worker-src").innerText;
  const src = URL.createObjectURL(new Blob([workerSrc], { type: "application/javascript" }));

  const btn = document.createElement("button");
  btn.innerText = "Click me!";
  btn.addEventListener("click", () => {
    const worker = new Worker(src);
    const channel = new MessageChannel();

    channel.port1.addEventListener("message", (message) => {
      if (message.data.name === "messagePortResult") {
        channel.port1.postMessage({ name: "getBuffers" });
      } else if (message.data.name === "getBuffersResult") {
        console.log("This is what I got back from the worker: ", message.data.data);
      }
    });

    channel.port1.start();

    worker.postMessage({ name: "messagePort", port: channel.port2 }, [channel.port2]);
  });

  document.body.appendChild(btn);
});
<script id="worker-src" type="x-js/x-worker">
  let port = null;

  addEventListener("message", (message) => {
    if (message.data.name === "messagePort") {
      port = message.data.port;

      port.addEventListener("message", () => {
        const buffers = [];

        for (let i = 0; i < 1000; i++) {
          buffers.push(new ArrayBuffer(1024));
        }

        port.postMessage({ name: "getBuffersResult", data: buffers }, buffers);
      });

      port.start();

      port.postMessage({ name: "messagePortResult" });
    }
  });
</script>

这绝对是一个错误,您没有做任何“违反规范”的事情不,您的代码“应该”有效。

你开得很好this issue,根据我的经验,这些问题得到的处理比崩溃报告更快,而且实际上它已经在三天后得到修复。

顺便说一下,我做了一个不使用 Worker 的更简单的复制:

button.onclick = (evt) => {
  const { port1 } = new MessageChannel();
  const buffers = [];
  for( let i = 0; i<1000; i++ ) {
    buffers.push( new ArrayBuffer( 1024 ) );
  }
  port1.postMessage( buffers, buffers );
};
<button id="button">Crash Firefox Tab</button>


也就是说,您可能可以解决此错误。

  • 此错误仅涉及 MessageChannel 的 MessagePorts。也许您可以重写代码,以便继续使用 Worker 的 MessagePorts:

const worker_content = `
  const buffers = [];
  for( let i = 0; i<1000; i++ ) {
    buffers.push( new ArrayBuffer( 1024 ) );  
  }
  postMessage( { data: buffers }, buffers );
`;
const worker_url = URL.createObjectURL( new Blob( [ worker_content ] ) );
worker = new Worker( worker_url );
worker.onmessage = (evt) => {
  console.log( "received", evt.data );
};

  • 传输这么多 ArrayBuffers 听起来有点奇怪。 我不确定你为什么需要这样做,但有一种方法一直到SharedArrayBuffers回来玩就是调一个大的ArrayBuffer,然后从里面创建很多"sub-arrays"
    这样你就可以继续处理它们,就好像它们是许多小数组一样,而实际上仍然有一个底层 ArrayBuffer 并且在两者之间做很多 IO 时 GC 不必启动环境
    我没有真正检查过,但我认为这比在任何浏览器中传输那么多小缓冲区要快。

const nb_of_buffers = 1000;
const size_of_buffers = 1024;
const { port1, port2 } = new MessageChannel();
{
  // in your main thread
  port1.onmessage = (evt) => {
    const big_arr = evt.data;
    const size_of_array = size_of_buffers / big_arr.BYTES_PER_ELEMENT;
    const arrays = [];
    for( let i = 0; i < nb_of_buffers; i++) {
      const start = i * size_of_array;
      const end = start + size_of_array;
      arrays.push( big_arr.subarray( start, end ) );
    }
    console.log( "received %s arrays", arrays.length );
    console.log( "first array", arrays[ 0 ] );
    console.log( "last array", arrays[ arrays.length - 1 ] );
    console.log( "same buffer anyway?", arrays[ 0 ].buffer === arrays[ arrays.length - 1 ].buffer );
  };
}
{
  // in Worker
  const big_buffer = new ArrayBuffer( 1024 * 1000 );
  const big_arr = new Uint32Array( big_buffer );
  const size_of_array = size_of_buffers / big_arr.BYTES_PER_ELEMENT;
  const arrays = [];
  for( let i = 0; i < nb_of_buffers; i++) {
    const start = i * size_of_array;
    const end = start + size_of_array;
    const sub_array = big_arr.subarray( start, end );
    arrays.push( sub_array );
    sub_array.fill( i );
  }
  // transfer to main
  port2.postMessage( big_arr, [big_buffer] );
  console.log( "sub_arrays buffer got transferred?",
    arrays.every( arr => arr.buffer.byteLength === 0 )
  );
}

  • 如果你真的需要那么多ArrayBuffers,你可以在每个线程并使用单个大 ArrayBuffer 仅用于传输,从那个大 ArrayBuffer 填充较小的。这意味着您将不断地将数据存储在内存中三次,但之后不会创建新数据,并且不必启动 GC。

const nb_of_buffers = 1000;
const size_of_buffers = 1024;
const { port1, port2 } = new MessageChannel();
{
  // in your main thread
  const buffers = [];
  for( let i = 0; i < nb_of_buffers; i++) {
    buffers.push( new ArrayBuffer( size_of_buffers ) );
  }
  port1.onmessage = (evt) => {
    const transfer_arr = new Uint32Array( evt.data );
    // update the values of each small arrays
    buffers.forEach( (buf, index) => {
      const size_of_arr = size_of_buffers / transfer_arr.BYTES_PER_ELEMENT;
      const start = index * size_of_arr;
      const end = start + size_of_arr;
      const sub_array = transfer_arr.subarray( start, end );
      new Uint32Array( buf ).set( sub_array );
    } );
    console.log( "first array", new Uint32Array( buffers[ 0 ] ) );
    console.log( "last array", new Uint32Array( buffers[ buffers.length - 1 ] ) );
  };
}
{
  // in Worker
  const buffers = [];
  for( let i = 0; i < nb_of_buffers; i++) {
    const buf = new ArrayBuffer( size_of_buffers );
    buffers.push( buf );
    new Uint32Array( buf ).fill( i );
  }
  // copy inside big_buffer
  const big_buffer = new ArrayBuffer( size_of_buffers * nb_of_buffers );
  const big_array = new Uint32Array( big_buffer );
  buffers.forEach( (buf, index) => {
    const small_array = new Uint32Array( buf );
    const size_of_arr = size_of_buffers / small_array.BYTES_PER_ELEMENT;
    const start = index * size_of_arr;
    big_array.set( small_array, start );
  } );
  // transfer to main
  port2.postMessage( big_buffer, [ big_buffer ] );
}