#[cfg(stream_local)]
use core::cell::Cell;
#[cfg(stream_local)]
use core::sync::atomic::AtomicU64;
#[cfg(stream_local)]
use super::StreamPolicy;
#[derive(
Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub struct StreamId {
pub value: u64,
}
#[cfg(stream_local)]
static STREAM_COUNT: AtomicU64 = AtomicU64::new(0);
#[cfg(stream_local)]
std::thread_local! {
static OVERRIDE: Cell<Option<u64>> = const { Cell::new(None) };
static DEFAULT: Cell<Option<u64>> = const { Cell::new(None) };
}
#[cfg(stream_local)]
pub(crate) fn set_override(value: Option<u64>) -> Option<u64> {
OVERRIDE.with(|cell| cell.replace(value))
}
impl StreamId {
pub fn executes<F, T>(self, f: F) -> T
where
F: FnOnce() -> T,
{
#[cfg(stream_local)]
{
struct Guard(Option<u64>);
impl Drop for Guard {
fn drop(&mut self) {
set_override(self.0);
}
}
let _guard = Guard(set_override(Some(self.value)));
f()
}
#[cfg(not(stream_local))]
f()
}
pub fn current() -> Self {
#[cfg(stream_local)]
{
if let Some(value) = OVERRIDE.with(|cell| cell.get()) {
return Self { value };
}
match super::policy() {
StreamPolicy::Single => Self { value: 0 },
StreamPolicy::PerTask => Self::per_task(),
StreamPolicy::PerThread => Self::per_thread(),
}
}
#[cfg(not(stream_local))]
Self { value: 0 }
}
pub fn allocate() -> Self {
#[cfg(stream_local)]
{
Self {
value: STREAM_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed),
}
}
#[cfg(not(stream_local))]
Self { value: 0 }
}
#[cfg(stream_local)]
fn per_thread() -> Self {
DEFAULT.with(|cell| match cell.get() {
Some(value) => Self { value },
None => {
let new = Self::allocate();
cell.set(Some(new.value));
new
}
})
}
#[cfg(all(stream_local, tokio_rt))]
fn per_task() -> Self {
match tokio::task::try_id() {
Some(id) => {
use core::hash::BuildHasher;
let hash = foldhash::fast::FixedState::default().hash_one(id);
Self {
value: hash | (1 << 63),
}
}
None => Self::per_thread(),
}
}
#[cfg(all(stream_local, not(tokio_rt)))]
fn per_task() -> Self {
#[cfg(feature = "std")]
{
use std::sync::Once;
static WARN: Once = Once::new();
WARN.call_once(|| {
log::warn!(
"Stream policy 'per-task' requires the 'tokio' feature of cubecl-environment; falling back to 'per-thread'."
);
});
}
Self::per_thread()
}
}
impl core::fmt::Display for StreamId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!("StreamId({:?})", self.value))
}
}
#[cfg(all(test, stream_local))]
mod tests {
use super::*;
#[test]
fn executes_restores_previous_override() {
let outer = StreamId { value: 1_000_000 };
let inner = StreamId { value: 2_000_000 };
outer.executes(|| {
assert_eq!(StreamId::current(), outer);
inner.executes(|| {
assert_eq!(StreamId::current(), inner);
});
assert_eq!(StreamId::current(), outer);
});
}
#[test]
fn executes_restores_no_override_state() {
let scoped = StreamId { value: 500_000 };
scoped.executes(|| {
assert_eq!(StreamId::current(), scoped);
});
assert_eq!(OVERRIDE.with(|cell| cell.get()), None);
assert_ne!(StreamId::current(), scoped);
}
#[test]
fn current_is_stable_on_one_thread() {
let _guard = crate::stream::tests_policy_lock();
assert_eq!(StreamId::current(), StreamId::current());
}
#[test]
fn allocate_returns_distinct_ids() {
assert_ne!(StreamId::allocate(), StreamId::allocate());
}
}