use std::collections::BTreeMap;
use std::collections::VecDeque;
use std::sync::Arc;
use std::task::Context;
use std::task::Poll;
use vortex_array::ArrayRef;
use vortex_error::VortexResult;
use vortex_error::vortex_bail;
use vortex_error::vortex_err;
use vortex_io::request::Completion;
use vortex_io::request::IoBatch;
use vortex_io::request::IoConsumer;
use vortex_io::request::IoIntent;
use vortex_io::request::IoOwnerId;
use vortex_io::request::IoRequestId;
use vortex_io::request::IoResult;
use vortex_io::request::IoSource;
use vortex_io::request::IoTarget;
use vortex_utils::aliases::hash_map::Entry;
use vortex_utils::aliases::hash_map::HashMap;
use crate::planning::morsel::Morsel;
use crate::planning::morsel::MorselOutput;
use crate::planning::planner::Planner;
use crate::planning::planner::PlannerOutput;
use crate::planning::planner::State;
use crate::planning::planner::WorkScope;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RootId(pub u64);
pub struct Batch {
pub root: RootId,
pub scope: WorkScope,
pub array: ArrayRef,
}
pub enum Progress {
Batch(Batch),
RootDone(RootId),
Waiting,
Idle,
}
enum Item {
Planner(Box<dyn Planner>),
Morsel(Box<dyn Morsel>),
}
enum Output {
Planner(PlannerOutput),
Morsel(MorselOutput),
}
struct Work {
id: IoOwnerId,
root: RootId,
scope: WorkScope,
item: Item,
path: Vec<u64>,
next_child: u64,
outstanding: HashMap<IoRequestId, IoTarget>,
}
impl Work {
fn priority(&self) -> Vec<u64> {
let mut priority = self.path.clone();
priority.push(self.next_child);
priority
}
fn child_path(&mut self) -> Vec<u64> {
let path = self.priority();
self.next_child += 1;
path
}
fn state(&self) -> State {
match &self.item {
Item::Planner(planner) => planner.state(),
Item::Morsel(morsel) => morsel.state(),
}
}
fn consumer(&mut self) -> &mut dyn IoConsumer {
match &mut self.item {
Item::Planner(planner) => &mut **planner,
Item::Morsel(morsel) => &mut **morsel,
}
}
fn deliver(&mut self, request: IoRequestId, result: IoResult) -> VortexResult<()> {
let Some(target) = self.outstanding.remove(&request) else {
vortex_bail!(
"completion for {request:?} of {:?}, which is not outstanding",
self.id
);
};
if !result.matches(&target) {
vortex_bail!("IO source answered {target:?} with {}", result.kind());
}
self.consumer().set_io_result(request, result);
Ok(())
}
}
#[derive(Default)]
struct ReadyQueue {
priorities: BTreeMap<Vec<u64>, IoOwnerId>,
work: HashMap<IoOwnerId, Box<Work>>,
}
impl ReadyQueue {
fn insert(&mut self, work: Box<Work>) {
self.priorities.insert(work.priority(), work.id);
self.work.insert(work.id, work);
}
fn pop(&mut self) -> Option<Box<Work>> {
let (_, owner) = self.priorities.pop_first()?;
self.work.remove(&owner)
}
fn is_empty(&self) -> bool {
self.priorities.is_empty()
}
fn cancel(&mut self, root: RootId) {
self.work.retain(|_, work| work.root != root);
self.priorities
.retain(|_, owner| self.work.contains_key(owner));
}
}
const IO_POLL_INTERVAL: usize = 64;
struct Root {
io: Arc<dyn IoSource>,
live: usize,
}
#[derive(Default)]
pub struct Run {
roots: HashMap<RootId, Root>,
next_root: u64,
next_owner: u64,
queue: ReadyQueue,
parked: HashMap<IoOwnerId, Box<Work>>,
events: VecDeque<Progress>,
visits_until_poll: usize,
step_limit: Option<usize>,
steps: usize,
}
impl Drop for Run {
fn drop(&mut self) {
for root in self.roots.values() {
root.io.clear();
}
}
}
impl Run {
pub fn new() -> Self {
Self::default()
}
pub fn with_step_limit(mut self, steps: usize) -> Self {
self.step_limit = Some(steps);
self
}
pub fn admit(
&mut self,
root: Box<dyn Planner>,
scope: WorkScope,
io: Arc<dyn IoSource>,
) -> RootId {
let id = RootId(self.next_root);
self.next_root += 1;
self.roots.insert(id, Root { io, live: 0 });
self.spawn(id, scope, Item::Planner(root), vec![id.0]);
id
}
pub fn cancel(&mut self, root: RootId) {
let Some(cancelled) = self.roots.remove(&root) else {
return;
};
self.queue.cancel(root);
self.parked.retain(|_, work| work.root != root);
cancelled.io.clear();
}
pub fn live_roots(&self) -> usize {
self.roots.len()
}
pub fn advance(&mut self) -> VortexResult<Progress> {
loop {
if let Some(event) = self.events.pop_front() {
return Ok(event);
}
if self.visits_until_poll == 0 || self.queue.is_empty() {
self.take_completions()?;
self.visits_until_poll = IO_POLL_INTERVAL;
if self.queue.is_empty() {
return Ok(if self.parked.is_empty() {
Progress::Idle
} else {
Progress::Waiting
});
}
}
self.visits_until_poll -= 1;
let Some(work) = self.queue.pop() else {
continue;
};
self.steps += 1;
if let Some(limit) = self.step_limit
&& self.steps > limit
{
vortex_bail!("driver exceeded its step limit of {limit}");
}
self.visit(work)?;
}
}
pub fn complete(&mut self, completion: Completion) -> VortexResult<()> {
let Completion {
owner,
request,
result,
} = completion;
let result = result?;
if let Entry::Occupied(mut parked) = self.parked.entry(owner) {
let work = parked.get_mut();
work.deliver(request, result)?;
if work.state() != State::Waiting {
let work = parked.remove();
self.queue.insert(work);
} else if work.outstanding.is_empty() {
vortex_bail!(
"work for scope {:?} waits with no outstanding fetch",
work.scope
);
}
return Ok(());
}
let Some(work) = self.queue.work.get_mut(&owner) else {
vortex_bail!("completion for unknown work {owner:?}");
};
work.deliver(request, result)
}
pub fn poll_completion(&mut self, cx: &mut Context<'_>) -> Poll<VortexResult<()>> {
let sources: Vec<_> = self
.roots
.values()
.map(|root| Arc::clone(&root.io))
.collect();
if sources.is_empty() {
return Poll::Ready(Err(vortex_err!(
"the run is waiting for IO with no live root"
)));
}
let mut delivered = false;
for io in sources {
match io.poll_completion(cx) {
Poll::Ready(Ok(completion)) => {
if let Err(error) = self.complete(completion) {
return Poll::Ready(Err(error));
}
delivered = true;
}
Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
Poll::Pending => {}
}
}
if delivered {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
fn take_completions(&mut self) -> VortexResult<()> {
if self.roots.len() == 1 {
let Some(io) = self.roots.values().next().map(|root| Arc::clone(&root.io)) else {
return Ok(());
};
while let Some(completion) = io.poll()? {
self.complete(completion)?;
}
return Ok(());
}
let sources: Vec<_> = self
.roots
.values()
.map(|root| Arc::clone(&root.io))
.collect();
for io in sources {
while let Some(completion) = io.poll()? {
self.complete(completion)?;
}
}
Ok(())
}
fn spawn(&mut self, root: RootId, scope: WorkScope, item: Item, path: Vec<u64>) {
let id = IoOwnerId(self.next_owner);
self.next_owner += 1;
if let Some(root) = self.roots.get_mut(&root) {
root.live += 1;
}
let work = Box::new(Work {
id,
root,
scope,
item,
path,
next_child: 0,
outstanding: HashMap::default(),
});
self.queue.insert(work);
}
fn retire(&mut self, work: &Work) {
let Entry::Occupied(mut root) = self.roots.entry(work.root) else {
return;
};
root.get().io.release(work.id);
root.get_mut().live -= 1;
if root.get().live == 0 {
root.remove().io.clear();
self.events.push_back(Progress::RootDone(work.root));
}
}
fn park(&mut self, work: Box<Work>) -> VortexResult<()> {
if work.outstanding.is_empty() {
vortex_bail!(
"work for scope {:?} waits with no outstanding fetch",
work.scope
);
}
self.parked.insert(work.id, work);
Ok(())
}
fn settle(&mut self, work: Box<Work>) -> VortexResult<()> {
match work.state() {
State::Done => self.retire(&work),
State::NeedsCompute => self.queue.insert(work),
State::Waiting => self.park(work)?,
}
Ok(())
}
fn visit(&mut self, mut work: Box<Work>) -> VortexResult<()> {
let output = match &mut work.item {
Item::Planner(planner) => match planner.state() {
State::NeedsCompute => Output::Planner(planner.compute()?),
_ => return self.settle(work),
},
Item::Morsel(morsel) => match morsel.state() {
State::NeedsCompute => Output::Morsel(morsel.compute()?),
_ => return self.settle(work),
},
};
match output {
Output::Planner(PlannerOutput::Done) | Output::Morsel(MorselOutput::Done) => {
self.retire(&work);
}
Output::Planner(PlannerOutput::Continue) | Output::Morsel(MorselOutput::Continue) => {
self.queue.insert(work);
}
Output::Planner(PlannerOutput::NeedsIO(batch))
| Output::Morsel(MorselOutput::NeedsIO(batch)) => {
self.submit(&mut work, batch)?;
self.settle(work)?;
}
Output::Planner(PlannerOutput::Planner(scope, child)) => {
let path = work.child_path();
self.spawn(work.root, scope, Item::Planner(child), path);
self.queue.insert(work);
}
Output::Planner(PlannerOutput::Morsel(scope, morsel)) => {
let path = work.child_path();
self.spawn(work.root, scope, Item::Morsel(morsel), path);
self.queue.insert(work);
}
Output::Morsel(MorselOutput::Batch(array)) => {
if array.is_empty() {
vortex_bail!("morsel for scope {:?} returned an empty batch", work.scope);
}
self.events.push_back(Progress::Batch(Batch {
root: work.root,
scope: work.scope.clone(),
array,
}));
self.queue.insert(work);
}
}
Ok(())
}
fn submit(&mut self, work: &mut Work, batch: IoBatch) -> VortexResult<()> {
if batch.is_empty() {
vortex_bail!("compute() published an empty batch");
}
for request in &batch {
if request.intent == IoIntent::Fetch
&& work
.outstanding
.insert(request.request, request.target)
.is_some()
{
vortex_bail!(
"fetch {:?} of {:?} was published while it is outstanding",
request.request,
work.id
);
}
}
let Some(root) = self.roots.get(&work.root) else {
vortex_bail!("work {:?} outlived its root {:?}", work.id, work.root);
};
root.io.submit(work.id, batch)
}
}
pub struct Driver {
io: Arc<dyn IoSource>,
step_limit: Option<usize>,
}
impl Driver {
pub fn new(io: Arc<dyn IoSource>) -> Self {
Self {
io,
step_limit: None,
}
}
pub fn with_step_limit(self, steps: usize) -> Self {
Self {
step_limit: Some(steps),
..self
}
}
pub fn run(&self, root: Box<dyn Planner>) -> VortexResult<Vec<Batch>> {
let mut run = Run::new();
run.step_limit = self.step_limit;
let scope = WorkScope {
file_ordinal: 0,
rows: 0..0,
};
run.admit(root, scope, Arc::clone(&self.io));
let mut batches = Vec::new();
loop {
match run.advance()? {
Progress::Batch(batch) => batches.push(batch),
Progress::RootDone(_) | Progress::Idle => return Ok(batches),
Progress::Waiting => run.complete(self.io.wait()?)?,
}
}
}
}
#[cfg(test)]
mod tests;