#[cfg(not(target_arch = "wasm32"))]
use std::sync::{Arc, atomic::{AtomicBool, AtomicU64, Ordering}};
#[cfg(not(target_arch = "wasm32"))]
use parking_lot::Mutex;
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RuntimeMetricId(u64);
#[cfg(not(target_arch = "wasm32"))]
impl RuntimeMetricId {
pub const fn as_u64(self) -> u64 {
self.0
}
}
#[cfg(not(target_arch = "wasm32"))]
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
#[cfg(not(target_arch = "wasm32"))]
fn allocate_id(counter: &AtomicU64) -> Option<RuntimeMetricId> {
counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed,
|next| if next != 0 && next <= i64::MAX as u64 { Some(next + 1) } else { None })
.ok().map(RuntimeMetricId)
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) struct RuntimeMetricIdentitySlot(Mutex<Option<Arc<RuntimeMetricIdentity>>>);
#[cfg(not(target_arch = "wasm32"))]
impl RuntimeMetricIdentitySlot {
pub(crate) const fn new() -> Self {
Self(Mutex::new(None))
}
pub(crate) fn get_or_allocate(&self) -> Option<Arc<RuntimeMetricIdentity>> {
let mut slot = self.0.lock();
if slot.is_none() {
let id = allocate_id(&NEXT_ID)?;
*slot = Some(Arc::new(RuntimeMetricIdentity {
id,
in_flight: AtomicBool::new(false),
}));
}
slot.as_ref().cloned()
}
}
#[cfg(target_arch = "wasm32")]
pub(crate) struct RuntimeMetricIdentitySlot;
#[cfg(target_arch = "wasm32")]
impl RuntimeMetricIdentitySlot {
pub(crate) const fn new() -> Self { Self }
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) struct RuntimeMetricIdentity {
id: RuntimeMetricId,
in_flight: AtomicBool,
}
#[cfg(not(target_arch = "wasm32"))]
impl RuntimeMetricIdentity {
pub(crate) fn id(&self) -> RuntimeMetricId { self.id }
pub(crate) fn try_claim(self: &Arc<Self>) -> Option<RuntimeMetricRequestPermit> {
self.in_flight.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.ok().map(|_| RuntimeMetricRequestPermit(self.clone()))
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) struct RuntimeMetricRequestPermit(Arc<RuntimeMetricIdentity>);
#[cfg(not(target_arch = "wasm32"))]
impl Drop for RuntimeMetricRequestPermit {
fn drop(&mut self) {
self.0.in_flight.store(false, Ordering::Release);
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
use std::{collections::BTreeSet, sync::Barrier, thread};
#[test]
fn test_identity_allocation_boundaries_and_shared_slot() {
let counter = AtomicU64::new(i64::MAX as u64 - 1);
assert_eq!(allocate_id(&counter).unwrap().as_u64(), i64::MAX as u64 - 1);
assert_eq!(allocate_id(&counter).unwrap().as_u64(), i64::MAX as u64);
for _ in 0..3 { assert!(allocate_id(&counter).is_none()); }
assert_eq!(counter.load(Ordering::Relaxed), i64::MAX as u64 + 1);
for invalid in [0, u64::MAX] {
let counter = AtomicU64::new(invalid);
assert!(allocate_id(&counter).is_none());
assert_eq!(counter.load(Ordering::Relaxed), invalid);
}
let slot = Arc::new(RuntimeMetricIdentitySlot::new());
let threads: Vec<_> = (0..8).map(|_| {
let slot = slot.clone();
thread::spawn(move || slot.get_or_allocate().unwrap().id())
}).collect();
let ids: BTreeSet<_> = threads.into_iter().map(|thread| thread.join().unwrap()).collect();
assert_eq!(ids.len(), 1);
let first = slot.get_or_allocate().unwrap();
assert!(Arc::ptr_eq(&first, &slot.get_or_allocate().unwrap()));
let second = RuntimeMetricIdentitySlot::new().get_or_allocate().unwrap();
assert_ne!(first.id(), second.id());
}
#[test]
fn test_request_permit_exactly_one_and_unwind_release() {
let identity = RuntimeMetricIdentitySlot::new().get_or_allocate().unwrap();
let start = Arc::new(Barrier::new(8));
let attempted = Arc::new(Barrier::new(8));
let threads: Vec<_> = (0..8).map(|_| {
let (identity, start, attempted) = (identity.clone(), start.clone(), attempted.clone());
thread::spawn(move || {
start.wait();
let permit = identity.try_claim();
attempted.wait();
permit.is_some()
})
}).collect();
assert_eq!(threads.into_iter().map(|thread| usize::from(thread.join().unwrap())).sum::<usize>(), 1);
let failed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _permit = identity.try_claim().unwrap();
assert!(identity.try_claim().is_none());
panic!("仅验证许可展开释放");
}));
assert!(failed.is_err());
assert!(identity.try_claim().is_some());
}
}