vortex_scan/planning/driver/
mod.rs1use std::collections::BTreeMap;
20use std::collections::VecDeque;
21use std::sync::Arc;
22use std::task::Context;
23use std::task::Poll;
24
25use vortex_array::ArrayRef;
26use vortex_error::VortexResult;
27use vortex_error::vortex_bail;
28use vortex_error::vortex_err;
29use vortex_io::request::Completion;
30use vortex_io::request::IoBatch;
31use vortex_io::request::IoConsumer;
32use vortex_io::request::IoIntent;
33use vortex_io::request::IoOwnerId;
34use vortex_io::request::IoRequestId;
35use vortex_io::request::IoResult;
36use vortex_io::request::IoSource;
37use vortex_io::request::IoTarget;
38use vortex_utils::aliases::hash_map::Entry;
39use vortex_utils::aliases::hash_map::HashMap;
40
41use crate::planning::morsel::Morsel;
42use crate::planning::morsel::MorselOutput;
43use crate::planning::planner::Planner;
44use crate::planning::planner::PlannerOutput;
45use crate::planning::planner::State;
46use crate::planning::planner::WorkScope;
47
48#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
50pub struct RootId(pub u64);
51
52pub struct Batch {
54 pub root: RootId,
56 pub scope: WorkScope,
58 pub array: ArrayRef,
60}
61
62pub enum Progress {
64 Batch(Batch),
66 RootDone(RootId),
68 Waiting,
71 Idle,
73}
74
75enum Item {
76 Planner(Box<dyn Planner>),
77 Morsel(Box<dyn Morsel>),
78}
79
80enum Output {
82 Planner(PlannerOutput),
83 Morsel(MorselOutput),
84}
85
86struct Work {
89 id: IoOwnerId,
90 root: RootId,
91 scope: WorkScope,
92 item: Item,
93 path: Vec<u64>,
95 next_child: u64,
97 outstanding: HashMap<IoRequestId, IoTarget>,
99}
100
101impl Work {
102 fn priority(&self) -> Vec<u64> {
103 let mut priority = self.path.clone();
104 priority.push(self.next_child);
105 priority
106 }
107
108 fn child_path(&mut self) -> Vec<u64> {
111 let path = self.priority();
112 self.next_child += 1;
113 path
114 }
115
116 fn state(&self) -> State {
117 match &self.item {
118 Item::Planner(planner) => planner.state(),
119 Item::Morsel(morsel) => morsel.state(),
120 }
121 }
122
123 fn consumer(&mut self) -> &mut dyn IoConsumer {
124 match &mut self.item {
125 Item::Planner(planner) => &mut **planner,
126 Item::Morsel(morsel) => &mut **morsel,
127 }
128 }
129
130 fn deliver(&mut self, request: IoRequestId, result: IoResult) -> VortexResult<()> {
132 let Some(target) = self.outstanding.remove(&request) else {
133 vortex_bail!(
134 "completion for {request:?} of {:?}, which is not outstanding",
135 self.id
136 );
137 };
138 if !result.matches(&target) {
139 vortex_bail!("IO source answered {target:?} with {}", result.kind());
140 }
141 self.consumer().set_io_result(request, result);
142 Ok(())
143 }
144}
145
146#[derive(Default)]
148struct ReadyQueue {
149 priorities: BTreeMap<Vec<u64>, IoOwnerId>,
150 work: HashMap<IoOwnerId, Box<Work>>,
151}
152
153impl ReadyQueue {
154 fn insert(&mut self, work: Box<Work>) {
155 self.priorities.insert(work.priority(), work.id);
156 self.work.insert(work.id, work);
157 }
158
159 fn pop(&mut self) -> Option<Box<Work>> {
160 let (_, owner) = self.priorities.pop_first()?;
161 self.work.remove(&owner)
162 }
163
164 fn is_empty(&self) -> bool {
165 self.priorities.is_empty()
166 }
167
168 fn cancel(&mut self, root: RootId) {
169 self.work.retain(|_, work| work.root != root);
170 self.priorities
171 .retain(|_, owner| self.work.contains_key(owner));
172 }
173}
174
175const IO_POLL_INTERVAL: usize = 64;
177
178struct Root {
180 io: Arc<dyn IoSource>,
181 live: usize,
182}
183
184#[derive(Default)]
188pub struct Run {
189 roots: HashMap<RootId, Root>,
190 next_root: u64,
191 next_owner: u64,
192 queue: ReadyQueue,
193 parked: HashMap<IoOwnerId, Box<Work>>,
194 events: VecDeque<Progress>,
196 visits_until_poll: usize,
198 step_limit: Option<usize>,
199 steps: usize,
200}
201
202impl Drop for Run {
203 fn drop(&mut self) {
204 for root in self.roots.values() {
205 root.io.clear();
206 }
207 }
208}
209
210impl Run {
211 pub fn new() -> Self {
213 Self::default()
214 }
215
216 pub fn with_step_limit(mut self, steps: usize) -> Self {
219 self.step_limit = Some(steps);
220 self
221 }
222
223 pub fn admit(
228 &mut self,
229 root: Box<dyn Planner>,
230 scope: WorkScope,
231 io: Arc<dyn IoSource>,
232 ) -> RootId {
233 let id = RootId(self.next_root);
234 self.next_root += 1;
235 self.roots.insert(id, Root { io, live: 0 });
236 self.spawn(id, scope, Item::Planner(root), vec![id.0]);
237 id
238 }
239
240 pub fn cancel(&mut self, root: RootId) {
243 let Some(cancelled) = self.roots.remove(&root) else {
244 return;
245 };
246 self.queue.cancel(root);
247 self.parked.retain(|_, work| work.root != root);
248 cancelled.io.clear();
249 }
250
251 pub fn live_roots(&self) -> usize {
253 self.roots.len()
254 }
255
256 pub fn advance(&mut self) -> VortexResult<Progress> {
259 loop {
260 if let Some(event) = self.events.pop_front() {
261 return Ok(event);
262 }
263 if self.visits_until_poll == 0 || self.queue.is_empty() {
264 self.take_completions()?;
265 self.visits_until_poll = IO_POLL_INTERVAL;
266 if self.queue.is_empty() {
267 return Ok(if self.parked.is_empty() {
268 Progress::Idle
269 } else {
270 Progress::Waiting
271 });
272 }
273 }
274 self.visits_until_poll -= 1;
275 let Some(work) = self.queue.pop() else {
276 continue;
277 };
278 self.steps += 1;
279 if let Some(limit) = self.step_limit
280 && self.steps > limit
281 {
282 vortex_bail!("driver exceeded its step limit of {limit}");
283 }
284 self.visit(work)?;
285 }
286 }
287
288 pub fn complete(&mut self, completion: Completion) -> VortexResult<()> {
290 let Completion {
291 owner,
292 request,
293 result,
294 } = completion;
295 let result = result?;
296 if let Entry::Occupied(mut parked) = self.parked.entry(owner) {
297 let work = parked.get_mut();
298 work.deliver(request, result)?;
299 if work.state() != State::Waiting {
301 let work = parked.remove();
302 self.queue.insert(work);
303 } else if work.outstanding.is_empty() {
304 vortex_bail!(
305 "work for scope {:?} waits with no outstanding fetch",
306 work.scope
307 );
308 }
309 return Ok(());
310 }
311 let Some(work) = self.queue.work.get_mut(&owner) else {
313 vortex_bail!("completion for unknown work {owner:?}");
314 };
315 work.deliver(request, result)
316 }
317
318 pub fn poll_completion(&mut self, cx: &mut Context<'_>) -> Poll<VortexResult<()>> {
323 let sources: Vec<_> = self
324 .roots
325 .values()
326 .map(|root| Arc::clone(&root.io))
327 .collect();
328 if sources.is_empty() {
329 return Poll::Ready(Err(vortex_err!(
330 "the run is waiting for IO with no live root"
331 )));
332 }
333 let mut delivered = false;
334 for io in sources {
335 match io.poll_completion(cx) {
336 Poll::Ready(Ok(completion)) => {
337 if let Err(error) = self.complete(completion) {
338 return Poll::Ready(Err(error));
339 }
340 delivered = true;
341 }
342 Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
343 Poll::Pending => {}
344 }
345 }
346 if delivered {
347 Poll::Ready(Ok(()))
348 } else {
349 Poll::Pending
350 }
351 }
352
353 fn take_completions(&mut self) -> VortexResult<()> {
355 if self.roots.len() == 1 {
356 let Some(io) = self.roots.values().next().map(|root| Arc::clone(&root.io)) else {
358 return Ok(());
359 };
360 while let Some(completion) = io.poll()? {
361 self.complete(completion)?;
362 }
363 return Ok(());
364 }
365 let sources: Vec<_> = self
366 .roots
367 .values()
368 .map(|root| Arc::clone(&root.io))
369 .collect();
370 for io in sources {
371 while let Some(completion) = io.poll()? {
372 self.complete(completion)?;
373 }
374 }
375 Ok(())
376 }
377
378 fn spawn(&mut self, root: RootId, scope: WorkScope, item: Item, path: Vec<u64>) {
379 let id = IoOwnerId(self.next_owner);
380 self.next_owner += 1;
381 if let Some(root) = self.roots.get_mut(&root) {
382 root.live += 1;
383 }
384 let work = Box::new(Work {
385 id,
386 root,
387 scope,
388 item,
389 path,
390 next_child: 0,
391 outstanding: HashMap::default(),
392 });
393 self.queue.insert(work);
394 }
395
396 fn retire(&mut self, work: &Work) {
398 let Entry::Occupied(mut root) = self.roots.entry(work.root) else {
399 return;
400 };
401 root.get().io.release(work.id);
402 root.get_mut().live -= 1;
403 if root.get().live == 0 {
404 root.remove().io.clear();
405 self.events.push_back(Progress::RootDone(work.root));
406 }
407 }
408
409 fn park(&mut self, work: Box<Work>) -> VortexResult<()> {
411 if work.outstanding.is_empty() {
412 vortex_bail!(
413 "work for scope {:?} waits with no outstanding fetch",
414 work.scope
415 );
416 }
417 self.parked.insert(work.id, work);
418 Ok(())
419 }
420
421 fn settle(&mut self, work: Box<Work>) -> VortexResult<()> {
423 match work.state() {
424 State::Done => self.retire(&work),
425 State::NeedsCompute => self.queue.insert(work),
426 State::Waiting => self.park(work)?,
427 }
428 Ok(())
429 }
430
431 fn visit(&mut self, mut work: Box<Work>) -> VortexResult<()> {
433 let output = match &mut work.item {
434 Item::Planner(planner) => match planner.state() {
435 State::NeedsCompute => Output::Planner(planner.compute()?),
436 _ => return self.settle(work),
437 },
438 Item::Morsel(morsel) => match morsel.state() {
439 State::NeedsCompute => Output::Morsel(morsel.compute()?),
440 _ => return self.settle(work),
441 },
442 };
443 match output {
444 Output::Planner(PlannerOutput::Done) | Output::Morsel(MorselOutput::Done) => {
445 self.retire(&work);
446 }
447 Output::Planner(PlannerOutput::Continue) | Output::Morsel(MorselOutput::Continue) => {
448 self.queue.insert(work);
449 }
450 Output::Planner(PlannerOutput::NeedsIO(batch))
451 | Output::Morsel(MorselOutput::NeedsIO(batch)) => {
452 self.submit(&mut work, batch)?;
453 self.settle(work)?;
454 }
455 Output::Planner(PlannerOutput::Planner(scope, child)) => {
456 let path = work.child_path();
457 self.spawn(work.root, scope, Item::Planner(child), path);
458 self.queue.insert(work);
459 }
460 Output::Planner(PlannerOutput::Morsel(scope, morsel)) => {
461 let path = work.child_path();
462 self.spawn(work.root, scope, Item::Morsel(morsel), path);
463 self.queue.insert(work);
464 }
465 Output::Morsel(MorselOutput::Batch(array)) => {
466 if array.is_empty() {
467 vortex_bail!("morsel for scope {:?} returned an empty batch", work.scope);
468 }
469 self.events.push_back(Progress::Batch(Batch {
470 root: work.root,
471 scope: work.scope.clone(),
472 array,
473 }));
474 self.queue.insert(work);
475 }
476 }
477 Ok(())
478 }
479
480 fn submit(&mut self, work: &mut Work, batch: IoBatch) -> VortexResult<()> {
482 if batch.is_empty() {
483 vortex_bail!("compute() published an empty batch");
484 }
485 for request in &batch {
486 if request.intent == IoIntent::Fetch
487 && work
488 .outstanding
489 .insert(request.request, request.target)
490 .is_some()
491 {
492 vortex_bail!(
493 "fetch {:?} of {:?} was published while it is outstanding",
494 request.request,
495 work.id
496 );
497 }
498 }
499 let Some(root) = self.roots.get(&work.root) else {
500 vortex_bail!("work {:?} outlived its root {:?}", work.id, work.root);
501 };
502 root.io.submit(work.id, batch)
503 }
504}
505
506pub struct Driver {
508 io: Arc<dyn IoSource>,
509 step_limit: Option<usize>,
510}
511
512impl Driver {
513 pub fn new(io: Arc<dyn IoSource>) -> Self {
515 Self {
516 io,
517 step_limit: None,
518 }
519 }
520
521 pub fn with_step_limit(self, steps: usize) -> Self {
524 Self {
525 step_limit: Some(steps),
526 ..self
527 }
528 }
529
530 pub fn run(&self, root: Box<dyn Planner>) -> VortexResult<Vec<Batch>> {
532 let mut run = Run::new();
533 run.step_limit = self.step_limit;
534 let scope = WorkScope {
535 file_ordinal: 0,
536 rows: 0..0,
537 };
538 run.admit(root, scope, Arc::clone(&self.io));
539 let mut batches = Vec::new();
540 loop {
541 match run.advance()? {
542 Progress::Batch(batch) => batches.push(batch),
543 Progress::RootDone(_) | Progress::Idle => return Ok(batches),
544 Progress::Waiting => run.complete(self.io.wait()?)?,
545 }
546 }
547 }
548}
549
550#[cfg(test)]
551mod tests;