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