Skip to main content

nuclei/fs/file/
many.rs

1//! Many positional reads in one call: [`Handle::read_many_at`]. The reads
2//! wait once for the cursor writes accepted before the call (as each
3//! positional read does), then go out together: on io_uring every read the
4//! window holds is an entry of the shard's ring, handed to the kernel in the
5//! shard's one submission; elsewhere each is a job of the blocking pool. At
6//! most [`MANY_AT_ONCE`] are out at a time, the next going out as one ends,
7//! so a call of any length holds a bounded share of the ring and the pool.
8//! The reads read into the caller's own buffers, which the runtime's buffer
9//! cap does not count. `proofs/tla/many/ReadMany.tla` checks the window.
10
11use core::future::poll_fn;
12use core::task::Poll;
13use std::fs::File as StdFile;
14use std::io;
15use std::sync::Arc;
16
17use super::FileIo;
18use super::work::{Work, tally_read};
19use crate::buf::owned::{IoBufMut, OwnedBuf};
20use crate::io::Handle;
21use crate::io::source::Failure;
22
23/// The most reads of one call out at once.
24pub(crate) const MANY_AT_ONCE: usize = 64;
25
26/// A read's end: its result, and the caller's buffer with what it read.
27type End = (io::Result<usize>, OwnedBuf);
28
29impl FileIo {
30    pub(crate) async fn read_many_at<B>(
31        self: &Arc<Self>,
32        reads: Vec<(B, u64)>,
33    ) -> Vec<(io::Result<usize>, B)>
34    where
35        B: IoBufMut,
36    {
37        poll_fn(|cx| self.poll_ordered(cx)).await;
38        let mut ends: Vec<Option<End>> = (0..reads.len()).map(|_| None).collect();
39        let mut waiting = reads.into_iter().enumerate();
40        let mut out: Vec<(usize, Work<End>)> = Vec::with_capacity(MANY_AT_ONCE.min(ends.len()));
41        poll_fn(|cx| {
42            loop {
43                while out.len() < MANY_AT_ONCE {
44                    let Some((i, (buf, at))) = waiting.next() else {
45                        break;
46                    };
47                    let buf = OwnedBuf::filled(buf);
48                    if buf.len() == buf.capacity() {
49                        ends[i] = Some((Ok(0), buf));
50                        continue;
51                    }
52                    out.push((i, self.submit_read_owned(&mut Some(buf), at, cx.waker())));
53                }
54                let before = out.len();
55                out.retain_mut(|(i, work)| match (work.pooled(), work.poll(cx.waker())) {
56                    (pooled, Poll::Ready(end)) => {
57                        tally_read(pooled, &end.0, |n| *n);
58                        ends[*i] = Some(end);
59                        false
60                    }
61                    (_, Poll::Pending) => true,
62                });
63                if out.is_empty() && waiting.len() == 0 {
64                    return Poll::Ready(());
65                }
66                if out.len() == before {
67                    return Poll::Pending;
68                }
69            }
70        })
71        .await;
72        ends.into_iter()
73            .map(|end| {
74                let (result, buf) = end.unwrap_or_else(|| {
75                    (
76                        Err(io::Error::other("the read lost its buffer")),
77                        OwnedBuf::default(),
78                    )
79                });
80                (result, buf.into_filled())
81            })
82            .collect()
83    }
84}
85
86impl Handle<StdFile> {
87    /// Reads at each offset of `reads` into the spare capacity of its
88    /// buffer, after the bytes it holds, and gives every buffer back in the
89    /// order of `reads` with its read's result, as
90    /// [`read_at_owned`](Self::read_at_owned) answers one: the bytes that
91    /// came, zero at the end of the file or for a buffer with no spare
92    /// capacity. The reads are made at once, in any order: on io_uring
93    /// together, in the shard ring's one submission, at most 64 out at a
94    /// time; elsewhere as jobs of the blocking pool, as many at a time.
95    ///
96    /// The reads see every write accepted at the cursor before the call, and
97    /// every positional write that returned before it. Each read is one call
98    /// of the system's positional read into its buffer itself, never copied.
99    /// If the future is dropped, the reads still out are left to end by
100    /// themselves, and their buffers go with them.
101    ///
102    /// # Errors
103    ///
104    /// Each read's own, as `read_at_owned`'s. On a stream (a pipe, a
105    /// terminal) the file has no offset to read at, and every read answers
106    /// the system's error for that.
107    pub async fn read_many_at<B>(&self, reads: Vec<(B, u64)>) -> Vec<(io::Result<usize>, B)>
108    where
109        B: IoBufMut,
110    {
111        match self.file() {
112            Ok(file) => file.read_many_at(reads).await,
113            Err(err) => {
114                let failure = Failure::of(&err);
115                reads
116                    .into_iter()
117                    .map(|(buf, _)| (Err(failure.error()), buf))
118                    .collect()
119            }
120        }
121    }
122}