cubecl_environment/stream/
id.rs1#[cfg(stream_local)]
4use core::cell::Cell;
5#[cfg(stream_local)]
6use core::sync::atomic::AtomicU64;
7
8#[cfg(stream_local)]
9use super::StreamPolicy;
10
11#[derive(
17 Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
18)]
19pub struct StreamId {
20 pub value: u64,
22}
23
24#[cfg(stream_local)]
25static STREAM_COUNT: AtomicU64 = AtomicU64::new(0);
26
27#[cfg(stream_local)]
28std::thread_local! {
29 static OVERRIDE: Cell<Option<u64>> = const { Cell::new(None) };
31 static DEFAULT: Cell<Option<u64>> = const { Cell::new(None) };
33}
34
35#[cfg(stream_local)]
42pub(crate) fn set_override(value: Option<u64>) -> Option<u64> {
43 OVERRIDE.with(|cell| cell.replace(value))
44}
45
46impl StreamId {
47 pub fn executes<F, T>(self, f: F) -> T
53 where
54 F: FnOnce() -> T,
55 {
56 #[cfg(stream_local)]
57 {
58 struct Guard(Option<u64>);
59
60 impl Drop for Guard {
61 fn drop(&mut self) {
62 set_override(self.0);
63 }
64 }
65
66 let _guard = Guard(set_override(Some(self.value)));
67 f()
68 }
69
70 #[cfg(not(stream_local))]
71 f()
72 }
73
74 pub fn current() -> Self {
83 #[cfg(stream_local)]
84 {
85 if let Some(value) = OVERRIDE.with(|cell| cell.get()) {
86 return Self { value };
87 }
88
89 match super::policy() {
90 StreamPolicy::Single => Self { value: 0 },
91 StreamPolicy::PerTask => Self::per_task(),
92 StreamPolicy::PerThread => Self::per_thread(),
93 }
94 }
95
96 #[cfg(not(stream_local))]
97 Self { value: 0 }
98 }
99
100 pub fn allocate() -> Self {
104 #[cfg(stream_local)]
105 {
106 Self {
107 value: STREAM_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed),
108 }
109 }
110
111 #[cfg(not(stream_local))]
112 Self { value: 0 }
113 }
114
115 #[cfg(stream_local)]
116 fn per_thread() -> Self {
117 DEFAULT.with(|cell| match cell.get() {
118 Some(value) => Self { value },
119 None => {
120 let new = Self::allocate();
121 cell.set(Some(new.value));
122 new
123 }
124 })
125 }
126
127 #[cfg(all(stream_local, tokio_rt))]
128 fn per_task() -> Self {
129 match tokio::task::try_id() {
130 Some(id) => {
131 use core::hash::BuildHasher;
132
133 let hash = foldhash::fast::FixedState::default().hash_one(id);
134
135 Self {
141 value: hash | (1 << 63),
142 }
143 }
144 None => Self::per_thread(),
146 }
147 }
148
149 #[cfg(all(stream_local, not(tokio_rt)))]
150 fn per_task() -> Self {
151 #[cfg(feature = "std")]
152 {
153 use std::sync::Once;
154
155 static WARN: Once = Once::new();
156 WARN.call_once(|| {
157 log::warn!(
158 "Stream policy 'per-task' requires the 'tokio' feature of cubecl-environment; falling back to 'per-thread'."
159 );
160 });
161 }
162
163 Self::per_thread()
164 }
165}
166
167impl core::fmt::Display for StreamId {
168 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
169 f.write_fmt(format_args!("StreamId({:?})", self.value))
170 }
171}
172
173#[cfg(all(test, stream_local))]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn executes_restores_previous_override() {
179 let outer = StreamId { value: 1_000_000 };
180 let inner = StreamId { value: 2_000_000 };
181
182 outer.executes(|| {
183 assert_eq!(StreamId::current(), outer);
184 inner.executes(|| {
185 assert_eq!(StreamId::current(), inner);
186 });
187 assert_eq!(StreamId::current(), outer);
188 });
189 }
190
191 #[test]
192 fn executes_restores_no_override_state() {
193 let scoped = StreamId { value: 500_000 };
196
197 scoped.executes(|| {
198 assert_eq!(StreamId::current(), scoped);
199 });
200
201 assert_eq!(OVERRIDE.with(|cell| cell.get()), None);
202 assert_ne!(StreamId::current(), scoped);
203 }
204
205 #[test]
206 fn current_is_stable_on_one_thread() {
207 let _guard = crate::stream::tests_policy_lock();
208
209 assert_eq!(StreamId::current(), StreamId::current());
210 }
211
212 #[test]
213 fn allocate_returns_distinct_ids() {
214 assert_ne!(StreamId::allocate(), StreamId::allocate());
215 }
216}