Search code examples
javascriptrustwebassembly

JavaScript ArrayBuffer "detaches" when constructing a new Rust Vec via WASM


The basic idea is that a pointer is returned and then an image will be displayed using the pointer after creating a Vec at each video frame:

JavaScript:

var ptr = exports.alloc(size);
let bytes = new Uint8ClampedArray(exports.memory.buffer, ptr, size);
let image = new ImageData(bytes, 320, 240);

function tick() {
    requestAnimationFrame(tick);
    ctx.drawImage(video, 0, 0, width, height);
    let imageData = ctx.getImageData(0, 0, width, height).data;
    bytes.set(imageData);
    exports.create_vector();
    ctx.putImageData(img, 0, 0);
}

Rust:

#[no_mangle]
pub extern "C" fn alloc(capacity: usize) -> *mut c_void {
    let mut buf = Vec::with_capacity(capacity);
    let ptr = buf.as_mut_ptr();
    mem::forget(buf);
    return ptr as *mut c_void;
}

#[no_mangle]
pub extern "C" fn create_vector() {
    let _: Vec<u8> = Vec::with_capacity(320 * 240);
}

Here is the actual code.

Here are the error messages:

Chrome:

Uncaught TypeError: Cannot perform %TypedArray%.prototype.set on a detached ArrayBuffer
    at Uint8ClampedArray.set (<anonymous>)
    at tick

Safari:

This webpage was reloaded because a problem occurred

Mozilla:

DOMException: "An attempt was made to use an object that is not, or is no longer, usable"

TypeError: Underlying ArrayBuffer has been detached from the view

The main culprits seem to be:

let bytes = new Uint8ClampedArray(exports.memory.buffer, ptr, size);
// ...
    exports.create_vector();
// ...

i.e., the browser crashes when I try to use ptr again after calling exports.create_vector.

What is going wrong here? Is there a solution?


Solution

  • When you allocate a new buffer and there is not enough memory allocated by the WASM "process" for it, the browser will allocate a new, larger buffer and will copy all data into it from the old location. It's quite transparent inside the WASM interpreter, but all pointers passed to JavaScript (with which you construct Uint8ClampedArray) get invalidated. There are two solutions to my knowledge:

    • Allocate enough memory in the WASM "process" at initialization and immediately deallocate it so pointers will not get invalidated so long as memory usage is lower than the selected size.
    • Keep track of buffers passed to JavaScript and "renew" pointers on such errors.