为什么 MessagePort.postMessage 会导致 Firefox 崩溃?
Why is MessagePort.postMessage crashing Firefox?
我提交了 a crash report to Firefox,但我也想确保我没有写错或规范禁止的内容。
在下面的代码片段中:
- 主进程创建一个web worker
- 主进程创建一个
MessageChannel
- 主进程发送
port2
给web worker
- 网络工作者确认接收到端口
- 主进程在通道上发送消息请求实际工作
- 网络工作者创建 1000
ArrayBuffer
s 并将它们传回
如标题所述,这段代码几乎每次都会让我的 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 ] );
}
我提交了 a crash report to Firefox,但我也想确保我没有写错或规范禁止的内容。
在下面的代码片段中:
- 主进程创建一个web worker
- 主进程创建一个
MessageChannel
- 主进程发送
port2
给web worker - 网络工作者确认接收到端口
- 主进程在通道上发送消息请求实际工作
- 网络工作者创建 1000
ArrayBuffer
s 并将它们传回
如标题所述,这段代码几乎每次都会让我的 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 ] );
}