use std::collections::VecDeque;
use std::fmt;
use std::ops::Range;
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use vortex_array::ArrayRef;
use vortex_array::ExecutionCtx;
use vortex_array::VortexSessionExecute;
use vortex_array::buffer::BufferHandle;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_ensure;
use vortex_error::vortex_err;
use vortex_mask::Mask;
use vortex_session::VortexSession;
use super::DEFAULT_CAPACITY;
use super::compile::Chain;
use super::compile::Shares;
use super::port::Arena;
use super::port::PipelineId;
use super::port::PortId;
use super::port::Slab;
use super::port::SourceEnd;
use super::values::ValueCache;
use crate::plan::physical::PlanRef;
use crate::plan::physical::operator::Cx;
use crate::plan::physical::operator::InletSet;
use crate::plan::physical::operator::Input;
use crate::plan::physical::operator::Operator;
use crate::plan::physical::operator::Source;
use crate::plan::physical::operator::SourceBlocked;
use crate::plan::physical::operator::SourceReady;
use crate::plan::physical::operator::Step;
use crate::segments::SegmentId;
#[cfg(debug_assertions)]
mod validate;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ReadId(u64);
#[derive(Clone, Debug)]
pub struct ReadRequest {
pub id: ReadId,
pub segment_id: SegmentId,
}
#[derive(Clone, Debug)]
pub struct Split {
pub rows: Range<u64>,
pub mask: Mask,
}
impl Split {
pub fn all(rows: Range<u64>) -> Self {
let len = usize::try_from(rows.end - rows.start).unwrap_or(usize::MAX);
Self {
mask: Mask::new_true(len),
rows,
}
}
}
#[derive(Debug)]
pub enum Turn {
Read(ReadRequest),
Output(usize, ArrayRef),
Waiting,
Done,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum State {
Runnable,
Blocked(SourceBlocked),
Outlet,
}
pub(crate) struct Pipeline {
source: Box<dyn Source>,
stages: Vec<Box<dyn Operator>>,
inlets: Vec<PortId>,
outlets: SmallVec<[PortId; 1]>,
bytes: VecDeque<(SegmentId, BufferHandle)>,
pending: usize,
finishing: Option<usize>,
continuing: Vec<usize>,
source_more: bool,
touched: InletSet,
changed: InletSet,
state: State,
queued: bool,
}
enum Progress {
Blocked(SourceBlocked),
Outlet,
Done,
}
fn drive(
pipe: &mut Pipeline,
arena: &mut Arena,
session: &VortexSession,
exec: &mut ExecutionCtx,
reads: &mut SmallVec<[SegmentId; 1]>,
) -> VortexResult<Progress> {
let Pipeline {
source,
stages,
inlets,
outlets,
bytes,
finishing,
continuing,
source_more,
touched,
changed,
..
} = pipe;
let mut cx = Cx {
arena,
inlets,
bytes,
reads,
session,
exec,
touched,
changed,
};
loop {
if *finishing == Some(stages.len()) && continuing.is_empty() {
for &outlet in outlets.iter() {
cx.arena.close(outlet);
}
return Ok(Progress::Done);
}
if !outlets.iter().all(|&outlet| cx.arena.has_room(outlet)) {
return Ok(Progress::Outlet);
}
if let Some(stage) = continuing.pop() {
let step = stages[stage].compute(Input::None, &mut cx)?;
push(stages, stage, step, continuing, outlets, &mut cx)?;
continue;
}
if let Some(stage) = *finishing {
*finishing = Some(stage + 1);
let step = stages[stage].compute(Input::End, &mut cx)?;
push(stages, stage, step, continuing, outlets, &mut cx)?;
continue;
}
if !*source_more {
let ready = source.ready(&mut cx)?;
cx.changed.clear();
vortex_ensure!(
cx.reads.is_empty() || ready == SourceReady::Blocked(SourceBlocked::Io),
"A source that queues reads must block on IO"
);
match ready {
SourceReady::Ready => {}
SourceReady::Blocked(blocked) => return Ok(Progress::Blocked(blocked)),
}
}
let step = source.compute(Input::None, &mut cx)?;
*source_more = matches!(&step, Step::More(_));
match step {
Step::More(batch) | Step::Final(batch) => {
push_batch(stages, 0, batch, continuing, outlets, &mut cx)?;
}
Step::End => *finishing = Some(0),
}
}
}
fn push(
stages: &mut [Box<dyn Operator>],
stage: usize,
step: Step,
continuing: &mut Vec<usize>,
outlets: &[PortId],
cx: &mut Cx<'_>,
) -> VortexResult<()> {
let batch = match step {
Step::End => return Ok(()),
Step::More(batch) => {
continuing.push(stage);
batch
}
Step::Final(batch) => batch,
};
push_batch(stages, stage + 1, batch, continuing, outlets, cx)
}
fn push_batch(
stages: &mut [Box<dyn Operator>],
start: usize,
mut batch: ArrayRef,
continuing: &mut Vec<usize>,
outlets: &[PortId],
cx: &mut Cx<'_>,
) -> VortexResult<()> {
for (index, stage) in stages.iter_mut().enumerate().skip(start) {
batch = match stage.compute(Input::Batch(batch), cx)? {
Step::More(batch) => {
continuing.push(index);
batch
}
Step::Final(batch) => batch,
Step::End => return Ok(()),
};
}
push_out(batch, outlets, cx.arena);
Ok(())
}
fn push_out(batch: ArrayRef, outlets: &[PortId], arena: &mut Arena) {
if let Some((&last, rest)) = outlets.split_last() {
for &outlet in rest {
arena.push(outlet, batch.clone());
}
arena.push(last, batch);
}
}
pub(crate) struct Core {
pub(crate) session: VortexSession,
pub(crate) exec: ExecutionCtx,
pub(crate) row_offset: u64,
pub(crate) arena: Arena,
pipelines: Slab<Option<Pipeline>>,
free_pipelines: Vec<PipelineId>,
ready: Vec<PipelineId>,
reads: FxHashMap<ReadId, (Option<PipelineId>, SegmentId)>,
next_read: u64,
new_reads: VecDeque<ReadRequest>,
dirty: Vec<usize>,
dirty_flags: Vec<bool>,
pub(crate) shares: Shares,
pub(crate) split: usize,
pub(crate) values: ValueCache,
}
impl Core {
fn new(session: VortexSession, row_offset: u64, splits: Vec<Range<u64>>) -> Self {
Self {
exec: session.create_execution_ctx(),
session,
row_offset,
arena: Arena::default(),
pipelines: Slab::default(),
free_pipelines: Vec::new(),
ready: Vec::new(),
reads: FxHashMap::default(),
next_read: 0,
new_reads: VecDeque::new(),
dirty: Vec::new(),
dirty_flags: Vec::new(),
shares: Shares::new(splits),
split: usize::MAX,
values: ValueCache::default(),
}
}
pub(crate) fn add_pipeline(
&mut self,
chain: Chain,
outlets: SmallVec<[PortId; 1]>,
) -> PipelineId {
let touched = InletSet::new(chain.inlets.len());
let changed = InletSet::all(chain.inlets.len());
let pipeline = Pipeline {
continuing: Vec::new(),
source_more: false,
source: chain.source,
stages: chain.stages,
inlets: chain.inlets,
outlets,
bytes: VecDeque::new(),
pending: 0,
finishing: None,
touched,
changed,
state: State::Runnable,
queued: true,
};
let id = match self.free_pipelines.pop() {
Some(id) => id,
None => self.pipelines.push(None),
};
for (index, &inlet) in pipeline.inlets.iter().enumerate() {
self.arena.get_mut(inlet).source = SourceEnd::Pipeline { id, inlet: index };
}
for &outlet in &pipeline.outlets {
self.arena.get_mut(outlet).sink = Some(id);
}
self.pipelines[id] = Some(pipeline);
self.ready.push(id);
id
}
pub(crate) fn compile_stage(
&mut self,
plan: &PlanRef,
rows: Range<u64>,
mask: &Mask,
(slot, split): (usize, usize),
) -> VortexResult<Option<PortId>> {
self.split = split;
let chain = self.compile(plan, rows, mask);
self.split = usize::MAX;
let Some(chain) = chain? else {
return Ok(None);
};
let port = self
.arena
.create(DEFAULT_CAPACITY, None, SourceEnd::Scan(slot));
self.add_pipeline(chain, smallvec::smallvec![port]);
#[cfg(debug_assertions)]
self.validate();
Ok(Some(port))
}
fn pipeline(&self, id: PipelineId) -> &Pipeline {
self.pipelines[id]
.as_ref()
.unwrap_or_else(|| unreachable!("pipeline {id} is not live"))
}
fn pipeline_mut(&mut self, id: PipelineId) -> &mut Pipeline {
self.pipelines[id]
.as_mut()
.unwrap_or_else(|| unreachable!("pipeline {id} is not live"))
}
fn make_runnable(&mut self, id: PipelineId) {
let pipe = self.pipeline_mut(id);
pipe.state = State::Runnable;
if !pipe.queued {
pipe.queued = true;
self.ready.push(id);
}
}
fn run(&mut self, id: PipelineId) -> VortexResult<()> {
let pipe = self.pipelines[id]
.as_mut()
.ok_or_else(|| vortex_err!("Pipeline {id} is not live"))?;
pipe.queued = false;
let mut reads = SmallVec::new();
let progress = drive(
pipe,
&mut self.arena,
&self.session,
&mut self.exec,
&mut reads,
)?;
for segment_id in reads {
self.ask(id, segment_id);
}
#[cfg(debug_assertions)]
self.validate_progress(id, &progress);
match &progress {
Progress::Blocked(SourceBlocked::Io) if !self.pipeline(id).bytes.is_empty() => {
self.make_runnable(id);
}
Progress::Blocked(blocked) => self.pipeline_mut(id).state = State::Blocked(*blocked),
Progress::Outlet => self.pipeline_mut(id).state = State::Outlet,
Progress::Done => {}
}
self.wake_neighbours(id);
if matches!(progress, Progress::Done) {
self.retire(id);
}
Ok(())
}
fn ask(&mut self, id: PipelineId, segment_id: SegmentId) {
self.read(id, segment_id);
self.pipeline_mut(id).pending += 1;
}
fn wake_neighbours(&mut self, id: PipelineId) {
for index in 0..self.pipeline(id).outlets.len() {
let port = self.pipeline(id).outlets[index];
let queue = self.arena.get_mut(port);
if !queue.take_changed() {
continue;
}
match queue.source {
SourceEnd::Pipeline { id: source, inlet } => {
let source_pipe = self.pipeline_mut(source);
source_pipe.changed.insert(inlet);
if source_pipe.state == State::Blocked(SourceBlocked::Inlet(inlet)) {
self.make_runnable(source);
}
}
SourceEnd::Scan(slot) => self.mark_dirty(slot),
SourceEnd::Unclaimed => {}
}
}
let mut touched = std::mem::take(&mut self.pipeline_mut(id).touched);
for &index in touched.indices() {
if let Some(sink) = self.arena.get(self.pipeline(id).inlets[index]).sink
&& sink != id
&& self.sink_has_room(sink)
{
self.make_runnable(sink);
}
}
touched.clear();
self.pipeline_mut(id).touched = touched;
}
fn mark_dirty(&mut self, slot: usize) {
if !self.dirty_flags[slot] {
self.dirty_flags[slot] = true;
self.dirty.push(slot);
}
}
fn sink_has_room(&self, sink: PipelineId) -> bool {
let pipe = self.pipeline(sink);
pipe.state == State::Outlet
&& pipe
.outlets
.iter()
.all(|&outlet| self.arena.has_room(outlet))
}
pub(crate) fn finish_split(&mut self, index: usize) {
for port in self.shares.finish(index) {
if let Some(sink) = self.arena.get(port).sink {
let pipe = self.pipeline_mut(sink);
pipe.outlets.retain(|outlet| *outlet != port);
debug_assert!(
!pipe.outlets.is_empty(),
"pipeline {sink} shares its output with no split"
);
self.arena.get_mut(port).sink = None;
if self.sink_has_room(sink) {
self.make_runnable(sink);
}
}
self.arena.discard(port);
}
}
fn retire(&mut self, id: PipelineId) {
if let Some(pipe) = self.pipelines[id].take() {
for inlet in pipe.inlets {
self.arena.drop_source(inlet);
}
if pipe.pending > 0 {
self.cancel_reads(id);
}
self.free_pipelines.push(id);
}
}
fn cancel_reads(&mut self, id: PipelineId) {
for (owner, _) in self.reads.values_mut() {
if *owner == Some(id) {
*owner = None;
}
}
}
fn read_output(&mut self, port: PortId) {
if let Some(sink) = self.arena.get(port).sink
&& self.sink_has_room(sink)
{
self.make_runnable(sink);
}
}
fn deliver(&mut self, read: ReadId, bytes: BufferHandle) -> VortexResult<()> {
let (owner, segment) = self
.reads
.remove(&read)
.ok_or_else(|| vortex_err!("Unknown read {read:?}"))?;
if let Some(id) = owner {
let pipe = self.pipeline_mut(id);
pipe.pending -= 1;
pipe.bytes.push_back((segment, bytes));
self.make_runnable(id);
}
Ok(())
}
fn read(&mut self, owner: PipelineId, segment_id: SegmentId) {
let read = ReadId(self.next_read);
self.next_read += 1;
self.reads.insert(read, (Some(owner), segment_id));
self.new_reads.push_back(ReadRequest {
id: read,
segment_id,
});
}
fn describe_blocked(&self) -> String {
self.pipelines
.iter()
.enumerate()
.filter_map(|(id, pipe)| {
let pipe = pipe.as_ref()?;
Some(match pipe.state {
State::Blocked(SourceBlocked::Inlet(index)) => {
let port = pipe.inlets[index];
let queue = self.arena.get(port);
format!(
"{id}: {:?} (port {port}: {} queued, closed {}, sink {:?})",
pipe.state,
queue.len(),
queue.closed(),
queue.sink.map(|sink| (sink, self.pipeline(sink).state)),
)
}
state => format!("{id}: {state:?}"),
})
})
.collect::<Vec<_>>()
.join(", ")
}
}
#[derive(Clone)]
enum Root {
Plan(PlanRef),
}
struct Active {
index: usize,
output: Option<PortId>,
}
pub struct Scan {
core: Core,
root: Option<Root>,
splits: Vec<Split>,
next: usize,
active: Vec<Option<Active>>,
live: usize,
outputs: VecDeque<(usize, ArrayRef)>,
}
impl fmt::Debug for Scan {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Scan")
.field("splits", &self.splits.len())
.field("next", &self.next)
.field("live", &self.live)
.finish()
}
}
impl Scan {
pub fn try_new(
session: VortexSession,
plan: PlanRef,
splits: Vec<Split>,
) -> VortexResult<Self> {
for split in &splits {
vortex_ensure!(
split.rows.start <= split.rows.end
&& split.rows.end <= plan.row_count()
&& split.mask.len() as u64 == split.rows.end - split.rows.start,
"Split {:?} with a mask of {} rows does not fit a plan of {} rows",
split.rows,
split.mask.len(),
plan.row_count()
);
}
let root = Root::Plan(plan);
let mut core = Core::new(
session,
0,
splits.iter().map(|split| split.rows.clone()).collect(),
);
let start = splits
.iter()
.map(|split| split.rows.start)
.min()
.unwrap_or(0);
let end = splits.iter().map(|split| split.rows.end).max().unwrap_or(0);
match &root {
Root::Plan(plan) => core.shares.add(plan, start..end)?,
}
core.shares.retain_shared();
core.dirty_flags = vec![false; 1];
Ok(Self {
core,
root: Some(root),
splits,
next: 0,
active: vec![None],
live: 0,
outputs: VecDeque::new(),
})
}
pub fn with_max_active(mut self, max_active: usize) -> Self {
self.active.resize_with(max_active.max(1), || None);
self.core.dirty_flags = vec![false; self.active.len()];
self.core.dirty = Vec::with_capacity(self.active.len());
self
}
pub fn with_row_offset(mut self, row_offset: u64) -> Self {
self.core.row_offset = row_offset;
self
}
pub fn with_value_cache(mut self, values: ValueCache) -> Self {
self.core.values = values;
self
}
pub fn step(&mut self) -> VortexResult<Turn> {
loop {
if let Some((split, array)) = self.outputs.pop_front() {
return Ok(Turn::Output(split, array));
}
if let Some(read) = self.core.new_reads.pop_front() {
return Ok(Turn::Read(read));
}
if let Some(slot) = self.core.dirty.pop() {
self.core.dirty_flags[slot] = false;
self.advance(slot)?;
continue;
}
if self.live < self.active.len() && self.next < self.splits.len() {
self.activate()?;
continue;
}
if let Some(id) = self.core.ready.pop() {
self.core.run(id)?;
continue;
}
if self.live == 0 {
return Ok(Turn::Done);
}
if !self.core.reads.is_empty() {
return Ok(Turn::Waiting);
}
vortex_bail!(
"Scan is stuck with nothing runnable and no read in flight: {}",
self.core.describe_blocked()
);
}
}
pub fn deliver(&mut self, read: ReadId, bytes: BufferHandle) -> VortexResult<()> {
self.core.deliver(read, bytes)
}
fn activate(&mut self) -> VortexResult<()> {
let slot = self
.active
.iter()
.position(Option::is_none)
.ok_or_else(|| vortex_err!("No free split slot"))?;
let index = self.next;
self.next += 1;
let split = self.splits[index].clone();
let output = match self
.root
.as_ref()
.ok_or_else(|| vortex_err!("Scan has no plan for its next split"))?
{
Root::Plan(plan) => {
self.core
.compile_stage(plan, split.rows.clone(), &split.mask, (slot, index))?
}
};
if self.next == self.splits.len() {
self.root = None;
}
if output.is_none() {
self.core.finish_split(index);
} else {
self.active[slot] = Some(Active { index, output });
self.live += 1;
}
Ok(())
}
fn advance(&mut self, slot: usize) -> VortexResult<()> {
let Some(active) = self.active[slot].as_mut() else {
return Ok(());
};
while let Some(port) = active.output {
while let Some(batch) = self.core.arena.get_mut(port).pop() {
self.outputs.push_back((active.index, batch));
}
self.core.read_output(port);
if !self.core.arena.get(port).closed() {
return Ok(());
}
self.core.arena.drop_source(port);
active.output = None;
}
self.core.finish_split(active.index);
self.active[slot] = None;
self.live -= 1;
Ok(())
}
#[cfg(test)]
pub(crate) fn live_ports(&self) -> usize {
self.core.arena.live()
}
#[cfg(test)]
pub(crate) fn awaited_reads(&self) -> usize {
self.core
.reads
.values()
.filter(|(owner, _)| owner.is_some())
.count()
}
#[cfg(test)]
pub(crate) fn pending_shares(&self) -> usize {
self.core.shares.pending()
}
}