Skip to main content

nuclei/rt/runtime/
io_knobs.rs

1//! The builder's knobs of a runtime's IO handles: how long a dropped handle's bytes written
2//! behind may take to reach the kernel, the least a zero-copy write must have left for a send
3//! of it to go zero-copy, and the most one call of a `send_file` sends. Each is read once, as
4//! the runtime is built: the handles read the runtime's copy where their work runs, and a
5//! worker's ring keeps its own copy of the threshold.
6
7use std::io;
8use std::time::Duration;
9
10use super::Builder;
11
12/// The most one call that sends a file may be asked to send: what Linux's `sendfile(2)` sends in
13/// one call (`0x7ffff000` bytes), which is less than Windows' `TransmitFile` takes.
14const SEND_FILE_CHUNK_MAX: usize = 0x7fff_f000;
15
16/// What a runtime's handles read of its builder.
17#[derive(Clone, Copy, Debug)]
18pub(crate) struct IoKnobs {
19    /// How long a dropped handle's bytes written behind may take to reach the kernel.
20    pub(crate) linger: Duration,
21    /// The most bytes one call of a `send_file` sends.
22    pub(crate) send_file_chunk: u64,
23}
24
25impl Default for IoKnobs {
26    fn default() -> Self {
27        Self {
28            linger: crate::io::handle::LINGER,
29            send_file_chunk: crate::net::send_file::SEND_FILE_CHUNK,
30        }
31    }
32}
33
34/// The builder's own settings of [`IoKnobs`]; an unset one takes its default.
35#[derive(Clone, Copy, Debug, Default)]
36pub(super) struct IoSettings {
37    linger: Option<Duration>,
38    send_file_chunk: Option<usize>,
39}
40
41impl IoSettings {
42    /// Refuses a setting no handle can work with, as the configuration's mistake.
43    pub(super) fn validate(&self) -> io::Result<()> {
44        use crate::config::refuse;
45        if let Some(linger) = self.linger.filter(|l| l.is_zero()) {
46            return Err(refuse(
47                "write_behind_linger",
48                format_args!("{linger:?}"),
49                "more than zero",
50            ));
51        }
52        if let Some(bytes) = self
53            .send_file_chunk
54            .filter(|&b| !(1..=SEND_FILE_CHUNK_MAX).contains(&b))
55        {
56            return Err(refuse(
57                "send_file_chunk",
58                bytes,
59                format_args!("from 1 to {SEND_FILE_CHUNK_MAX} bytes"),
60            ));
61        }
62        Ok(())
63    }
64
65    /// The knobs, each set one as set and the rest at their defaults.
66    pub(super) fn resolve(self) -> IoKnobs {
67        let default = IoKnobs::default();
68        IoKnobs {
69            linger: self.linger.unwrap_or(default.linger),
70            send_file_chunk: self
71                .send_file_chunk
72                .map_or(default.send_file_chunk, |b| b as u64),
73        }
74    }
75}
76
77impl Builder {
78    /// How long the bytes a dropped handle wrote behind may take to reach the kernel (default
79    /// one minute): a task delivers them after the drop, as the kernel delivers what a closed
80    /// socket holds. Past it the rest is given up, and a TCP stream's connection is reset, so its
81    /// peer reads an error rather than the end of a stream cut short. [`get_mut`] and
82    /// [`into_inner`] wait for the bytes as long at most, and the runtime's drop waits for the
83    /// tasks. More than zero; [`build`](Self::build) fails with `InvalidInput` at zero.
84    ///
85    /// [`get_mut`]: crate::io::Handle::get_mut
86    /// [`into_inner`]: crate::io::Handle::into_inner
87    pub fn write_behind_linger(mut self, linger: Duration) -> Self {
88        self.io.linger = Some(linger);
89        self
90    }
91
92    /// The least a [`write_zero_copy`](crate::net::TcpStream::write_zero_copy) must have left
93    /// to send for its next send to go zero-copy, in bytes (default 1 MiB): below it, the kernel
94    /// copies the bytes, as it does for every write. Copying costs less than pinning the pages
95    /// for small writes; where the threshold pays depends on the machine and its network. Only
96    /// io_uring sends zero-copy, on the runtime's workers: every value is accepted, and `0`
97    /// sends every write zero-copy, `usize::MAX` none.
98    #[cfg_attr(not(ring), expect(unused_mut, reason = "only a ring sends zero-copy"))]
99    pub fn zero_copy_threshold(mut self, bytes: usize) -> Self {
100        #[cfg(ring)]
101        {
102            self.tuning.zero_copy_min = bytes;
103        }
104        #[cfg(not(ring))]
105        let _ = bytes;
106        self
107    }
108
109    /// The most bytes one call of a [`send_file`](crate::net::TcpStream::send_file) sends
110    /// (default 1 MiB). Each call runs on the blocking pool and may stall on a cold page there,
111    /// so this bounds how long a pool thread is held, and how much a cancelled `send_file` still
112    /// sends after its future went; where the system has no call for it, each chunk is read
113    /// into a buffer of this size. From 1 byte to `0x7ffff000` (what one `sendfile(2)` sends);
114    /// [`build`](Self::build) fails with `InvalidInput` outside.
115    pub fn send_file_chunk(mut self, bytes: usize) -> Self {
116        self.io.send_file_chunk = Some(bytes);
117        self
118    }
119}
120
121/// The knobs of the runtime this thread is inside, or of the default runtime if it runs; their
122/// defaults where neither does.
123pub(crate) fn current() -> IoKnobs {
124    super::running_inner().map_or_else(IoKnobs::default, |inner| inner.io)
125}
126
127#[cfg(test)]
128mod tests;