#[must_use = "acquisitions are serialized only while the guard lives"]
pub struct AcquisitionGuard {
#[cfg(all(feature = "serial-acquisition", not(target_arch = "wasm32")))]
_held: held::Held,
}
impl std::fmt::Debug for AcquisitionGuard {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("AcquisitionGuard")
}
}
pub fn serialize_acquisition() -> AcquisitionGuard {
AcquisitionGuard {
#[cfg(all(feature = "serial-acquisition", not(target_arch = "wasm32")))]
_held: held::Held::take(),
}
}
#[cfg(all(feature = "serial-acquisition", not(target_arch = "wasm32")))]
mod held {
use std::sync::{Condvar, Mutex, OnceLock, PoisonError};
use std::thread::ThreadId;
static TURN: Mutex<Option<(ThreadId, usize)>> = Mutex::new(None);
static FREED: Condvar = Condvar::new();
static KEPT: OnceLock<wgpu::Instance> = OnceLock::new();
pub(super) struct Held;
impl Held {
pub(super) fn take() -> Self {
let me = std::thread::current().id();
let mut turn = TURN.lock().unwrap_or_else(PoisonError::into_inner);
loop {
match &mut *turn {
None => {
*turn = Some((me, 1));
break;
}
Some((owner, count)) if *owner == me => {
*count += 1;
return Self;
}
Some(_) => turn = FREED.wait(turn).unwrap_or_else(PoisonError::into_inner),
}
}
drop(turn);
KEPT.get_or_init(|| {
wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env())
});
Self
}
}
impl Drop for Held {
fn drop(&mut self) {
let mut turn = TURN.lock().unwrap_or_else(PoisonError::into_inner);
if let Some((_, count)) = &mut *turn {
*count -= 1;
if *count == 0 {
*turn = None;
FREED.notify_all();
}
}
}
}
}
#[cfg(all(test, feature = "serial-acquisition", not(target_arch = "wasm32")))]
mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use super::serialize_acquisition;
#[test]
fn one_thread_holds_the_turn_at_a_time_and_may_take_it_again() {
let inside = Arc::new(AtomicUsize::new(0));
let threads: Vec<_> = (0..8)
.map(|_| {
let inside = Arc::clone(&inside);
std::thread::spawn(move || {
for _ in 0..50 {
let _outer = serialize_acquisition();
let _inner = serialize_acquisition();
assert_eq!(inside.fetch_add(1, Ordering::SeqCst), 0);
std::thread::yield_now();
inside.fetch_sub(1, Ordering::SeqCst);
}
})
})
.collect();
for thread in threads {
thread.join().expect("no two threads held the turn");
}
}
}