Thread Local Storage AccessError when using wgpu structs from within JsValue
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.
#[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,
}
Tried #4410 here. Still now working, waiting for further improvement on boa gc