1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Per-device serialization of wgpu's error-scope stack.
//
// `wgpu::Device::push_error_scope` / `pop_error_scope` operate on a
// **per-device, strictly-LIFO** stack. When two threads share ONE
// `wgpu::Device` and each brackets an allocation in its own
// `push → create → pop` triple, the calls interleave and wgpu aborts with
// "Mismatched pop_error_scope call: error scopes must be popped in reverse
// order". The hazard is per *device*, not per *pool* / per *call site*:
// every `push/pop` pair anywhere in the process that targets a given device
// must be mutually exclusive with every other pair on that same device.
//
// This module owns the one process-global registry that makes that true.
// `with_error_scope` keys a `Mutex<()>` on the `wgpu::Device`'s identity —
// wgpu's handle `Hash`, which every clone of one device shares (a
// `wgpu::Device` is itself a shared handle, so keying on an outer `Arc`'s
// address would split one device across two locks) — while independent
// devices get independent locks, so an N-device pipeline still allocates
// on each device concurrently.
//
// wgpu keeps the stack thread-local only with its `std` feature. Without it
// (a `default-features = false` build, which is how this crate depends on
// wgpu) one stack is shared by all threads, so the hazard is real for any
// two threads touching the device.
//
// Every scoped allocation on a device that can be shared must route through
// here: a frame pool or transient allocator that a shared render device
// drives from every render thread, an interop layer's output pools. It
// lives in this low-level crate for exactly that reason — one registry
// every such caller can reach. Any error-scope call site that can run on a
// SHARED device must route through here too.
use HashMap;
use ;
use ;
/// Process-global registry: device-identity → that device's error-scope
/// lock. The outer `Mutex` guards only the map (held for a hash lookup /
/// insert, never across GPU work); the inner `Arc<Mutex<()>>` is the
/// per-device lock held across the `push → create → pop` triple.
/// The error-scope lock for `device`, creating it on first use. Keyed on the
/// device's identity hash: every clone of one `wgpu::Device` maps to the same
/// lock. A 64-bit collision between two distinct devices would only make
/// them share a lock — extra serialization, never a missed one.
/// Run `f` (an allocation already bracketed by `push_error_scope` /
/// `pop_error_scope` on `device`) while holding `device`'s process-global
/// error-scope lock, so the push/pop triple cannot interleave with a
/// concurrent triple on the same device.
///
/// The lock guards a `()` with no invariant a panic can corrupt, so a
/// poisoned lock is recovered (the next caller proceeds) rather than
/// propagated. The lock is held only for the microsecond-scale push/create/pop
/// window — never across blocking GPU work — so peers on the *same* device
/// serialize their allocations (already serial in the driver) while peers on
/// *other* devices are unaffected.