---
# System prepended metadata

title: Multiple components cannot effectively share a GPUDevice because it is stateful

---

<!-- markdownlint-disable MD013 -->

# Multiple components cannot effectively share a GPUDevice because it is stateful

Issue: <https://github.com/gpuweb/gpuweb/issues/3250>

Proposal written and proposed by Kai, based on in-depth discussion with editors (Brandon/Myles/Kai) and more input from Brandon.

[**Previous drafts**](https://hackmd.io/@webgpu/SkRjVIcjc)

## Premises

- It should be possible for multiple "components" using the same device on the same thread to have different error scope state.
- It's okay if each component can't device.destroy() independently.
  - The per-component device should be a lightweight object that can be handled by GC.
  - This avoids difficult-to-understand tracking of which resources would get destroyed upon destroying one component's device.
- Error scope state should not be shared across threads by default, as it would be a footgun.
  - For implementing native semantics over JS, it is useful for there to be a way for a device's error state to be shared across multiple threads.

## Proposal
  
(Names are all placeholders.)

- All objects have an `[[errorTarget]]`, which (roughly speaking) has type `GPUDevice?`.
  - This means `createView()`, for example, does not have to move.
- The only pre-V1 change to today's API is the addition of `GPURootDevice`. (I have long expected we would want this - I just didn't realize it was a breaking change.)
  - There is only one `uncapturederror` `EventTarget` object for the device (`GPURootDevice`) and it's never possible to duplicate or transfer it. This approach is chosen so `[[errorTarget]]` can be nullable.
- When an object is sent to another thread, the error target does not come with it; `[[errorTarget]]` is `null`. Errors that happen on this object get sent straight to `uncapturederror`. To get them to go to an error target, you have to "clone" the object and specify an `errorTarget` to use.
- `GPUDevice` **is** the error target and can never have a null error target.
  - When you send it to another thread you have to explicitly choose whether it's going to share its error state tracker or get a new one.

### Pre-V1

Changes:

- ~~Add a `GPURootDevice` subclass of `GPUDevice`. Move `EventTarget` to `GPURootDevice`. Return `GPURootDevice` from `requestDevice()`.~~
  - ~~This is done now only because the addition of a new object in the prototype chain is observable. This was planned for multi-threading anyway.~~
  - **This is impossible because it requires multiple inheritance (`GPURootDevice : GPUDevice, EventTarget`).**

Editorial changes:

- `GPUObjectBase` gets an internal slot `[[errorTarget]]`, pointing to a "device client" (a thing which can send operations to a device).
- A new object created from any object `this` inherits `this.[[errorTarget]]`.
- All operations send their errors to `(GPUObjectBase)this`, which passes them through `[[errorTarget]]` and forwards uncaptured errors to `uncapturederror`.

Notes:

- There is exactly one error target per device, and not yet any way to override `[[errorTarget]]`. No significant implementation work is needed at this stage.

### Post-V1: Multi-Client

Changes:

- Most objects gain a method `object.withErrorTarget(GPUDevice errorTarget)`, which gives you a new JS object with the same underlying object and the provided `[[errorTarget]]`.
  - `GPUDevice` and `GPURootDevice` don't have these.
  - Object labels are client-shared state.
- Add `GPUDevice.withNewErrorTracker()` which returns a new `GPUDevice` with a new "device client" with an empty error state tracker.

### Post-V1: Multi-Threading

Changes:

- Make `[[errorTarget]]` nullable.
  - Objects without an `[[errorTarget]]` send errors directly to `uncapturederror`.
- Most objects are thread-shareable. When deserialized, they have a null `[[errorTarget]]`. To associate them with an error target, the receiver must call `withErrorTarget()`.
  - Object labels are thread-shared state.
- When `GPUDevice` or `GPURootDevice` is sent, you receive a `GPUReceivedDevice`. To get a `GPUDevice` from it, you must choose either `.withNewErrorTracker()` or `.withSharedErrorTracker()`. (These can be called multiple times.)
  - You can't get a `GPURootDevice` from it (that object is tied to the original thread).
  - This affects whether the internal `[[errorTarget]]` points to the same device "client" or a new one.

Note:

- Native-style semantics can be implemented with `withSharedErrorTracker()`. This also eliminates a need in the actual native API to use thread-local storage to track which error scope state to target.
  
### Post-V1: Multi-Queue

New independent queue objects behave the same way as other objects.
