cubecl_environment/stream/
handle.rs1use core::future::Future;
2use core::pin::Pin;
3use core::task::{Context, Poll};
4
5use super::StreamId;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub struct Stream {
20 id: StreamId,
21}
22
23impl Stream {
24 #[allow(clippy::new_without_default)]
26 pub fn new() -> Self {
27 Self {
28 id: StreamId::allocate(),
29 }
30 }
31
32 pub const fn from_id(id: StreamId) -> Self {
34 Self { id }
35 }
36
37 pub fn id(&self) -> StreamId {
39 self.id
40 }
41
42 pub fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
45 self.id.executes(f)
46 }
47
48 pub fn attach<F: Future>(&self, fut: F) -> StreamFuture<F> {
54 StreamFuture {
55 id: self.id,
56 inner: fut,
57 }
58 }
59}
60
61pub struct StreamFuture<F> {
63 id: StreamId,
64 inner: F,
65}
66
67impl<F: Future> Future for StreamFuture<F> {
68 type Output = F::Output;
69
70 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
71 let (id, inner) = unsafe {
74 let this = self.get_unchecked_mut();
75 (this.id, Pin::new_unchecked(&mut this.inner))
76 };
77 id.executes(|| inner.poll(cx))
78 }
79}
80
81#[cfg(multi_threading)]
82impl Stream {
83 pub fn spawn<T, F>(f: F) -> StreamJoinHandle<T>
88 where
89 F: FnOnce() -> T + Send + 'static,
90 T: Send + 'static,
91 {
92 let stream = Self::new();
93 let id = stream.id;
94 let handle = std::thread::spawn(move || id.executes(f));
95
96 StreamJoinHandle { stream, handle }
97 }
98}
99
100#[cfg(multi_threading)]
102#[derive(Debug)]
103pub struct StreamJoinHandle<T> {
104 stream: Stream,
105 handle: std::thread::JoinHandle<T>,
106}
107
108#[cfg(multi_threading)]
109impl<T> StreamJoinHandle<T> {
110 pub fn stream(&self) -> Stream {
112 self.stream
113 }
114
115 pub fn join(self) -> std::thread::Result<T> {
117 self.handle.join()
118 }
119}
120
121#[cfg(tokio_rt)]
122impl Stream {
123 pub fn spawn_task<F>(fut: F) -> (Stream, tokio::task::JoinHandle<F::Output>)
128 where
129 F: Future + Send + 'static,
130 F::Output: Send + 'static,
131 {
132 let stream = Self::new();
133 (stream, tokio::spawn(stream.attach(fut)))
134 }
135}
136
137pub fn spawn_detached(fut: impl Future<Output = ()> + Send + 'static) -> Stream {
141 let stream = Stream::new();
142 crate::future::spawn_detached(stream.attach(fut));
143 stream
144}
145
146#[cfg(all(test, multi_threading))]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn enter_pins_the_stream() {
152 let stream = Stream::new();
153 let current = stream.enter(StreamId::current);
154 assert_eq!(current, stream.id());
155 }
156
157 #[test]
158 fn spawn_runs_on_its_own_stream() {
159 let handle = Stream::spawn(StreamId::current);
160 let expected = handle.stream().id();
161 assert_eq!(handle.join().unwrap(), expected);
162 }
163}
164
165#[cfg(all(test, tokio_rt))]
166mod tests_tokio {
167 use super::*;
168 use crate::stream::StreamPolicy;
169 use alloc::vec::Vec;
170
171 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
172 async fn attach_keeps_stream_across_awaits() {
173 let stream = Stream::new();
174 let id = stream.id();
175
176 let checks = stream.attach(async move {
177 for _ in 0..32 {
178 assert_eq!(StreamId::current(), id);
179 tokio::task::yield_now().await;
180 }
181 });
182
183 tokio::spawn(checks).await.unwrap();
184 }
185
186 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
187 #[allow(clippy::await_holding_lock)]
192 async fn per_task_ids_are_stable_and_distinct() {
193 let _guard = crate::stream::tests_policy_lock();
194
195 crate::stream::set_policy(StreamPolicy::PerTask);
196
197 let mut handles = Vec::new();
198 for _ in 0..8 {
199 handles.push(tokio::spawn(async {
200 let first = StreamId::current();
201 for _ in 0..32 {
202 tokio::task::yield_now().await;
203 assert_eq!(StreamId::current(), first);
204 }
205 first
206 }));
207 }
208
209 let mut ids = Vec::new();
210 for handle in handles {
211 ids.push(handle.await.unwrap());
212 }
213 ids.sort();
214 ids.dedup();
215 assert_eq!(ids.len(), 8, "each task should get its own stream id");
216
217 crate::stream::tests_reset_policy();
218 }
219}