Skip to main content

dope_fiber/task/batch/
join.rs

1use core::{convert, pin, task::Poll};
2
3use o3::depot::array;
4
5use crate::{
6    abi, context,
7    context::wake,
8    core::driver::schedule::{
9        app,
10        ready::{
11            task,
12            task::{pool, topology},
13        },
14    },
15    task::{
16        batch::{Completed, Error, Index, Pool, RootError, Tag, state},
17        storage::slots,
18    },
19};
20
21const LIMIT: usize = 32;
22
23enum Progress<const N: usize> {
24    Done(Completed<N>),
25    More,
26    Parked,
27}
28
29/// One ordered, fixed-capacity group of homogeneous fibers.
30#[pin_project::pin_project(PinnedDrop)]
31#[must_use = "a fiber does nothing unless it is driven"]
32pub struct Join<'pool, 'd, F, const N: usize>
33where
34    F: abi::Fiber<'d>,
35{
36    #[pin]
37    table: slots::Table<'d, F, F::Output, N>,
38    pool: &'pool Pool<'d, N>,
39    state: state::State<N>,
40    root: Option<pool::Root<'d>>,
41}
42
43impl<'pool, 'd, F, const N: usize> Join<'pool, 'd, F, N>
44where
45    F: abi::Fiber<'d>,
46{
47    /// Builds an empty fixed-capacity batch.
48    pub fn new(pool: &'pool mut Pool<'d, N>) -> Self {
49        Self::from_table(pool, slots::Table::vacant(), array::Len::ZERO)
50    }
51
52    /// Builds a batch containing every fiber in array order.
53    pub fn from_array(pool: &'pool mut Pool<'d, N>, fibers: [F; N]) -> Self {
54        Self::from_map(pool, fibers, convert::identity)
55    }
56
57    /// Consumes each input in array order, constructing every fiber before returning.
58    pub fn from_map<P>(
59        pool: &'pool mut Pool<'d, N>,
60        input: [P; N],
61        make: impl FnMut(P) -> F,
62    ) -> Self {
63        Self::from_table(pool, slots::Table::from_map(input, make), array::Len::MAX)
64    }
65
66    /// Builds a full batch without materializing an intermediate fiber array.
67    pub fn from_fn(pool: &'pool mut Pool<'d, N>, make: impl FnMut(Index<N>) -> F) -> Self {
68        Self::from_table(pool, slots::Table::from_fn(make), array::Len::MAX)
69    }
70
71    fn from_table(
72        pool: &'pool mut Pool<'d, N>,
73        table: slots::Table<'d, F, F::Output, N>,
74        len: array::Len<N>,
75    ) -> Self {
76        Self {
77            table,
78            pool,
79            state: state::State::new(len),
80            root: None,
81        }
82    }
83
84    pub fn try_push(&mut self, fiber: F) -> Result<(), F> {
85        let Some(state) = self.state.vacancy() else {
86            return Err(fiber);
87        };
88        let index = slots::Index::from_vacant(state.slot());
89        // SAFETY: only this owner pairs its table and admitted prefix. Empty
90        // construction initializes every entry as vacant; full construction has
91        // no vacancy. Each insertion fills exactly the next entry and commits
92        // that same prefix. This exclusive borrow cannot overlap a pinned poll,
93        // cancellation, or another insertion.
94        let entry = unsafe { slots::raw::Vacant::new(&mut self.table, index) };
95        entry.insert(fiber);
96        state.commit();
97        Ok(())
98    }
99
100    fn progress(
101        self: pin::Pin<&mut Self>,
102        mut context: pin::Pin<&mut context::Context<'_, 'd>>,
103    ) -> Result<Progress<N>, Error> {
104        let mut this = self.project();
105        if let Some(done) = this.state.completed() {
106            return Ok(Progress::Done(done));
107        }
108
109        let parent: wake::Waker<'d> = context.as_ref().root_waker().into();
110        let pool = *this.pool;
111        let root = match pool.tasks.try_root(parent.0) {
112            Ok(root) => root,
113            Err(pool::RootError::Foreign) => {
114                return Err(Error::Root(RootError::Foreign));
115            }
116            Err(pool::RootError::Invalid(error)) => {
117                return Err(Error::Root(RootError::Invalid(error)));
118            }
119        };
120        match *this.root {
121            None => *this.root = Some(root),
122            Some(active) if active == root => {}
123            Some(active) => {
124                return Err(Error::Root(RootError::Changed {
125                    active: active.id(),
126                    observed: root.id(),
127                }));
128            }
129        }
130
131        let mut budget = LIMIT;
132        let mut initial = true;
133        while budget != 0 {
134            let Some(fresh) = this.state.fresh() else {
135                break;
136            };
137            let index = slots::Index::from_vacant(fresh.slot());
138            let permit = if initial {
139                initial = false;
140                None
141            } else {
142                match context.as_ref().try_continue() {
143                    context::Continue::Permit(permit) => Some(permit),
144                    context::Continue::Exhausted => return Ok(Progress::More),
145                }
146            };
147            let attach = pool.ready.attach(index);
148            let done = poll_slot(
149                this.table.as_mut(),
150                context.as_mut(),
151                index,
152                attach,
153                permit,
154                &pool.tasks,
155                root,
156            )
157            .map_err(Error::Task)?;
158            budget -= 1;
159            if let state::Status::Done(all) = fresh.commit(done) {
160                return Ok(Progress::Done(all));
161            }
162        }
163
164        if budget == 0 {
165            return Ok(if this.state.has_fresh() || !pool.ready.is_empty() {
166                Progress::More
167            } else {
168                Progress::Parked
169            });
170        }
171
172        let Some(mut drain) = pool.ready.drain() else {
173            return Ok(Progress::Parked);
174        };
175        loop {
176            let pending = match this.state.pending() {
177                state::PendingState::Live(pending) => pending,
178                state::PendingState::Done(done) => return Ok(Progress::Done(done)),
179            };
180            let (index, permit) = if initial {
181                let Some(index) = drain.pop() else {
182                    return Ok(Progress::Parked);
183                };
184                initial = false;
185                (index, None)
186            } else {
187                match context.as_ref().work().next(&mut drain) {
188                    app::Next::Item(index, permit) => (index, Some(permit)),
189                    app::Next::Empty => return Ok(Progress::Parked),
190                    app::Next::Exhausted(_) => return Ok(Progress::More),
191                }
192            };
193            let attach = pool.ready.attach(index);
194            budget -= 1;
195            let done = poll_slot(
196                this.table.as_mut(),
197                context.as_mut(),
198                index,
199                attach,
200                permit,
201                &pool.tasks,
202                root,
203            )
204            .map_err(Error::Task)?;
205            if let state::Status::Done(all) = pending.commit(done) {
206                return Ok(Progress::Done(all));
207            }
208            if budget == 0 {
209                return Ok(if drain.peek().is_some() {
210                    Progress::More
211                } else {
212                    Progress::Parked
213                });
214            }
215        }
216    }
217
218    fn cancel(self: pin::Pin<&mut Self>) {
219        let mut this = self.project();
220        if let Some(root) = this.root.take() {
221            let pool = *this.pool;
222            for index in slots::Index::until(this.state.next()) {
223                this.table.as_mut().cancel(index, |bindings| {
224                    bindings.reclaim_pool(index, &pool.tasks, root);
225                });
226            }
227        }
228    }
229}
230
231fn poll_slot<'turn, 'd: 'turn, F, const N: usize>(
232    mut table: pin::Pin<&mut slots::Table<'d, F, F::Output, N>>,
233    mut context: pin::Pin<&mut context::Context<'turn, 'd>>,
234    index: slots::Index<N>,
235    edge: topology::Edge<'_, 'd, slots::Index<N>>,
236    permit: Option<app::Permit<'turn, 'd>>,
237    pool: &pool::Pool<'d, Tag, N>,
238    root: pool::Root<'d>,
239) -> Result<state::Completion, task::Error>
240where
241    F: abi::Fiber<'d>,
242{
243    match table.as_mut().visit(index, (), |fiber, bindings, ()| {
244        let poll = bindings.poll_pool(edge, pool, root, |wake| {
245            match permit {
246                Some(permit) => unsafe {
247                    context::raw::Live::new(fiber).child_with(context.as_mut(), wake, permit)
248                },
249                None => {
250                    // SAFETY: progress consumes its original PollCall admission
251                    // for exactly the first live child; later children carry permits.
252                    // Table::visit ends this child's live state on Ready.
253                    unsafe { context::raw::Live::new(fiber).child(context.as_mut(), wake) }
254                }
255            }
256        });
257        let poll = match poll {
258            Ok(poll) => poll,
259            Err(error) => return slots::Action::Keep(Err(error)),
260        };
261        let Poll::Ready(output) = poll else {
262            return slots::Action::Keep(Ok(state::Completion::Pending));
263        };
264        slots::Action::Complete(output, Ok(state::Completion::Ready))
265    }) {
266        slots::Phase::Live(result) => result,
267        slots::Phase::Vacant(()) | slots::Phase::Done(()) => Ok(state::Completion::Pending),
268    }
269}
270
271impl<'pool, 'd, F, const N: usize> abi::Fiber<'d> for Join<'pool, 'd, F, N>
272where
273    F: abi::Fiber<'d>,
274{
275    type Output = Result<array::IntoIter<F::Output, N>, Error>;
276
277    fn poll(call: context::PollCall<'_, '_, 'd, Self>) -> Poll<Self::Output> {
278        let (mut this, mut context) = call.into_parts();
279        match this.as_mut().progress(context.as_mut()) {
280            Ok(Progress::Done(done)) => {
281                let output = this.as_mut().project().table.drain_exact(done);
282                *this.as_mut().project().root = None;
283                Poll::Ready(Ok(output))
284            }
285            Ok(Progress::Parked) => Poll::Pending,
286            Ok(Progress::More) => {
287                context.requeue();
288                Poll::Pending
289            }
290            Err(error) => {
291                this.as_mut().cancel();
292                Poll::Ready(Err(error))
293            }
294        }
295    }
296}
297
298#[pin_project::pinned_drop]
299impl<'d, F, const N: usize> PinnedDrop for Join<'_, 'd, F, N>
300where
301    F: abi::Fiber<'d>,
302{
303    fn drop(self: pin::Pin<&mut Self>) {
304        self.cancel();
305    }
306}
307
308const _: () = {
309    use core::mem;
310
311    let word = mem::size_of::<usize>();
312    assert!(mem::size_of::<Join<'static, 'static, abi::Ready<()>, 0>>() == 4 * word);
313    assert!(mem::align_of::<Join<'static, 'static, abi::Ready<()>, 0>>() == word);
314    assert!(mem::size_of::<Option<Join<'static, 'static, abi::Ready<()>, 0>>>() == 4 * word);
315};