use core::sync::atomic::{AtomicBool, Ordering};
use crate::error::Error;
static TAKEN: AtomicBool = AtomicBool::new(false);
#[derive(Debug)]
pub struct Claim {
_private: (),
}
impl Claim {
#[cfg_attr(
not(bela_device),
allow(
dead_code,
reason = "only the device-gated audio system takes it; still unit-tested on the host"
)
)]
pub fn take() -> Result<Self, Error> {
TAKEN
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.map(|_| Self { _private: () })
.map_err(|_| Error::AudioSystemExists)
}
}
impl Drop for Claim {
fn drop(&mut self) {
TAKEN.store(false, Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use std::panic::catch_unwind;
use std::sync::{Mutex, PoisonError};
use std::thread;
use super::*;
static SERIALISE: Mutex<()> = Mutex::new(());
fn serialised() -> impl Drop {
SERIALISE.lock().unwrap_or_else(PoisonError::into_inner)
}
#[test]
fn only_one_claim_exists_at_a_time() {
let _order = serialised();
let first = Claim::take().expect("the claim should be free");
assert_eq!(
Claim::take().unwrap_err(),
Error::AudioSystemExists,
"a second audio system must be refused"
);
drop(first);
Claim::take().expect("dropping the first should have freed the claim");
}
#[test]
fn another_thread_is_refused_too() {
let _order = serialised();
let held = Claim::take().expect("the claim should be free");
let from_other_thread = thread::spawn(|| Claim::take().map(|_| ()))
.join()
.expect("the thread should not panic");
assert_eq!(
from_other_thread.unwrap_err(),
Error::AudioSystemExists,
"the claim is process-wide, not per-thread"
);
drop(held);
}
#[test]
fn a_claim_dropped_while_unwinding_is_released() {
let _order = serialised();
let panicked = catch_unwind(|| {
let _claim = Claim::take().expect("the claim should be free");
panic!("something went wrong during setup");
});
assert!(panicked.is_err(), "the panic should propagate");
Claim::take().expect("a panic must not leave the claim held forever");
}
}