Skip to main content

vortex_layout/plan/physical/pipeline/
port.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Ports: queues of batches with one sink and one source, kept in an arena the scan owns so a
5//! port can outlive the pipeline or split at either end.
6//!
7//! A port has a consumer for as long as anything can be pushed into it: its source goes only
8//! once the sink has closed the port and the source has drained it, or once the port is
9//! detached from its sink's outlets, so no push ever reaches a port nobody will read.
10
11use std::collections::VecDeque;
12
13use vortex_array::ArrayRef;
14
15use crate::plan::physical::pipeline::DEFAULT_CAPACITY;
16
17/// Identifies a port in the scan's arena.
18pub(crate) type PortId = usize;
19
20/// Identifies a pipeline in the scan's table.
21pub(crate) type PipelineId = usize;
22
23/// The source end of a port: who takes its batches.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub(crate) enum SourceEnd {
26    /// Nobody yet: a share's port waiting for the pipeline it was made for, or a port whose
27    /// source has gone.
28    Unclaimed,
29    /// The source of this pipeline, which reads the port as one of its inlets.
30    Pipeline { id: PipelineId, inlet: usize },
31    /// The scan, reading the output of the split's stage running in this slot.
32    Scan(usize),
33}
34
35/// A queue of batches with one sink and one source.
36///
37/// The first batch is inline: a segment scan commonly emits only one batch. Additional batches
38/// use a deque, whose buffer is retained when the port is reused.
39pub(crate) struct Queue {
40    front: Option<ArrayRef>,
41    batches: VecDeque<ArrayRef>,
42    /// Batches the queue may hold before its sink is blocked.
43    capacity: usize,
44    /// The sink will push nothing more.
45    closed: bool,
46    /// The sink appended data or closed this port since its last run was settled.
47    changed: bool,
48    /// The pipeline whose sink pushes into this port; none once closed.
49    pub(crate) sink: Option<PipelineId>,
50    pub(crate) source: SourceEnd,
51    /// The source will take nothing more. Set only once nothing more can be pushed, so the
52    /// slot is freed as soon as the port is also closed.
53    source_gone: bool,
54    /// The slot is free for reuse.
55    free: bool,
56}
57
58impl Queue {
59    fn new(capacity: usize, sink: Option<PipelineId>, source: SourceEnd) -> Self {
60        Self {
61            front: None,
62            batches: VecDeque::new(),
63            capacity: capacity.max(1),
64            closed: false,
65            changed: false,
66            sink,
67            source,
68            source_gone: false,
69            free: false,
70        }
71    }
72
73    pub(crate) fn has_room(&self) -> bool {
74        self.len() < self.capacity
75    }
76
77    pub(crate) fn closed(&self) -> bool {
78        self.closed
79    }
80
81    /// Whether the sink is never blocked by it.
82    #[cfg(debug_assertions)]
83    pub(crate) fn unbounded(&self) -> bool {
84        self.capacity == usize::MAX
85    }
86
87    pub(crate) fn len(&self) -> usize {
88        usize::from(self.front.is_some()) + self.batches.len()
89    }
90
91    #[cfg(debug_assertions)]
92    pub(crate) fn is_free(&self) -> bool {
93        self.free
94    }
95
96    pub(crate) fn take_changed(&mut self) -> bool {
97        std::mem::take(&mut self.changed)
98    }
99
100    pub(crate) fn pop(&mut self) -> Option<ArrayRef> {
101        std::mem::replace(&mut self.front, self.batches.pop_front())
102    }
103}
104
105/// The reading side of a port, borrowed for one call.
106pub struct Inlet<'a> {
107    queue: &'a mut Queue,
108}
109
110impl Inlet<'_> {
111    /// The oldest batch, in place, so the source can slice it down without removing it.
112    pub fn peek_mut(&mut self) -> Option<&mut ArrayRef> {
113        self.queue.front.as_mut()
114    }
115
116    /// The oldest batch, removed. Removing a batch is what frees its slot.
117    pub fn take(&mut self) -> Option<ArrayRef> {
118        self.queue.pop()
119    }
120
121    /// Whether the sink has finished. Closed and empty is the end of the inlet.
122    pub fn closed(&self) -> bool {
123        self.queue.closed
124    }
125
126    /// Whether no batch is queued.
127    pub fn is_empty(&self) -> bool {
128        self.queue.front.is_none()
129    }
130
131    /// Batches queued.
132    pub fn len(&self) -> usize {
133        self.queue.len()
134    }
135
136    /// Whether the sink can push no more until the source takes a batch. An unbounded inlet
137    /// counts as full at the default capacity, so a source waiting for it to fill still runs.
138    pub fn full(&self) -> bool {
139        self.queue.len() >= self.queue.capacity.min(DEFAULT_CAPACITY)
140    }
141
142    /// Rows queued, over every batch.
143    pub fn rows(&self) -> usize {
144        self.queue
145            .front
146            .iter()
147            .chain(self.queue.batches.iter())
148            .map(|batch| batch.len())
149            .sum()
150    }
151}
152
153/// A growable table that never moves its entries: entries live in blocks of fixed size, so
154/// adding one never copies the others, as a vector's growth does.
155pub(crate) struct Slab<T> {
156    blocks: Vec<Vec<T>>,
157    len: usize,
158}
159
160const BLOCK: usize = 64;
161
162impl<T> Default for Slab<T> {
163    fn default() -> Self {
164        Self {
165            blocks: Vec::new(),
166            len: 0,
167        }
168    }
169}
170
171impl<T> Slab<T> {
172    #[cfg(test)]
173    pub(crate) fn len(&self) -> usize {
174        self.len
175    }
176
177    /// Adds `value`, returning its index.
178    pub(crate) fn push(&mut self, value: T) -> usize {
179        if self.len.is_multiple_of(BLOCK) {
180            self.blocks.push(Vec::with_capacity(BLOCK));
181        }
182        let index = self.len;
183        self.blocks[index / BLOCK].push(value);
184        self.len += 1;
185        index
186    }
187
188    pub(crate) fn iter(&self) -> impl Iterator<Item = &T> {
189        self.blocks.iter().flatten()
190    }
191}
192
193impl<T> std::ops::Index<usize> for Slab<T> {
194    type Output = T;
195
196    fn index(&self, index: usize) -> &T {
197        &self.blocks[index / BLOCK][index % BLOCK]
198    }
199}
200
201impl<T> std::ops::IndexMut<usize> for Slab<T> {
202    fn index_mut(&mut self, index: usize) -> &mut T {
203        &mut self.blocks[index / BLOCK][index % BLOCK]
204    }
205}
206
207/// Every port of a scan. Slots are reused once a port's sink has closed it and its source has
208/// gone, so no pipeline still refers to it.
209#[derive(Default)]
210pub(crate) struct Arena {
211    queues: Slab<Queue>,
212    free: Vec<PortId>,
213}
214
215impl Arena {
216    pub(crate) fn create(
217        &mut self,
218        capacity: usize,
219        sink: Option<PipelineId>,
220        source: SourceEnd,
221    ) -> PortId {
222        match self.free.pop() {
223            Some(id) => {
224                // A freed slot keeps any overflow buffer for reuse.
225                let queue = &mut self.queues[id];
226                let capacity = capacity.max(1);
227                queue.capacity = capacity;
228                queue.closed = false;
229                queue.changed = false;
230                queue.sink = sink;
231                queue.source = source;
232                queue.source_gone = false;
233                queue.free = false;
234                id
235            }
236            None => self.queues.push(Queue::new(capacity, sink, source)),
237        }
238    }
239
240    pub(crate) fn get(&self, id: PortId) -> &Queue {
241        &self.queues[id]
242    }
243
244    pub(crate) fn get_mut(&mut self, id: PortId) -> &mut Queue {
245        &mut self.queues[id]
246    }
247
248    pub(crate) fn inlet(&mut self, id: PortId) -> Inlet<'_> {
249        Inlet {
250            queue: &mut self.queues[id],
251        }
252    }
253
254    pub(crate) fn has_room(&self, id: PortId) -> bool {
255        self.queues[id].has_room()
256    }
257
258    /// Appends a batch. Empty batches are dropped.
259    pub(crate) fn push(&mut self, id: PortId, batch: ArrayRef) {
260        let queue = &mut self.queues[id];
261        debug_assert!(
262            !queue.source_gone && !queue.closed,
263            "push into port {id} with no consumer"
264        );
265        if !batch.is_empty() {
266            if queue.front.is_none() {
267                queue.front = Some(batch);
268            } else {
269                queue.batches.push_back(batch);
270            }
271            queue.changed = true;
272        }
273    }
274
275    /// The sink will push nothing more.
276    pub(crate) fn close(&mut self, id: PortId) {
277        let queue = &mut self.queues[id];
278        queue.changed |= !queue.closed;
279        queue.closed = true;
280        queue.sink = None;
281        self.maybe_free(id);
282    }
283
284    /// The source will take nothing more: what is queued is dropped. Nothing more may be
285    /// pushed, so the sink must have closed the port or be about to, as a retiring pipeline's
286    /// inlets' sinks have.
287    pub(crate) fn drop_source(&mut self, id: PortId) {
288        let queue = &mut self.queues[id];
289        queue.source_gone = true;
290        queue.source = SourceEnd::Unclaimed;
291        queue.front = None;
292        queue.batches.clear();
293        self.maybe_free(id);
294    }
295
296    /// Frees a port nobody will read, once it is no longer an outlet of any pipeline: a share's
297    /// port whose claimant never came. Closes it on the sink's behalf if the sink has not.
298    pub(crate) fn discard(&mut self, id: PortId) {
299        let queue = &mut self.queues[id];
300        debug_assert!(
301            queue.sink.is_none() && queue.source == SourceEnd::Unclaimed,
302            "discarding port {id} still attached to a pipeline"
303        );
304        queue.closed = true;
305        queue.source_gone = true;
306        queue.front = None;
307        queue.batches.clear();
308        self.maybe_free(id);
309    }
310
311    fn maybe_free(&mut self, id: PortId) {
312        let queue = &mut self.queues[id];
313        if queue.closed && queue.source_gone && !queue.free {
314            queue.free = true;
315            queue.front = None;
316            queue.batches.clear();
317            self.free.push(id);
318        }
319    }
320
321    /// Every port slot, free or not, with its id.
322    #[cfg(debug_assertions)]
323    pub(crate) fn iter(&self) -> impl Iterator<Item = (PortId, &Queue)> {
324        self.queues.iter().enumerate()
325    }
326
327    /// Ports in use.
328    #[cfg(test)]
329    pub(crate) fn live(&self) -> usize {
330        self.queues.len() - self.free.len()
331    }
332}
333
334#[cfg(test)]
335mod tests {
336    use rstest::rstest;
337    use vortex_array::ArrayRef;
338    use vortex_array::IntoArray;
339    use vortex_buffer::buffer;
340
341    use super::Arena;
342    use super::SourceEnd;
343
344    #[rstest]
345    #[case::single(1)]
346    #[case::bounded(4)]
347    #[case::unbounded(usize::MAX)]
348    fn queued_batches_keep_order_and_reused_ports_are_empty(#[case] capacity: usize) {
349        let mut arena = Arena::default();
350        let port = arena.create(capacity, None, SourceEnd::Scan(0));
351        let arrays: Vec<_> = (0..capacity.min(4))
352            .map(|i| buffer![i as u64].into_array())
353            .collect();
354        for array in &arrays {
355            assert!(arena.has_room(port));
356            arena.push(port, array.clone());
357        }
358        assert_eq!(arena.inlet(port).len(), arrays.len());
359        assert_eq!(arena.inlet(port).rows(), arrays.len());
360        arena.close(port);
361        for expected in arrays {
362            let actual = arena.inlet(port).take().expect("queued batch");
363            assert!(ArrayRef::ptr_eq(&actual, &expected));
364        }
365        assert!(arena.inlet(port).is_empty());
366        arena.drop_source(port);
367        let reused = arena.create(1, None, SourceEnd::Scan(0));
368        assert_eq!(reused, port);
369        assert!(arena.inlet(reused).is_empty());
370        assert!(arena.has_room(reused));
371    }
372}