Skip to main content

vortex_layout/plan/physical/pipeline/
scan.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! The scan: its pipelines, the driver that runs one, the scheduler that picks which, and the
5//! splits whose stages it compiles.
6
7use std::collections::VecDeque;
8use std::fmt;
9use std::ops::Range;
10
11use rustc_hash::FxHashMap;
12use smallvec::SmallVec;
13use vortex_array::ArrayRef;
14use vortex_array::ExecutionCtx;
15use vortex_array::VortexSessionExecute;
16use vortex_array::buffer::BufferHandle;
17use vortex_error::VortexResult;
18use vortex_error::vortex_bail;
19use vortex_error::vortex_ensure;
20use vortex_error::vortex_err;
21use vortex_mask::Mask;
22use vortex_session::VortexSession;
23
24use super::DEFAULT_CAPACITY;
25use super::compile::Chain;
26use super::compile::Shares;
27use super::port::Arena;
28use super::port::PipelineId;
29use super::port::PortId;
30use super::port::Slab;
31use super::port::SourceEnd;
32use super::values::ValueCache;
33use crate::plan::physical::PlanRef;
34use crate::plan::physical::operator::Cx;
35use crate::plan::physical::operator::InletSet;
36use crate::plan::physical::operator::Input;
37use crate::plan::physical::operator::Operator;
38use crate::plan::physical::operator::Source;
39use crate::plan::physical::operator::SourceBlocked;
40use crate::plan::physical::operator::SourceReady;
41use crate::plan::physical::operator::Step;
42use crate::segments::SegmentId;
43
44#[cfg(debug_assertions)]
45mod validate;
46
47/// Identifies a read a scan asked its owner for.
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
49pub struct ReadId(u64);
50
51/// A segment the scan needs read.
52#[derive(Clone, Debug)]
53pub struct ReadRequest {
54    /// Identifies the read when its bytes are delivered.
55    pub id: ReadId,
56    /// The segment to read.
57    pub segment_id: SegmentId,
58}
59
60/// A row range of the plan's domain, and the rows of it to produce.
61#[derive(Clone, Debug)]
62pub struct Split {
63    /// The rows.
64    pub rows: Range<u64>,
65    /// Which of them to produce; as long as `rows`.
66    pub mask: Mask,
67}
68
69impl Split {
70    /// Every row of `rows`.
71    pub fn all(rows: Range<u64>) -> Self {
72        let len = usize::try_from(rows.end - rows.start).unwrap_or(usize::MAX);
73        Self {
74            mask: Mask::new_true(len),
75            rows,
76        }
77    }
78}
79
80/// What the owner of a [`Scan`] does next.
81#[derive(Debug)]
82pub enum Turn {
83    /// Read this segment and [`deliver`](Scan::deliver) its bytes.
84    Read(ReadRequest),
85    /// The next rows of split `split`, in row order within the split.
86    Output(usize, ArrayRef),
87    /// Nothing can run until a read is delivered.
88    Waiting,
89    /// Every split has produced all its rows.
90    Done,
91}
92
93/// Whether a pipeline can run, and if not, what it waits for.
94#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95enum State {
96    Runnable,
97    /// What its source said it waits for.
98    Blocked(SourceBlocked),
99    /// Room in a full outlet, found by the driver before the source ran.
100    Outlet,
101}
102
103/// A source, its stages, and the ports they read and write.
104pub(crate) struct Pipeline {
105    source: Box<dyn Source>,
106    stages: Vec<Box<dyn Operator>>,
107    inlets: Vec<PortId>,
108    outlets: SmallVec<[PortId; 1]>,
109    /// The segments it asked for that have arrived, in order of arrival.
110    bytes: VecDeque<(SegmentId, BufferHandle)>,
111    /// Reads it asked for that have not arrived, so retiring it cancels them only when any
112    /// are left.
113    pending: usize,
114    /// Once its source has finished, the next stage to finish: stages are finished one at a
115    /// time, each when the outlets have room for what it flushes.
116    finishing: Option<usize>,
117    /// Stages with more output for their current input, resumed deepest first.
118    continuing: Vec<usize>,
119    /// The source can compute again without checking readiness.
120    source_more: bool,
121    touched: InletSet,
122    changed: InletSet,
123    state: State,
124    queued: bool,
125}
126
127/// What one run of a pipeline came to.
128enum Progress {
129    /// The source waits for this.
130    Blocked(SourceBlocked),
131    /// An outlet is full.
132    Outlet,
133    /// The pipeline has finished and closed its outlets.
134    Done,
135}
136
137/// Runs `pipe` until it blocks or finishes, collecting any reads its source requests.
138fn drive(
139    pipe: &mut Pipeline,
140    arena: &mut Arena,
141    session: &VortexSession,
142    exec: &mut ExecutionCtx,
143    reads: &mut SmallVec<[SegmentId; 1]>,
144) -> VortexResult<Progress> {
145    let Pipeline {
146        source,
147        stages,
148        inlets,
149        outlets,
150        bytes,
151        finishing,
152        continuing,
153        source_more,
154        touched,
155        changed,
156        ..
157    } = pipe;
158    let mut cx = Cx {
159        arena,
160        inlets,
161        bytes,
162        reads,
163        session,
164        exec,
165        touched,
166        changed,
167    };
168    loop {
169        // Closing needs no room, so a pipeline with nothing left to flush closes at once.
170        if *finishing == Some(stages.len()) && continuing.is_empty() {
171            for &outlet in outlets.iter() {
172                cx.arena.close(outlet);
173            }
174            return Ok(Progress::Done);
175        }
176        if !outlets.iter().all(|&outlet| cx.arena.has_room(outlet)) {
177            return Ok(Progress::Outlet);
178        }
179        if let Some(stage) = continuing.pop() {
180            let step = stages[stage].compute(Input::None, &mut cx)?;
181            push(stages, stage, step, continuing, outlets, &mut cx)?;
182            continue;
183        }
184        if let Some(stage) = *finishing {
185            *finishing = Some(stage + 1);
186            let step = stages[stage].compute(Input::End, &mut cx)?;
187            push(stages, stage, step, continuing, outlets, &mut cx)?;
188            continue;
189        }
190        if !*source_more {
191            let ready = source.ready(&mut cx)?;
192            cx.changed.clear();
193            vortex_ensure!(
194                cx.reads.is_empty() || ready == SourceReady::Blocked(SourceBlocked::Io),
195                "A source that queues reads must block on IO"
196            );
197            match ready {
198                SourceReady::Ready => {}
199                SourceReady::Blocked(blocked) => return Ok(Progress::Blocked(blocked)),
200            }
201        }
202        let step = source.compute(Input::None, &mut cx)?;
203        *source_more = matches!(&step, Step::More(_));
204        match step {
205            Step::More(batch) | Step::Final(batch) => {
206                push_batch(stages, 0, batch, continuing, outlets, &mut cx)?;
207            }
208            Step::End => *finishing = Some(0),
209        }
210    }
211}
212
213/// Records a stage's continuation before pushing its output through the remaining stages.
214fn push(
215    stages: &mut [Box<dyn Operator>],
216    stage: usize,
217    step: Step,
218    continuing: &mut Vec<usize>,
219    outlets: &[PortId],
220    cx: &mut Cx<'_>,
221) -> VortexResult<()> {
222    let batch = match step {
223        Step::End => return Ok(()),
224        Step::More(batch) => {
225            continuing.push(stage);
226            batch
227        }
228        Step::Final(batch) => batch,
229    };
230    push_batch(stages, stage + 1, batch, continuing, outlets, cx)
231}
232
233/// Pushes one batch, retaining continuations so outlet capacity bounds every output.
234fn push_batch(
235    stages: &mut [Box<dyn Operator>],
236    start: usize,
237    mut batch: ArrayRef,
238    continuing: &mut Vec<usize>,
239    outlets: &[PortId],
240    cx: &mut Cx<'_>,
241) -> VortexResult<()> {
242    for (index, stage) in stages.iter_mut().enumerate().skip(start) {
243        batch = match stage.compute(Input::Batch(batch), cx)? {
244            Step::More(batch) => {
245                continuing.push(index);
246                batch
247            }
248            Step::Final(batch) => batch,
249            Step::End => return Ok(()),
250        };
251    }
252    push_out(batch, outlets, cx.arena);
253    Ok(())
254}
255
256/// Writes `batch` into every outlet: one for a plain pipeline, one per claimant for a share.
257fn push_out(batch: ArrayRef, outlets: &[PortId], arena: &mut Arena) {
258    if let Some((&last, rest)) = outlets.split_last() {
259        for &outlet in rest {
260            arena.push(outlet, batch.clone());
261        }
262        arena.push(last, batch);
263    }
264}
265
266/// Everything a scan's pipelines share: the ports, the pipeline table, the scheduler, the reads
267/// in flight, and the shares.
268pub(crate) struct Core {
269    pub(crate) session: VortexSession,
270    pub(crate) exec: ExecutionCtx,
271    /// The global row index of plan row zero.
272    pub(crate) row_offset: u64,
273    pub(crate) arena: Arena,
274    pipelines: Slab<Option<Pipeline>>,
275    free_pipelines: Vec<PipelineId>,
276    /// Runnable pipelines, last in first out, so data a pipeline just produced is consumed
277    /// before more is produced.
278    ready: Vec<PipelineId>,
279    /// Reads in flight: the pipeline that asked, or `None` once that pipeline has finished and
280    /// the read is cancelled, and the segment.
281    reads: FxHashMap<ReadId, (Option<PipelineId>, SegmentId)>,
282    next_read: u64,
283    new_reads: VecDeque<ReadRequest>,
284    /// Split slots whose stage output has something new, with a flag per slot so a slot is
285    /// queued once.
286    dirty: Vec<usize>,
287    dirty_flags: Vec<bool>,
288    pub(crate) shares: Shares,
289    /// The split being compiled, whose shared ports a compile claims. `usize::MAX` when no
290    /// split is, as while the pipeline a share decodes with is built: it reads for no split.
291    pub(crate) split: usize,
292    /// Whole values kept for later scans of the same split.
293    pub(crate) values: ValueCache,
294}
295
296impl Core {
297    fn new(session: VortexSession, row_offset: u64, splits: Vec<Range<u64>>) -> Self {
298        Self {
299            exec: session.create_execution_ctx(),
300            session,
301            row_offset,
302            arena: Arena::default(),
303            pipelines: Slab::default(),
304            free_pipelines: Vec::new(),
305            ready: Vec::new(),
306            reads: FxHashMap::default(),
307            next_read: 0,
308            new_reads: VecDeque::new(),
309            dirty: Vec::new(),
310            dirty_flags: Vec::new(),
311            shares: Shares::new(splits),
312            split: usize::MAX,
313            values: ValueCache::default(),
314        }
315    }
316
317    /// Makes `chain` a pipeline writing `outlets`, and queues it to run.
318    pub(crate) fn add_pipeline(
319        &mut self,
320        chain: Chain,
321        outlets: SmallVec<[PortId; 1]>,
322    ) -> PipelineId {
323        let touched = InletSet::new(chain.inlets.len());
324        let changed = InletSet::all(chain.inlets.len());
325        let pipeline = Pipeline {
326            continuing: Vec::new(),
327            source_more: false,
328            source: chain.source,
329            stages: chain.stages,
330            inlets: chain.inlets,
331            outlets,
332            bytes: VecDeque::new(),
333            pending: 0,
334            finishing: None,
335            touched,
336            changed,
337            state: State::Runnable,
338            queued: true,
339        };
340        let id = match self.free_pipelines.pop() {
341            Some(id) => id,
342            None => self.pipelines.push(None),
343        };
344        for (index, &inlet) in pipeline.inlets.iter().enumerate() {
345            self.arena.get_mut(inlet).source = SourceEnd::Pipeline { id, inlet: index };
346        }
347        for &outlet in &pipeline.outlets {
348            self.arena.get_mut(outlet).sink = Some(id);
349        }
350        self.pipelines[id] = Some(pipeline);
351        self.ready.push(id);
352        id
353    }
354
355    /// Compiles `plan` over `rows`, restricted to `mask`, into pipelines whose output the scan
356    /// reads for split `split` in slot `slot`, and returns the output port. `None` when nothing
357    /// is selected.
358    pub(crate) fn compile_stage(
359        &mut self,
360        plan: &PlanRef,
361        rows: Range<u64>,
362        mask: &Mask,
363        (slot, split): (usize, usize),
364    ) -> VortexResult<Option<PortId>> {
365        self.split = split;
366        let chain = self.compile(plan, rows, mask);
367        self.split = usize::MAX;
368        let Some(chain) = chain? else {
369            return Ok(None);
370        };
371        let port = self
372            .arena
373            .create(DEFAULT_CAPACITY, None, SourceEnd::Scan(slot));
374        self.add_pipeline(chain, smallvec::smallvec![port]);
375        #[cfg(debug_assertions)]
376        self.validate();
377        Ok(Some(port))
378    }
379
380    fn pipeline(&self, id: PipelineId) -> &Pipeline {
381        self.pipelines[id]
382            .as_ref()
383            .unwrap_or_else(|| unreachable!("pipeline {id} is not live"))
384    }
385
386    fn pipeline_mut(&mut self, id: PipelineId) -> &mut Pipeline {
387        self.pipelines[id]
388            .as_mut()
389            .unwrap_or_else(|| unreachable!("pipeline {id} is not live"))
390    }
391
392    fn make_runnable(&mut self, id: PipelineId) {
393        let pipe = self.pipeline_mut(id);
394        pipe.state = State::Runnable;
395        if !pipe.queued {
396            pipe.queued = true;
397            self.ready.push(id);
398        }
399    }
400
401    /// Runs pipeline `id` once and settles what it did.
402    fn run(&mut self, id: PipelineId) -> VortexResult<()> {
403        let pipe = self.pipelines[id]
404            .as_mut()
405            .ok_or_else(|| vortex_err!("Pipeline {id} is not live"))?;
406        pipe.queued = false;
407        let mut reads = SmallVec::new();
408        let progress = drive(
409            pipe,
410            &mut self.arena,
411            &self.session,
412            &mut self.exec,
413            &mut reads,
414        )?;
415        for segment_id in reads {
416            self.ask(id, segment_id);
417        }
418        #[cfg(debug_assertions)]
419        self.validate_progress(id, &progress);
420        match &progress {
421            // A read-ahead source can still have delivered bytes to consume.
422            Progress::Blocked(SourceBlocked::Io) if !self.pipeline(id).bytes.is_empty() => {
423                self.make_runnable(id);
424            }
425            // Nothing changes a port while its source's pipeline runs, so the condition the run reported
426            // holds until a neighbour wakes it.
427            Progress::Blocked(blocked) => self.pipeline_mut(id).state = State::Blocked(*blocked),
428            Progress::Outlet => self.pipeline_mut(id).state = State::Outlet,
429            Progress::Done => {}
430        }
431        self.wake_neighbours(id);
432        if matches!(progress, Progress::Done) {
433            self.retire(id);
434        }
435        Ok(())
436    }
437
438    /// Issues a read of `segment_id`, which pipeline `id`'s source asked for.
439    fn ask(&mut self, id: PipelineId, segment_id: SegmentId) {
440        self.read(id, segment_id);
441        self.pipeline_mut(id).pending += 1;
442    }
443
444    /// Wakes the sources of what pipeline `id` pushed and the sinks of what it took.
445    fn wake_neighbours(&mut self, id: PipelineId) {
446        for index in 0..self.pipeline(id).outlets.len() {
447            let port = self.pipeline(id).outlets[index];
448            let queue = self.arena.get_mut(port);
449            if !queue.take_changed() {
450                continue;
451            }
452            match queue.source {
453                SourceEnd::Pipeline { id: source, inlet } => {
454                    // Record every change, including inlets the source is not waiting on.
455                    let source_pipe = self.pipeline_mut(source);
456                    source_pipe.changed.insert(inlet);
457                    if source_pipe.state == State::Blocked(SourceBlocked::Inlet(inlet)) {
458                        self.make_runnable(source);
459                    }
460                }
461                SourceEnd::Scan(slot) => self.mark_dirty(slot),
462                SourceEnd::Unclaimed => {}
463            }
464        }
465        // Only an inlet the run read can have gained room, so a source with many inlets, as a
466        // concatenation of many chunks, pays for the inlets it read, not for all of them.
467        let mut touched = std::mem::take(&mut self.pipeline_mut(id).touched);
468        for &index in touched.indices() {
469            if let Some(sink) = self.arena.get(self.pipeline(id).inlets[index]).sink
470                && sink != id
471                && self.sink_has_room(sink)
472            {
473                self.make_runnable(sink);
474            }
475        }
476        touched.clear();
477        self.pipeline_mut(id).touched = touched;
478    }
479
480    /// Queues split slot `slot` for the scan to read its stage's output.
481    fn mark_dirty(&mut self, slot: usize) {
482        if !self.dirty_flags[slot] {
483            self.dirty_flags[slot] = true;
484            self.dirty.push(slot);
485        }
486    }
487
488    /// Whether pipeline `sink` is blocked on its outlets and every one of them has room now.
489    fn sink_has_room(&self, sink: PipelineId) -> bool {
490        let pipe = self.pipeline(sink);
491        pipe.state == State::Outlet
492            && pipe
493                .outlets
494                .iter()
495                .all(|&outlet| self.arena.has_room(outlet))
496    }
497
498    /// Finishes split `index`: its unclaimed share ports are detached from the pipelines
499    /// sharing them, which then push to the claimants alone, and freed.
500    pub(crate) fn finish_split(&mut self, index: usize) {
501        for port in self.shares.finish(index) {
502            // A sink that has finished already closed the port, and so left it.
503            if let Some(sink) = self.arena.get(port).sink {
504                let pipe = self.pipeline_mut(sink);
505                pipe.outlets.retain(|outlet| *outlet != port);
506                // The claimant that built the shared pipeline reads its own port to the end,
507                // so no split's leaving takes a shared pipeline's last outlet.
508                debug_assert!(
509                    !pipe.outlets.is_empty(),
510                    "pipeline {sink} shares its output with no split"
511                );
512                self.arena.get_mut(port).sink = None;
513                if self.sink_has_room(sink) {
514                    self.make_runnable(sink);
515                }
516            }
517            self.arena.discard(port);
518        }
519    }
520
521    /// Drops a finished pipeline. Its outlets are closed; its inlets lose their source; the reads
522    /// it asked for and never took are cancelled, so their bytes are dropped on arrival instead
523    /// of reaching a pipeline that reuses its id.
524    fn retire(&mut self, id: PipelineId) {
525        if let Some(pipe) = self.pipelines[id].take() {
526            for inlet in pipe.inlets {
527                self.arena.drop_source(inlet);
528            }
529            if pipe.pending > 0 {
530                self.cancel_reads(id);
531            }
532            self.free_pipelines.push(id);
533        }
534    }
535
536    /// Cancels the reads pipeline `id` waits for: they become reads for no pipeline, whose
537    /// bytes are dropped on arrival.
538    fn cancel_reads(&mut self, id: PipelineId) {
539        for (owner, _) in self.reads.values_mut() {
540            if *owner == Some(id) {
541                *owner = None;
542            }
543        }
544    }
545
546    /// The scan has read a stage's output: wakes its sink if the output was full.
547    fn read_output(&mut self, port: PortId) {
548        if let Some(sink) = self.arena.get(port).sink
549            && self.sink_has_room(sink)
550        {
551            self.make_runnable(sink);
552        }
553    }
554
555    fn deliver(&mut self, read: ReadId, bytes: BufferHandle) -> VortexResult<()> {
556        let (owner, segment) = self
557            .reads
558            .remove(&read)
559            .ok_or_else(|| vortex_err!("Unknown read {read:?}"))?;
560        if let Some(id) = owner {
561            let pipe = self.pipeline_mut(id);
562            pipe.pending -= 1;
563            pipe.bytes.push_back((segment, bytes));
564            self.make_runnable(id);
565        }
566        Ok(())
567    }
568
569    /// Issues a read of `segment` for pipeline `owner`.
570    fn read(&mut self, owner: PipelineId, segment_id: SegmentId) {
571        let read = ReadId(self.next_read);
572        self.next_read += 1;
573        self.reads.insert(read, (Some(owner), segment_id));
574        self.new_reads.push_back(ReadRequest {
575            id: read,
576            segment_id,
577        });
578    }
579
580    /// Every live pipeline's state, and for one waiting on an inlet, that port and its sink.
581    fn describe_blocked(&self) -> String {
582        self.pipelines
583            .iter()
584            .enumerate()
585            .filter_map(|(id, pipe)| {
586                let pipe = pipe.as_ref()?;
587                Some(match pipe.state {
588                    State::Blocked(SourceBlocked::Inlet(index)) => {
589                        let port = pipe.inlets[index];
590                        let queue = self.arena.get(port);
591                        format!(
592                            "{id}: {:?} (port {port}: {} queued, closed {}, sink {:?})",
593                            pipe.state,
594                            queue.len(),
595                            queue.closed(),
596                            queue.sink.map(|sink| (sink, self.pipeline(sink).state)),
597                        )
598                    }
599                    state => format!("{id}: {state:?}"),
600                })
601            })
602            .collect::<Vec<_>>()
603            .join(", ")
604    }
605}
606
607/// What a scan runs over each split.
608#[derive(Clone)]
609enum Root {
610    /// The plan, under the split's selection.
611    Plan(PlanRef),
612}
613
614/// A split being run.
615struct Active {
616    index: usize,
617    /// The output port of the stage running, read by the scan.
618    output: Option<PortId>,
619}
620
621/// Runs a plan over a list of splits.
622///
623/// The plan runs as one stage under each split's selection.
624///
625/// Up to [`with_max_active`](Self::with_max_active) splits are compiled at once, in order, so
626/// reads of later splits are in flight while earlier ones compute.
627pub struct Scan {
628    core: Core,
629    root: Option<Root>,
630    splits: Vec<Split>,
631    next: usize,
632    active: Vec<Option<Active>>,
633    live: usize,
634    outputs: VecDeque<(usize, ArrayRef)>,
635}
636
637impl fmt::Debug for Scan {
638    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
639        f.debug_struct("Scan")
640            .field("splits", &self.splits.len())
641            .field("next", &self.next)
642            .field("live", &self.live)
643            .finish()
644    }
645}
646
647impl Scan {
648    /// A scan of `plan` over `splits`, with one split active at a time.
649    ///
650    /// Counts, from the plan, how often each split will read each segment, so segments read
651    /// more than once are decoded once. Builds no pipeline and issues no read.
652    pub fn try_new(
653        session: VortexSession,
654        plan: PlanRef,
655        splits: Vec<Split>,
656    ) -> VortexResult<Self> {
657        for split in &splits {
658            vortex_ensure!(
659                split.rows.start <= split.rows.end
660                    && split.rows.end <= plan.row_count()
661                    && split.mask.len() as u64 == split.rows.end - split.rows.start,
662                "Split {:?} with a mask of {} rows does not fit a plan of {} rows",
663                split.rows,
664                split.mask.len(),
665                plan.row_count()
666            );
667        }
668        let root = Root::Plan(plan);
669        let mut core = Core::new(
670            session,
671            0,
672            splits.iter().map(|split| split.rows.clone()).collect(),
673        );
674        // Only the rows some split reads can share a segment between readers.
675        let start = splits
676            .iter()
677            .map(|split| split.rows.start)
678            .min()
679            .unwrap_or(0);
680        let end = splits.iter().map(|split| split.rows.end).max().unwrap_or(0);
681        match &root {
682            Root::Plan(plan) => core.shares.add(plan, start..end)?,
683        }
684        core.shares.retain_shared();
685        core.dirty_flags = vec![false; 1];
686        Ok(Self {
687            core,
688            root: Some(root),
689            splits,
690            next: 0,
691            active: vec![None],
692            live: 0,
693            outputs: VecDeque::new(),
694        })
695    }
696
697    /// Runs up to `max_active` splits at once.
698    pub fn with_max_active(mut self, max_active: usize) -> Self {
699        self.active.resize_with(max_active.max(1), || None);
700        self.core.dirty_flags = vec![false; self.active.len()];
701        self.core.dirty = Vec::with_capacity(self.active.len());
702        self
703    }
704
705    /// Sets the global row index of plan row zero, which row-index plans add to their rows.
706    pub fn with_row_offset(mut self, row_offset: u64) -> Self {
707        self.core.row_offset = row_offset;
708        self
709    }
710
711    /// Shares whole values, such as dictionary values, with the other scans given `values`.
712    pub fn with_value_cache(mut self, values: ValueCache) -> Self {
713        self.core.values = values;
714        self
715    }
716
717    /// Runs pipelines until the owner has something to do.
718    pub fn step(&mut self) -> VortexResult<Turn> {
719        loop {
720            if let Some((split, array)) = self.outputs.pop_front() {
721                return Ok(Turn::Output(split, array));
722            }
723            if let Some(read) = self.core.new_reads.pop_front() {
724                return Ok(Turn::Read(read));
725            }
726            if let Some(slot) = self.core.dirty.pop() {
727                self.core.dirty_flags[slot] = false;
728                self.advance(slot)?;
729                continue;
730            }
731            if self.live < self.active.len() && self.next < self.splits.len() {
732                self.activate()?;
733                continue;
734            }
735            if let Some(id) = self.core.ready.pop() {
736                self.core.run(id)?;
737                continue;
738            }
739            // Reads still in flight once every split has finished were cancelled; nothing waits
740            // for their bytes.
741            if self.live == 0 {
742                return Ok(Turn::Done);
743            }
744            if !self.core.reads.is_empty() {
745                return Ok(Turn::Waiting);
746            }
747            vortex_bail!(
748                "Scan is stuck with nothing runnable and no read in flight: {}",
749                self.core.describe_blocked()
750            );
751        }
752    }
753
754    /// Delivers the bytes of a read [`step`](Self::step) returned.
755    pub fn deliver(&mut self, read: ReadId, bytes: BufferHandle) -> VortexResult<()> {
756        self.core.deliver(read, bytes)
757    }
758
759    /// Starts the next split in a free slot.
760    fn activate(&mut self) -> VortexResult<()> {
761        let slot = self
762            .active
763            .iter()
764            .position(Option::is_none)
765            .ok_or_else(|| vortex_err!("No free split slot"))?;
766        let index = self.next;
767        self.next += 1;
768        let split = self.splits[index].clone();
769        let output = match self
770            .root
771            .as_ref()
772            .ok_or_else(|| vortex_err!("Scan has no plan for its next split"))?
773        {
774            Root::Plan(plan) => {
775                self.core
776                    .compile_stage(plan, split.rows.clone(), &split.mask, (slot, index))?
777            }
778        };
779        if self.next == self.splits.len() {
780            // Compiled operators own what they need. The last split no longer needs the
781            // lowering tree or the source layouts retained by its lazy children.
782            self.root = None;
783        }
784        if output.is_none() {
785            self.core.finish_split(index);
786        } else {
787            self.active[slot] = Some(Active { index, output });
788            self.live += 1;
789        }
790        Ok(())
791    }
792
793    /// Takes what the stage running in `slot` produced, and moves the split on when the stage
794    /// has finished.
795    fn advance(&mut self, slot: usize) -> VortexResult<()> {
796        let Some(active) = self.active[slot].as_mut() else {
797            return Ok(());
798        };
799        while let Some(port) = active.output {
800            while let Some(batch) = self.core.arena.get_mut(port).pop() {
801                self.outputs.push_back((active.index, batch));
802            }
803            self.core.read_output(port);
804            if !self.core.arena.get(port).closed() {
805                return Ok(());
806            }
807            self.core.arena.drop_source(port);
808            active.output = None;
809        }
810        self.core.finish_split(active.index);
811        self.active[slot] = None;
812        self.live -= 1;
813        Ok(())
814    }
815
816    /// Ports in use.
817    #[cfg(test)]
818    pub(crate) fn live_ports(&self) -> usize {
819        self.core.arena.live()
820    }
821
822    /// Reads in flight that a pipeline waits for.
823    #[cfg(test)]
824    pub(crate) fn awaited_reads(&self) -> usize {
825        self.core
826            .reads
827            .values()
828            .filter(|(owner, _)| owner.is_some())
829            .count()
830    }
831
832    /// Shared ports not yet claimed or dropped.
833    #[cfg(test)]
834    pub(crate) fn pending_shares(&self) -> usize {
835        self.core.shares.pending()
836    }
837}