use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use super::StreamId;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Stream {
id: StreamId,
}
impl Stream {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
id: StreamId::allocate(),
}
}
pub const fn from_id(id: StreamId) -> Self {
Self { id }
}
pub fn id(&self) -> StreamId {
self.id
}
pub fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
self.id.executes(f)
}
pub fn attach<F: Future>(&self, fut: F) -> StreamFuture<F> {
StreamFuture {
id: self.id,
inner: fut,
}
}
}
pub struct StreamFuture<F> {
id: StreamId,
inner: F,
}
impl<F: Future> Future for StreamFuture<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let (id, inner) = unsafe {
let this = self.get_unchecked_mut();
(this.id, Pin::new_unchecked(&mut this.inner))
};
id.executes(|| inner.poll(cx))
}
}
#[cfg(multi_threading)]
impl Stream {
pub fn spawn<T, F>(f: F) -> StreamJoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
let stream = Self::new();
let id = stream.id;
let handle = std::thread::spawn(move || id.executes(f));
StreamJoinHandle { stream, handle }
}
}
#[cfg(multi_threading)]
#[derive(Debug)]
pub struct StreamJoinHandle<T> {
stream: Stream,
handle: std::thread::JoinHandle<T>,
}
#[cfg(multi_threading)]
impl<T> StreamJoinHandle<T> {
pub fn stream(&self) -> Stream {
self.stream
}
pub fn join(self) -> std::thread::Result<T> {
self.handle.join()
}
}
#[cfg(tokio_rt)]
impl Stream {
pub fn spawn_task<F>(fut: F) -> (Stream, tokio::task::JoinHandle<F::Output>)
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let stream = Self::new();
(stream, tokio::spawn(stream.attach(fut)))
}
}
pub fn spawn_detached(fut: impl Future<Output = ()> + Send + 'static) -> Stream {
let stream = Stream::new();
crate::future::spawn_detached(stream.attach(fut));
stream
}
#[cfg(all(test, multi_threading))]
mod tests {
use super::*;
#[test]
fn enter_pins_the_stream() {
let stream = Stream::new();
let current = stream.enter(StreamId::current);
assert_eq!(current, stream.id());
}
#[test]
fn spawn_runs_on_its_own_stream() {
let handle = Stream::spawn(StreamId::current);
let expected = handle.stream().id();
assert_eq!(handle.join().unwrap(), expected);
}
}
#[cfg(all(test, tokio_rt))]
mod tests_tokio {
use super::*;
use crate::stream::StreamPolicy;
use alloc::vec::Vec;
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn attach_keeps_stream_across_awaits() {
let stream = Stream::new();
let id = stream.id();
let checks = stream.attach(async move {
for _ in 0..32 {
assert_eq!(StreamId::current(), id);
tokio::task::yield_now().await;
}
});
tokio::spawn(checks).await.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
#[allow(clippy::await_holding_lock)]
async fn per_task_ids_are_stable_and_distinct() {
let _guard = crate::stream::tests_policy_lock();
crate::stream::set_policy(StreamPolicy::PerTask);
let mut handles = Vec::new();
for _ in 0..8 {
handles.push(tokio::spawn(async {
let first = StreamId::current();
for _ in 0..32 {
tokio::task::yield_now().await;
assert_eq!(StreamId::current(), first);
}
first
}));
}
let mut ids = Vec::new();
for handle in handles {
ids.push(handle.await.unwrap());
}
ids.sort();
ids.dedup();
assert_eq!(ids.len(), 8, "each task should get its own stream id");
crate::stream::tests_reset_policy();
}
}