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