Skip to main content

rudb_io/
submit.rs

1//! Stating every read up front, and waiting for the answers.
2//!
3//! `spec/engine/05-scan.md` section 5.3 is the specification. `File::read_at` is synchronous and a
4//! thread that calls it stops until the bytes arrive, which on a machine where the data does not
5//! fit in the page cache is a core that is not computing. The answer is the one DuckDB v2.0
6//! reached and it is implementable with the standard library alone: the caller states everything
7//! it wants in one call and then either waits or goes and does something else.
8//!
9//! # Why this is not ceremony
10//!
11//! Two things pay for the interface, and neither of them is available to a caller that reads one
12//! range at a time.
13//!
14//! Batching. A Parquet row group scan knows every byte range it needs before it reads any of them,
15//! so it can hand all of them over at once, and a backend that has all of them at once can issue
16//! them concurrently and can coalesce adjacent ranges into one larger read. On object storage the
17//! per request cost dominates and the coalescing is worth more than the concurrency.
18//!
19//! Overlap. The scan submits the reads for row group n plus one while it is decoding row group n,
20//! which is the read ahead that turns a stop and go scan into a continuous one.
21//!
22//! # The shape io_uring needs
23//!
24//! io_uring is deferred, per section 5.3, because it is Linux only and it is a large amount of
25//! unsafe code against a raw syscall interface under a zero dependency rule. What is not deferred
26//! is the shape, because retrofitting it is the expensive half.
27//!
28//! A [`Request`] owns its destination buffer and hands that buffer back inside the [`Response`].
29//! That is not an accident of the borrow checker, it is what a kernel interface wants: the buffer
30//! has to stay alive and untouched for as long as the kernel might write into it, which a borrow
31//! cannot promise once the submitting stack frame is free to return. Ownership can, and it is the
32//! same ownership a registered buffer pool would hand out. So the day io_uring arrives it goes in
33//! under [`File::submit`] and no caller changes.
34//!
35//! [`File::submit`]: crate::File::submit
36
37use std::collections::VecDeque;
38use std::sync::{Arc, Condvar, Mutex};
39
40use rudb_common::{Error, Result};
41
42/// One read, with the buffer its bytes land in.
43///
44/// The length wanted is the length of the buffer. There is no separate length field because two
45/// fields that have to agree are two fields that disagree eventually.
46#[derive(Debug)]
47pub struct Request {
48    offset: u64,
49    buf: Vec<u8>,
50}
51
52impl Request {
53    /// A read of `len` bytes at `offset`, into a buffer allocated here.
54    #[must_use]
55    pub fn new(offset: u64, len: usize) -> Self {
56        Self { offset, buf: vec![0; len] }
57    }
58
59    /// A read at `offset` into a buffer the caller already has.
60    ///
61    /// The buffer's length is the length of the read, and its contents are overwritten. This is
62    /// the form a scan uses on its second row group, because the alternative is allocating a
63    /// column's worth of page buffers per row group for the life of the query.
64    #[must_use]
65    pub fn reusing(offset: u64, buf: Vec<u8>) -> Self {
66        Self { offset, buf }
67    }
68
69    /// Where in the file the read starts.
70    #[must_use]
71    pub fn offset(&self) -> u64 {
72        self.offset
73    }
74
75    /// How many bytes are wanted.
76    #[must_use]
77    pub fn len(&self) -> usize {
78        self.buf.len()
79    }
80
81    /// Whether this request asks for nothing.
82    #[must_use]
83    pub fn is_empty(&self) -> bool {
84        self.buf.is_empty()
85    }
86
87    /// One past the last byte wanted.
88    #[must_use]
89    pub fn end(&self) -> u64 {
90        self.offset + self.buf.len() as u64
91    }
92
93    /// The destination buffer, taken out of the request.
94    #[must_use]
95    pub fn into_buffer(self) -> Vec<u8> {
96        self.buf
97    }
98}
99
100/// One finished read.
101///
102/// The `index` is the position the request had in the vector handed to `submit`, and it is here
103/// because completions do not arrive in submission order. A caller that decodes as answers arrive
104/// has no other way to know which page it is looking at.
105#[derive(Debug)]
106pub struct Response {
107    index: usize,
108    offset: u64,
109    read: usize,
110    buf: Vec<u8>,
111}
112
113impl Response {
114    /// A finished read of `read` bytes into `buf`.
115    #[must_use]
116    pub fn new(index: usize, offset: u64, read: usize, buf: Vec<u8>) -> Self {
117        Self { index, offset, read, buf }
118    }
119
120    /// Which request this answers, by position in the submitted vector.
121    #[must_use]
122    pub fn index(&self) -> usize {
123        self.index
124    }
125
126    /// Where in the file the read started.
127    #[must_use]
128    pub fn offset(&self) -> u64 {
129        self.offset
130    }
131
132    /// How many bytes arrived.
133    #[must_use]
134    pub fn read(&self) -> usize {
135        self.read
136    }
137
138    /// The bytes that arrived, which is a prefix of the buffer and not all of it.
139    #[must_use]
140    pub fn bytes(&self) -> &[u8] {
141        &self.buf[..self.read]
142    }
143
144    /// Whether fewer bytes arrived than were asked for.
145    ///
146    /// A short read is not an error here for the same reason it is not one in `read_at`: the end
147    /// of the file is a fact about the file and the caller is the one that knows whether it was
148    /// expecting to be there. A caller that cannot proceed on a short read says so itself.
149    #[must_use]
150    pub fn is_short(&self) -> bool {
151        self.read < self.buf.len()
152    }
153
154    /// The buffer, taken out of the response so it can be handed to the next request.
155    ///
156    /// The bytes past [`Self::read`] are whatever was in the buffer before.
157    #[must_use]
158    pub fn into_buffer(self) -> Vec<u8> {
159        self.buf
160    }
161}
162
163#[derive(Debug)]
164struct State {
165    /// One slot per request, filled when that read finishes.
166    slots: Vec<Option<Result<Response>>>,
167    /// The slots that are filled and not yet handed out, in the order they were filled.
168    ready: VecDeque<usize>,
169    /// How many slots are still empty.
170    outstanding: usize,
171    /// How many [`Filler`] handles exist. When this reaches zero with slots still empty, the
172    /// backend went away and the waiters are woken with an error rather than left on the condvar.
173    fillers: usize,
174}
175
176#[derive(Debug)]
177struct Shared {
178    state: Mutex<State>,
179    wake: Condvar,
180}
181
182impl Shared {
183    fn lock(&self) -> std::sync::MutexGuard<'_, State> {
184        // A poisoned mutex means a thread panicked while holding it, and the panic is the finding.
185        // Propagating a lock error on top of it would bury the thing that actually went wrong.
186        self.state.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
187    }
188}
189
190/// The end of a [`Completion`] that a backend fills in.
191///
192/// Cloneable, because a batch of reads is served by however many I/O threads the pool feels like
193/// putting on it and each of them finishes its own requests.
194#[derive(Debug)]
195pub struct Filler {
196    shared: Arc<Shared>,
197}
198
199impl Clone for Filler {
200    fn clone(&self) -> Self {
201        self.shared.lock().fillers += 1;
202        Self { shared: Arc::clone(&self.shared) }
203    }
204}
205
206impl Filler {
207    /// Records the outcome of the request at `index`.
208    ///
209    /// Filling the same index twice is ignored rather than treated as an error, because the
210    /// alternative is a backend that panics inside an I/O thread on a bug that a wrong answer test
211    /// would have caught anyway.
212    pub fn finish(&self, index: usize, outcome: Result<Response>) {
213        let mut state = self.shared.lock();
214        if state.slots.get(index).is_none_or(Option::is_some) {
215            return;
216        }
217        state.slots[index] = Some(outcome);
218        state.ready.push_back(index);
219        state.outstanding -= 1;
220        drop(state);
221        self.shared.wake.notify_all();
222    }
223}
224
225impl Drop for Filler {
226    fn drop(&mut self) {
227        let mut state = self.shared.lock();
228        state.fillers -= 1;
229        if state.fillers > 0 || state.outstanding == 0 {
230            return;
231        }
232        // Nobody is left to fill these. A caller blocked in `wait` would otherwise be blocked
233        // forever, and the test gate for the scan asks for no wrong answers and no hangs, in that
234        // order, which makes this the case worth being explicit about rather than the one worth
235        // assuming cannot happen.
236        for index in 0..state.slots.len() {
237            if state.slots[index].is_some() {
238                continue;
239            }
240            state.slots[index] =
241                Some(Err(Error::io("the I/O backend stopped before this read finished")));
242            state.ready.push_back(index);
243        }
244        state.outstanding = 0;
245        drop(state);
246        self.shared.wake.notify_all();
247    }
248}
249
250/// The answer to a batch of reads, which can be waited on or polled.
251///
252/// Two ways to consume one and they are for different callers. [`Completion::wait`] is for the
253/// caller that needs all of it before it can do anything, which is most of them and is what
254/// `read_at` is written in terms of. [`Completion::take`] hands back reads in the order they
255/// finished, which is for the scan that decodes a page as soon as that page has arrived instead of
256/// waiting for the slowest read in the row group.
257#[derive(Debug)]
258pub struct Completion {
259    shared: Arc<Shared>,
260    /// How many responses have been handed out by [`Completion::take`].
261    taken: usize,
262}
263
264impl Completion {
265    /// A completion for `count` requests, and the handle a backend fills it through.
266    #[must_use]
267    pub fn pending(count: usize) -> (Self, Filler) {
268        let mut slots = Vec::with_capacity(count);
269        slots.resize_with(count, || None);
270        let shared = Arc::new(Shared {
271            state: Mutex::new(State {
272                slots,
273                ready: VecDeque::with_capacity(count),
274                outstanding: count,
275                fillers: 1,
276            }),
277            wake: Condvar::new(),
278        });
279        (Self { shared: Arc::clone(&shared), taken: 0 }, Filler { shared })
280    }
281
282    /// A completion that is already finished, for a backend with nothing to wait for.
283    #[must_use]
284    pub fn ready(responses: Vec<Result<Response>>) -> Self {
285        let (completion, filler) = Self::pending(responses.len());
286        for (index, outcome) in responses.into_iter().enumerate() {
287            filler.finish(index, outcome);
288        }
289        completion
290    }
291
292    /// How many requests were submitted.
293    #[must_use]
294    pub fn len(&self) -> usize {
295        self.shared.lock().slots.len()
296    }
297
298    /// Whether nothing was submitted.
299    #[must_use]
300    pub fn is_empty(&self) -> bool {
301        self.len() == 0
302    }
303
304    /// How many reads have finished and not been taken, without blocking.
305    ///
306    /// This is the poll. A scan uses it to decide whether there is decoding to be getting on with
307    /// or whether it may as well wait.
308    #[must_use]
309    pub fn ready_count(&self) -> usize {
310        self.shared.lock().ready.len()
311    }
312
313    /// Whether every read has finished, without blocking.
314    #[must_use]
315    pub fn is_done(&self) -> bool {
316        self.shared.lock().outstanding == 0
317    }
318
319    /// The next read to finish, blocking until one does.
320    ///
321    /// `None` once every request has been handed back. The order is completion order, which is why
322    /// [`Response::index`] exists.
323    pub fn take(&mut self) -> Option<Result<Response>> {
324        let mut state = self.shared.lock();
325        loop {
326            if let Some(index) = state.ready.pop_front() {
327                self.taken += 1;
328                return state.slots[index].take();
329            }
330            if state.outstanding == 0 {
331                return None;
332            }
333            state = self.shared.wake.wait(state).unwrap_or_else(std::sync::PoisonError::into_inner);
334        }
335    }
336
337    /// Every response that has not already been taken, in the order the requests were submitted.
338    ///
339    /// Blocks until the last read finishes.
340    ///
341    /// # Errors
342    ///
343    /// The first failure by request position, so that two runs of the same faulty read report the
344    /// same error rather than whichever one happened to finish first.
345    pub fn wait(mut self) -> Result<Vec<Response>> {
346        let mut state = self.shared.lock();
347        while state.outstanding > 0 {
348            state = self.shared.wake.wait(state).unwrap_or_else(std::sync::PoisonError::into_inner);
349        }
350        state.ready.clear();
351        self.taken = state.slots.len();
352        let mut out = Vec::with_capacity(state.slots.len());
353        let mut failure = None;
354        for slot in &mut state.slots {
355            match slot.take() {
356                Some(Ok(response)) => out.push(response),
357                Some(Err(error)) => failure = failure.or(Some(error)),
358                None => {}
359            }
360        }
361        match failure {
362            Some(error) => Err(error),
363            None => Ok(out),
364        }
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    use std::sync::Arc;
371    use std::sync::atomic::{AtomicUsize, Ordering};
372
373    use rudb_common::Error;
374
375    use super::{Completion, Request, Response};
376
377    fn response(index: usize, bytes: &[u8]) -> Response {
378        Response::new(index, index as u64, bytes.len(), bytes.to_vec())
379    }
380
381    #[test]
382    fn a_request_states_its_length_through_its_buffer() {
383        let request = Request::new(64, 8);
384        assert_eq!(request.offset(), 64);
385        assert_eq!(request.len(), 8);
386        assert_eq!(request.end(), 72);
387        assert!(!request.is_empty());
388        assert_eq!(Request::reusing(0, vec![1, 2, 3]).len(), 3);
389    }
390
391    #[test]
392    fn wait_returns_responses_in_submission_order_however_they_finished() {
393        let (completion, filler) = Completion::pending(3);
394        filler.finish(2, Ok(response(2, b"cc")));
395        filler.finish(0, Ok(response(0, b"a")));
396        filler.finish(1, Ok(response(1, b"bbb")));
397        let responses = completion.wait().unwrap();
398        assert_eq!(responses.iter().map(Response::index).collect::<Vec<_>>(), [0, 1, 2]);
399        assert_eq!(responses[1].bytes(), b"bbb");
400    }
401
402    #[test]
403    fn take_returns_responses_in_completion_order_and_then_stops() {
404        let (mut completion, filler) = Completion::pending(2);
405        filler.finish(1, Ok(response(1, b"second")));
406        assert_eq!(completion.ready_count(), 1);
407        assert!(!completion.is_done());
408        assert_eq!(completion.take().unwrap().unwrap().index(), 1);
409        filler.finish(0, Ok(response(0, b"first")));
410        assert!(completion.is_done());
411        assert_eq!(completion.take().unwrap().unwrap().index(), 0);
412        assert!(completion.take().is_none());
413    }
414
415    #[test]
416    fn wait_reports_the_first_failure_by_position_not_by_arrival() {
417        let (completion, filler) = Completion::pending(3);
418        filler.finish(2, Err(Error::io("late")));
419        filler.finish(1, Err(Error::io("early")));
420        filler.finish(0, Ok(response(0, b"fine")));
421        let error = completion.wait().unwrap_err();
422        assert!(error.to_string().contains("early"), "{error}");
423    }
424
425    #[test]
426    fn a_short_read_is_a_response_and_not_an_error() {
427        let completion = Completion::ready(vec![Ok(Response::new(0, 0, 2, vec![7, 7, 0, 0]))]);
428        let responses = completion.wait().unwrap();
429        assert!(responses[0].is_short());
430        assert_eq!(responses[0].bytes(), &[7, 7]);
431        assert_eq!(responses[0].read(), 2);
432    }
433
434    #[test]
435    fn a_buffer_comes_back_out_of_the_response_to_be_used_again() {
436        let request = Request::reusing(0, vec![0; 4]);
437        let buf = request.into_buffer();
438        let response = Response::new(0, 0, 4, buf);
439        assert_eq!(response.into_buffer().len(), 4);
440    }
441
442    #[test]
443    fn a_backend_that_goes_away_wakes_the_waiter_instead_of_hanging_it() {
444        // The test gate for the scan asks for no wrong answers and no hangs. This is the hang.
445        let (completion, filler) = Completion::pending(2);
446        filler.finish(0, Ok(response(0, b"one")));
447        drop(filler);
448        let error = completion.wait().unwrap_err();
449        assert!(error.to_string().contains("stopped before"), "{error}");
450    }
451
452    #[test]
453    fn the_last_filler_out_is_the_one_that_wakes_the_waiter() {
454        let (mut completion, filler) = Completion::pending(2);
455        let second = filler.clone();
456        drop(filler);
457        assert!(!completion.is_done());
458        second.finish(0, Ok(response(0, b"one")));
459        drop(second);
460        assert_eq!(completion.take().unwrap().unwrap().index(), 0);
461        assert!(completion.take().unwrap().is_err());
462        assert!(completion.take().is_none());
463    }
464
465    #[test]
466    fn filling_a_slot_twice_leaves_the_first_answer_in_place() {
467        let (mut completion, filler) = Completion::pending(1);
468        filler.finish(0, Ok(response(0, b"kept")));
469        filler.finish(0, Err(Error::io("ignored")));
470        assert_eq!(completion.take().unwrap().unwrap().bytes(), b"kept");
471    }
472
473    #[test]
474    fn a_waiter_blocks_until_another_thread_fills_the_last_slot() {
475        let (completion, filler) = Completion::pending(2);
476        let filled = Arc::new(AtomicUsize::new(0));
477        let counter = Arc::clone(&filled);
478        let worker = std::thread::spawn(move || {
479            for index in 0..2 {
480                std::thread::sleep(std::time::Duration::from_millis(5));
481                counter.fetch_add(1, Ordering::SeqCst);
482                filler.finish(index, Ok(response(index, b"x")));
483            }
484        });
485        assert_eq!(completion.wait().unwrap().len(), 2);
486        assert_eq!(filled.load(Ordering::SeqCst), 2);
487        worker.join().unwrap();
488    }
489}