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;