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#[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 pub fn new(pool: &'pool mut Pool<'d, N>) -> Self {
46 Self::from_table(pool, slots::Table::vacant(), array::Len::ZERO)
47 }
48
49 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 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 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 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 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};