I submitted a crash report to Firefox, but I would also like to make sure that I did not write something wrong or forbidden by the spec.
In the snippet below:
MessageChannel
port2
to the web workerArrayBuffer
s and transfers them backAs stated in the title, this code crashes Firefox for me pretty much every time. I tried to send a different amount of buffers, and it seems like the watershed is at about ~170 buffers. Specifically, I had the impression that up to 171, Firefox does not crash, but between 171-174 things get weird (as in the window becomes unresponsive, of nothing comes back from the worker) and at 175 it always crashes.
Is my code wrong or is this a Firefox bug/limitation?
Chrome, Edge and Safari seem to be okay with the code.
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 is definitely a bug, and you are not doing anything "against the specs" no, your code "should" work.
You did very well opening this issue, in my experience these get treated faster than just crash reports and indeed it's already fixed after three days.
By the way, I made a simpler repro which doesn't use a 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>
That being said, it is probably possible for you to workaround this bug.
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 );
};
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 )
);
}
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 ] );
}