use std::sync::atomic::{AtomicU64, Ordering};
static TASK_ID_SEQ: AtomicU64 = AtomicU64::new(1);
#[inline]
fn advance(current: u64) -> Option<u64> {
match current {
0 => None,
u64::MAX => Some(0),
value => Some(value + 1),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TaskId(u64);
impl TaskId {
#[inline]
pub(crate) fn next() -> Self {
let id = TASK_ID_SEQ
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, advance)
.unwrap_or_else(|_| panic!("TaskId space exhausted; identities cannot wrap safely"));
TaskId(id)
}
#[inline]
#[must_use]
pub fn get(self) -> u64 {
self.0
}
#[cfg(feature = "test-util")]
#[cfg_attr(docsrs, doc(cfg(feature = "test-util")))]
#[must_use]
pub fn for_tests() -> Self {
Self::next()
}
}
impl std::fmt::Display for TaskId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "#{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ids_are_nonzero_unique_and_monotonic() {
let a = TaskId::next();
let b = TaskId::next();
assert!(a.get() >= 1, "zero is reserved and must never be minted");
assert!(b.get() > a.get(), "ids must increase: {a} then {b}");
assert_ne!(a, b);
}
#[test]
fn sequence_uses_zero_as_an_exhausted_sentinel() {
assert_eq!(advance(1), Some(2));
assert_eq!(advance(u64::MAX - 1), Some(u64::MAX));
assert_eq!(advance(u64::MAX), Some(0));
assert_eq!(advance(0), None);
}
#[cfg(feature = "test-util")]
#[test]
fn for_tests_draws_from_the_runtime_sequence() {
let runtime = TaskId::next();
let test = TaskId::for_tests();
let runtime_after = TaskId::next();
assert!(test.get() > runtime.get(), "test ids share the sequence");
assert!(runtime_after.get() > test.get(), "no collision is possible");
}
}