use std::collections::VecDeque;
use vortex_array::ArrayRef;
use crate::plan::physical::pipeline::DEFAULT_CAPACITY;
pub(crate) type PortId = usize;
pub(crate) type PipelineId = usize;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SourceEnd {
Unclaimed,
Pipeline { id: PipelineId, inlet: usize },
Scan(usize),
}
pub(crate) struct Queue {
front: Option<ArrayRef>,
batches: VecDeque<ArrayRef>,
capacity: usize,
closed: bool,
changed: bool,
pub(crate) sink: Option<PipelineId>,
pub(crate) source: SourceEnd,
source_gone: bool,
free: bool,
}
impl Queue {
fn new(capacity: usize, sink: Option<PipelineId>, source: SourceEnd) -> Self {
Self {
front: None,
batches: VecDeque::new(),
capacity: capacity.max(1),
closed: false,
changed: false,
sink,
source,
source_gone: false,
free: false,
}
}
pub(crate) fn has_room(&self) -> bool {
self.len() < self.capacity
}
pub(crate) fn closed(&self) -> bool {
self.closed
}
#[cfg(debug_assertions)]
pub(crate) fn unbounded(&self) -> bool {
self.capacity == usize::MAX
}
pub(crate) fn len(&self) -> usize {
usize::from(self.front.is_some()) + self.batches.len()
}
#[cfg(debug_assertions)]
pub(crate) fn is_free(&self) -> bool {
self.free
}
pub(crate) fn take_changed(&mut self) -> bool {
std::mem::take(&mut self.changed)
}
pub(crate) fn pop(&mut self) -> Option<ArrayRef> {
std::mem::replace(&mut self.front, self.batches.pop_front())
}
}
pub struct Inlet<'a> {
queue: &'a mut Queue,
}
impl Inlet<'_> {
pub fn peek_mut(&mut self) -> Option<&mut ArrayRef> {
self.queue.front.as_mut()
}
pub fn take(&mut self) -> Option<ArrayRef> {
self.queue.pop()
}
pub fn closed(&self) -> bool {
self.queue.closed
}
pub fn is_empty(&self) -> bool {
self.queue.front.is_none()
}
pub fn len(&self) -> usize {
self.queue.len()
}
pub fn full(&self) -> bool {
self.queue.len() >= self.queue.capacity.min(DEFAULT_CAPACITY)
}
pub fn rows(&self) -> usize {
self.queue
.front
.iter()
.chain(self.queue.batches.iter())
.map(|batch| batch.len())
.sum()
}
}
pub(crate) struct Slab<T> {
blocks: Vec<Vec<T>>,
len: usize,
}
const BLOCK: usize = 64;
impl<T> Default for Slab<T> {
fn default() -> Self {
Self {
blocks: Vec::new(),
len: 0,
}
}
}
impl<T> Slab<T> {
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.len
}
pub(crate) fn push(&mut self, value: T) -> usize {
if self.len.is_multiple_of(BLOCK) {
self.blocks.push(Vec::with_capacity(BLOCK));
}
let index = self.len;
self.blocks[index / BLOCK].push(value);
self.len += 1;
index
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &T> {
self.blocks.iter().flatten()
}
}
impl<T> std::ops::Index<usize> for Slab<T> {
type Output = T;
fn index(&self, index: usize) -> &T {
&self.blocks[index / BLOCK][index % BLOCK]
}
}
impl<T> std::ops::IndexMut<usize> for Slab<T> {
fn index_mut(&mut self, index: usize) -> &mut T {
&mut self.blocks[index / BLOCK][index % BLOCK]
}
}
#[derive(Default)]
pub(crate) struct Arena {
queues: Slab<Queue>,
free: Vec<PortId>,
}
impl Arena {
pub(crate) fn create(
&mut self,
capacity: usize,
sink: Option<PipelineId>,
source: SourceEnd,
) -> PortId {
match self.free.pop() {
Some(id) => {
let queue = &mut self.queues[id];
let capacity = capacity.max(1);
queue.capacity = capacity;
queue.closed = false;
queue.changed = false;
queue.sink = sink;
queue.source = source;
queue.source_gone = false;
queue.free = false;
id
}
None => self.queues.push(Queue::new(capacity, sink, source)),
}
}
pub(crate) fn get(&self, id: PortId) -> &Queue {
&self.queues[id]
}
pub(crate) fn get_mut(&mut self, id: PortId) -> &mut Queue {
&mut self.queues[id]
}
pub(crate) fn inlet(&mut self, id: PortId) -> Inlet<'_> {
Inlet {
queue: &mut self.queues[id],
}
}
pub(crate) fn has_room(&self, id: PortId) -> bool {
self.queues[id].has_room()
}
pub(crate) fn push(&mut self, id: PortId, batch: ArrayRef) {
let queue = &mut self.queues[id];
debug_assert!(
!queue.source_gone && !queue.closed,
"push into port {id} with no consumer"
);
if !batch.is_empty() {
if queue.front.is_none() {
queue.front = Some(batch);
} else {
queue.batches.push_back(batch);
}
queue.changed = true;
}
}
pub(crate) fn close(&mut self, id: PortId) {
let queue = &mut self.queues[id];
queue.changed |= !queue.closed;
queue.closed = true;
queue.sink = None;
self.maybe_free(id);
}
pub(crate) fn drop_source(&mut self, id: PortId) {
let queue = &mut self.queues[id];
queue.source_gone = true;
queue.source = SourceEnd::Unclaimed;
queue.front = None;
queue.batches.clear();
self.maybe_free(id);
}
pub(crate) fn discard(&mut self, id: PortId) {
let queue = &mut self.queues[id];
debug_assert!(
queue.sink.is_none() && queue.source == SourceEnd::Unclaimed,
"discarding port {id} still attached to a pipeline"
);
queue.closed = true;
queue.source_gone = true;
queue.front = None;
queue.batches.clear();
self.maybe_free(id);
}
fn maybe_free(&mut self, id: PortId) {
let queue = &mut self.queues[id];
if queue.closed && queue.source_gone && !queue.free {
queue.free = true;
queue.front = None;
queue.batches.clear();
self.free.push(id);
}
}
#[cfg(debug_assertions)]
pub(crate) fn iter(&self) -> impl Iterator<Item = (PortId, &Queue)> {
self.queues.iter().enumerate()
}
#[cfg(test)]
pub(crate) fn live(&self) -> usize {
self.queues.len() - self.free.len()
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use vortex_array::ArrayRef;
use vortex_array::IntoArray;
use vortex_buffer::buffer;
use super::Arena;
use super::SourceEnd;
#[rstest]
#[case::single(1)]
#[case::bounded(4)]
#[case::unbounded(usize::MAX)]
fn queued_batches_keep_order_and_reused_ports_are_empty(#[case] capacity: usize) {
let mut arena = Arena::default();
let port = arena.create(capacity, None, SourceEnd::Scan(0));
let arrays: Vec<_> = (0..capacity.min(4))
.map(|i| buffer![i as u64].into_array())
.collect();
for array in &arrays {
assert!(arena.has_room(port));
arena.push(port, array.clone());
}
assert_eq!(arena.inlet(port).len(), arrays.len());
assert_eq!(arena.inlet(port).rows(), arrays.len());
arena.close(port);
for expected in arrays {
let actual = arena.inlet(port).take().expect("queued batch");
assert!(ArrayRef::ptr_eq(&actual, &expected));
}
assert!(arena.inlet(port).is_empty());
arena.drop_source(port);
let reused = arena.create(1, None, SourceEnd::Scan(0));
assert_eq!(reused, port);
assert!(arena.inlet(reused).is_empty());
assert!(arena.has_room(reused));
}
}