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