为什么MessagePort.postMessage会使Firefox崩溃



我向Firefox提交了一份崩溃报告,但我也想确保我没有写错或被规范禁止。

在下面的片段中:

  • 主进程创建一个web工作程序
  • 主进程创建一个MessageChannel
  • 主进程将port2发送给web工作者
  • 网络工作人员确认接收到端口
  • 主进程在通道上发送消息以请求实际工作
  • web工作者创建1000个ArrayBuffer并将其传输回

正如标题中所述,这段代码几乎每次都会让Firefox崩溃。我尝试发送不同数量的缓冲区,看起来分水岭大约在170个缓冲区。具体来说,我的印象是,在171之前,Firefox不会崩溃,但在171-174之间,事情变得很奇怪(因为窗口中没有反应,工作人员什么也没有回来(,在175时,它总是崩溃。

我的代码是错的还是Firefox的错误/限制

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>

这绝对是一个bug,你什么都没做"对照规格";不,你的代码";应该";工作

你在打开这个问题时做得很好,根据我的经验,这些问题比车祸报告处理得更快,事实上,三天后就已经解决了。

顺便说一句,我做了一个更简单的repo,它不使用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,并创建许多";子阵列";通过这种方式,您可以像处理许多小阵列一样继续处理这些阵列,而实际上仍然有一个底层的ArrayBuffer,并且GC在两个环境之间执行许多IO时不必介入
    我没有真正检查,但我认为这将比在任何浏览器中传输这么多小缓冲区更快

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 )
);
}

  • 如果真的需要那么多ArrayBuffer,您可以在每个线程中创建副本,并使用一个大的ArrayBuffer仅用于传输,从大的ArrayBBuffer中填充较小的。这意味着您将不断地将数据存储在内存中三次,但之后再也不会创建新的数据,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 ] );
}

最新更新