1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
45use super::super::common::TaskHandle;
6use super::{AtomicFutureHandle, Bomb, Meta, DONE, RESULT_TAKEN};
7use crate::scope::Spawnable;
8use crate::ScopeHandle;
9use std::future::Future;
10use std::marker::PhantomData;
11use std::pin::Pin;
12use std::task::{ready, Context, Poll};
1314/// `SpawnableFuture` is a boxed future that can be spawned without incurring any more allocations
15/// i.e. it doesn't end up with the double boxing that you end up with if you try and spawn `Box<dyn
16/// Future>`. It can be used in place of `BoxFuture` although it carries more overhead than
17/// `BoxFuture`, so it shouldn't be used if it isn't going to be spawned on a scope.
18/// `SpawnableFuture` implements `Future` but the future will not be running as a separate task if
19/// used this way. If polled and then later spawned, the spawned task will be polled again and any
20/// waker recorded when polled prior to spawning will be impotent.
21pub struct SpawnableFuture<'a, O>(AtomicFutureHandle<'a>, PhantomData<O>);
2223impl<O> Unpin for SpawnableFuture<'_, O> {}
2425impl<'a, O> SpawnableFuture<'a, O> {
26/// Creates a new spawnable future. To spawn the future on a scope, use either `spawn_on` or
27 /// `compute_on`.
28pub fn new<F: Future<Output = O> + Send + 'a>(future: F) -> Self
29where
30O: Send + 'a,
31 {
32Self(AtomicFutureHandle::new(None, 0, future), PhantomData)
33 }
3435fn meta(&mut self) -> &mut Meta {
36// SAFETY: This is safe because we know there is only one reference to the handle.
37unsafe { &mut *self.0 .0.as_mut() }
38 }
39}
4041impl<O> Spawnable for SpawnableFuture<'static, O> {
42type Output = O;
4344fn into_task(mut self, scope: ScopeHandle) -> TaskHandle {
45let meta = self.meta();
46 meta.id = scope.executor().next_task_id();
47 meta.scope = Some(scope);
48self.0
49}
50}
5152// This is intentionally &mut as otherwise we can't have a specialized implementation of
53// `Spawnable`. This should work just the same because of Rust's autoref behaviour.
54impl<O> Future for &mut SpawnableFuture<'_, O> {
55type Output = O;
5657fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
58// We cannot recover from panics.
59let bomb = Bomb;
6061let meta = self.meta();
62let result = unsafe { (meta.vtable.poll)(meta.into(), cx) };
6364 std::mem::forget(bomb);
6566ready!(result);
6768let result = unsafe { ((meta.vtable.get_result)(meta.into()) as *const O).read() };
69*meta.state.get_mut() = DONE | RESULT_TAKEN;
7071 Poll::Ready(result)
72 }
73}
7475#[cfg(test)]
76mod tests {
77use super::SpawnableFuture;
78use crate::{Scope, SendExecutor};
79use std::future::poll_fn;
80use std::sync::atomic::AtomicU64;
81use std::sync::atomic::Ordering::Relaxed;
82use std::sync::Arc;
83use std::task::Poll;
8485#[test]
86fn test_spawnable_future() {
87let mut executor = SendExecutor::new(2);
88 executor.run(async move {
89let counter = Arc::new(AtomicU64::new(0));
90let counter2 = Arc::clone(&counter);
91let mut task1 = SpawnableFuture::new(async move {
92let () = poll_fn(|_cx| {
93if counter2.fetch_add(1, Relaxed) == 1 {
94 Poll::Ready(())
95 } else {
96 Poll::Pending
97 }
98 })
99 .await;
100 });
101let old = counter.load(Relaxed);
102assert!(futures::poll!(&mut task1).is_pending());
103assert_eq!(counter.load(Relaxed), old + 1);
104105 Scope::current().spawn(task1).await;
106107assert_eq!(counter.load(Relaxed), old + 2);
108 });
109 }
110}