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