boa icon indicating copy to clipboard operation
boa copied to clipboard

Thread Local Storage AccessError when using wgpu structs from within JsValue

Open xbwwj opened this issue 6 months ago • 1 comments

Describe the bug

The program panic with Thread Local Storage AccessError when use wgpu struct from JsValue.

To Reproduce

The WebGPU implementation (partly) is in this branch https://github.com/xubaiwang/boa/tree/main/core/runtime/src/webgpu.

Running this module with boa cli:

// webgpu.js
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

const input = new Float32Array([1, 3, 5]);
console.log("input =", input);
console.log("input instanceof Float32Array =", input instanceof Float32Array);

const workBuffer = device.createBuffer({
  size: input.byteLength,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC |
    GPUBufferUsage.COPY_DST,
});
console.log("workBuffer =", workBuffer);
console.log(
  "workBuffer instanceof GPUBuffer =",
  workBuffer instanceof GPUBuffer,
);

device.queue.writeBuffer(workBuffer, 0, input);
console.log("writeBuffer success");

console.log("end");
❯ cargo run -- -m webgpu-min.js
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.57s
     Running `target/debug/boa -m webgpu-min.js`
input = 1,3,5
input instanceof Float32Array = true
workBuffer = [object Object]
workBuffer instanceof GPUBuffer = true
writeBuffer success
end
The application panicked (crashed).
Message:  cannot access a Thread Local Storage value during or after destruction: AccessError
Location: /nix/store/wpdjjqamyjqhi3x54ivx0dkr9sm74w1b-rust-default-1.89.0/lib/rustlib/src/rust/library/std/src/thread/local.rs:281
fatal runtime error: thread local panicked on drop, aborting
Aborted                    (core dumped) cargo run -- -m webgpu-min.js

Expected behavior

The code should run without panic (similar code passes on rquickjs).

// the rquickjs version pass
#[derive(Trace, JsLifetime, Debug, Clone)]
#[rquickjs::class(rename = "GPUDevice", rename_all = "camelCase")]
pub struct Device<'js> {
    #[qjs(skip_trace)]
    pub inner: wgpu::Device,
    #[qjs(get)]
    pub queue: Value<'js>,
}

So I assume things may work differently in boa_gc. But there is too little documentation on how to play with it.

Build environment

  • OS: NixOS Linux
  • Version: 25.05
  • Target triple: x86_64-unknown-linux-gnu
  • Rustc version: rustc 1.89.0 (29483883e 2025-08-04)

Additional context

To make GPUDevice.queue always return the same object (it's a property rather than getter, not different new JsValue each time), I wrap the queue into a JsValue rather than kept as raw wgpu struct.

https://github.com/xubaiwang/boa/blob/5444f706ea9da0b559fb197bd4a3796417fcea86/core/runtime/src/webgpu/device.rs#L17

#[derive(Debug, Clone, JsData, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
pub struct Device {
    /// Inner wgpu handle.
    #[unsafe_ignore_trace]
    pub inner: wgpu::Device,
    /// Returns the primary `GPUQueue` for the device.
    pub queue: JsValue,  // <- Value wrapped rather than `Queue` or `wgpu::Queue`
}

#[derive(Debug, JsData, Trace, Finalize)]
#[boa_gc(unsafe_no_drop)]
pub struct Queue {
    /// Inner wgpu handle.
    #[unsafe_ignore_trace]
    pub inner: wgpu::Queue,
}

xbwwj avatar Aug 08 '25 13:08 xbwwj

Tried #4410 here. Still now working, waiting for further improvement on boa gc

xbwwj avatar Sep 10 '25 04:09 xbwwj