Skip to main content

net_io/
limits.rs

1//! Every bound net-io enforces, in one value.
2
3use std::time::Duration;
4
5use crate::addr::PrefixPolicy;
6use crate::driver::DriverKind;
7use crate::error::{ConfigReason, NetError};
8use crate::info::Lane;
9
10/// Lanes that can hold a connection reservation (0 to 7).
11const RESERVED_LANES: usize = 8;
12
13/// A minimum transfer rate, checked per direction while that direction is active.
14///
15/// # Examples
16///
17/// ```
18/// use std::time::Duration;
19/// let rate = net_io::MinRate::new(512, Duration::from_secs(10));
20/// assert_eq!(rate.grace, Duration::from_secs(5));
21/// assert!(net_io::MinRate::off().is_off());
22/// ```
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct MinRate {
25    /// Bytes per second the peer must reach over the window.
26    pub bytes_per_sec: u32,
27    /// The sliding window.
28    pub window: Duration,
29    /// How long after a direction becomes active the check starts.
30    pub grace: Duration,
31    /// How often the rate is checked.
32    pub check: Duration,
33}
34
35impl MinRate {
36    /// No minimum rate.
37    #[must_use]
38    pub const fn off() -> MinRate {
39        MinRate {
40            bytes_per_sec: 0,
41            window: Duration::from_secs(10),
42            grace: Duration::from_secs(5),
43            check: Duration::from_secs(1),
44        }
45    }
46
47    /// `bytes_per_sec` over `window`, after a 5 s grace, checked every second.
48    #[must_use]
49    pub const fn new(bytes_per_sec: u32, window: Duration) -> MinRate {
50        MinRate {
51            bytes_per_sec,
52            window,
53            grace: Duration::from_secs(5),
54            check: Duration::from_secs(1),
55        }
56    }
57
58    /// Whether the rate check is off.
59    #[must_use]
60    pub const fn is_off(&self) -> bool {
61        self.bytes_per_sec == 0
62    }
63}
64
65/// Adaptive deadlines under pressure (applied from `0.6.0`).
66///
67/// ```
68/// let p = net_io::Pressure::default();
69/// assert!(p.high > p.low);
70/// ```
71#[derive(Clone, Copy, Debug, PartialEq)]
72pub struct Pressure {
73    /// Fraction of `max_connections` above which deadlines shrink.
74    pub high: f32,
75    /// Fraction below which they return to normal.
76    pub low: f32,
77    /// The multiplier applied to handshake and idle timeouts.
78    pub factor: f32,
79}
80
81impl Default for Pressure {
82    fn default() -> Pressure {
83        Pressure {
84            high: 0.8,
85            low: 0.6,
86            factor: 0.25,
87        }
88    }
89}
90
91/// A buffer size class: a power of two from 1 KiB to 1 MiB.
92///
93/// ```
94/// use net_io::SizeClass;
95/// assert_eq!(SizeClass::MEDIUM.bytes(), 16 * 1024);
96/// assert!(SizeClass::new(3000).is_err());
97/// assert_eq!(SizeClass::new(2048).unwrap().bytes(), 2048);
98/// ```
99#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
100pub struct SizeClass(u32);
101
102impl SizeClass {
103    /// 4 KiB.
104    pub const SMALL: SizeClass = SizeClass(4 * 1024);
105    /// 16 KiB.
106    pub const MEDIUM: SizeClass = SizeClass(16 * 1024);
107    /// 64 KiB.
108    pub const LARGE: SizeClass = SizeClass(64 * 1024);
109
110    /// A class of `bytes`.
111    ///
112    /// # Errors
113    ///
114    /// `InvalidConfig { field: "size_class" }` unless `bytes` is a power of two from 1 KiB to 1 MiB.
115    pub const fn new(bytes: u32) -> Result<SizeClass, NetError> {
116        if !bytes.is_power_of_two() {
117            return Err(NetError::InvalidConfig {
118                field: "size_class",
119                reason: ConfigReason::Inconsistent,
120            });
121        }
122        if bytes < 1024 {
123            return Err(NetError::InvalidConfig {
124                field: "size_class",
125                reason: ConfigReason::TooSmall { min: 1024 },
126            });
127        }
128        if bytes > 1024 * 1024 {
129            return Err(NetError::InvalidConfig {
130                field: "size_class",
131                reason: ConfigReason::TooLarge { max: 1024 * 1024 },
132            });
133        }
134        Ok(SizeClass(bytes))
135    }
136
137    /// The size in bytes.
138    #[must_use]
139    pub const fn bytes(self) -> u32 {
140        self.0
141    }
142}
143
144/// Every bound net-io enforces. Unset values take the defaults of the driver
145/// that runs the connection.
146///
147/// # Examples
148///
149/// ```
150/// use net_io::{DriverKind, Limits, MinRate};
151/// use std::time::Duration;
152///
153/// let limits = Limits::builder()
154///     .max_connections(20_000)
155///     .idle_timeout(Duration::from_secs(15))
156///     .min_rate(MinRate::new(512, Duration::from_secs(10)))
157///     .build()?;
158/// assert_eq!(limits.max_connections(), Some(20_000));
159/// assert_eq!(limits.read_timeout(), None);
160/// assert_eq!(limits.resolve(DriverKind::Blocking).read_timeout(), Some(Duration::from_secs(30)));
161/// # Ok::<(), net_io::NetError>(())
162/// ```
163#[non_exhaustive]
164#[derive(Clone, Debug, Default, PartialEq)]
165pub struct Limits {
166    max_connections: Option<u32>,
167    max_per_listener: Option<u32>,
168    max_per_source: Option<(u32, PrefixPolicy)>,
169    backlog: Option<u32>,
170    accept_batch: Option<u16>,
171    handshake_timeout: Option<Duration>,
172    idle_timeout: Option<Duration>,
173    read_timeout: Option<Duration>,
174    write_timeout: Option<Duration>,
175    park_timeout: Option<Duration>,
176    min_rate: Option<MinRate>,
177    proxy_header_max: Option<u16>,
178    read_class: Option<(SizeClass, SizeClass)>,
179    output_budget: Option<u32>,
180    completion_queue: Option<u32>,
181    buffer_budget: Option<u64>,
182    drain_timeout: Option<Duration>,
183    graceful_linger: Option<(Duration, u32)>,
184    refusal_bytes_max: Option<u16>,
185    tick: Option<Duration>,
186    pressure: Option<Pressure>,
187    lane_reserve: [Option<u32>; RESERVED_LANES],
188}
189
190/// The resolved values a running connection uses.
191#[derive(Clone, Copy, Debug, PartialEq)]
192pub(crate) struct Effective {
193    pub max_connections: u32,
194    pub max_per_listener: u32,
195    pub max_per_source: u32,
196    pub source_policy: PrefixPolicy,
197    pub backlog: u32,
198    pub accept_batch: u16,
199    pub handshake_timeout: Duration,
200    pub idle_timeout: Duration,
201    pub read_timeout: Duration,
202    pub write_timeout: Duration,
203    pub park_timeout: Duration,
204    pub min_rate: MinRate,
205    pub proxy_header_max: u16,
206    pub read_class: SizeClass,
207    pub read_class_max: SizeClass,
208    pub output_budget: u32,
209    pub completion_queue: u32,
210    pub buffer_budget: u64,
211    pub drain_timeout: Duration,
212    pub graceful_linger: Duration,
213    pub graceful_linger_bytes: u32,
214    pub refusal_bytes_max: u16,
215    pub tick: Duration,
216}
217
218impl Effective {
219    /// The defaults of `driver` (blocking, or the reactor).
220    pub(crate) fn defaults(driver: DriverKind) -> Effective {
221        let blocking = driver.resolve() == DriverKind::Blocking;
222        let max_connections = if blocking { 128 } else { 10_000 };
223        Effective {
224            max_connections,
225            max_per_listener: max_connections,
226            max_per_source: if blocking { 16 } else { 256 },
227            source_policy: PrefixPolicy::DEFAULT,
228            backlog: 1024,
229            accept_batch: if blocking { 16 } else { 64 },
230            handshake_timeout: Duration::from_secs(10),
231            idle_timeout: Duration::from_secs(if blocking { 5 } else { 60 }),
232            read_timeout: Duration::from_secs(30),
233            write_timeout: Duration::from_secs(30),
234            park_timeout: Duration::from_secs(30),
235            min_rate: MinRate::new(256, Duration::from_secs(10)),
236            proxy_header_max: 1024,
237            read_class: SizeClass::MEDIUM,
238            read_class_max: SizeClass::LARGE,
239            output_budget: 64 * 1024,
240            completion_queue: if blocking { 1 } else { 1024 },
241            buffer_budget: if blocking { 64 << 20 } else { 512 << 20 },
242            drain_timeout: Duration::from_secs(30),
243            graceful_linger: Duration::from_secs(2),
244            graceful_linger_bytes: 64 * 1024,
245            refusal_bytes_max: 512,
246            tick: Duration::from_millis(if blocking { 10 } else { 1 }),
247        }
248    }
249}
250
251impl Limits {
252    /// A builder with every value unset.
253    ///
254    /// ```
255    /// let limits = net_io::Limits::builder().max_connections(100).build()?;
256    /// assert_eq!(limits.max_per_source(), None);
257    /// # Ok::<(), net_io::NetError>(())
258    /// ```
259    pub fn builder() -> LimitsBuilder {
260        LimitsBuilder::default()
261    }
262
263    /// Every value set to the reactor's defaults.
264    ///
265    /// ```
266    /// assert_eq!(net_io::Limits::reactor_defaults().max_connections(), Some(10_000));
267    /// ```
268    #[must_use]
269    pub fn reactor_defaults() -> Limits {
270        Limits::from_effective(&Effective::defaults(DriverKind::Reactor))
271    }
272
273    /// Every value set to the blocking driver's defaults.
274    ///
275    /// ```
276    /// assert_eq!(net_io::Limits::blocking_defaults().idle_timeout(), Some(std::time::Duration::from_secs(5)));
277    /// ```
278    #[must_use]
279    pub fn blocking_defaults() -> Limits {
280        Limits::from_effective(&Effective::defaults(DriverKind::Blocking))
281    }
282
283    /// A builder starting from these values.
284    ///
285    /// Every value carried over counts as set, so lowering `max_connections`
286    /// below a carried-over `max_per_listener` fails validation. Start from
287    /// [`Limits::builder`] to let dependent values follow `max_connections`.
288    ///
289    /// ```
290    /// let limits = net_io::Limits::blocking_defaults().to_builder().max_connections(256).build()?;
291    /// assert_eq!(limits.max_connections(), Some(256));
292    /// # Ok::<(), net_io::NetError>(())
293    /// ```
294    pub fn to_builder(&self) -> LimitsBuilder {
295        LimitsBuilder {
296            limits: self.clone(),
297        }
298    }
299
300    /// These limits with unset values filled from `driver`'s defaults.
301    ///
302    /// ```
303    /// let limits = net_io::Limits::builder().max_connections(10).build()?;
304    /// let resolved = limits.resolve(net_io::DriverKind::Blocking);
305    /// assert_eq!(resolved.max_per_source().map(|(n, _)| n), Some(10));
306    /// # Ok::<(), net_io::NetError>(())
307    /// ```
308    #[must_use]
309    pub fn resolve(&self, driver: DriverKind) -> Limits {
310        let mut out = Limits::from_effective(&self.effective(driver));
311        out.pressure = self.pressure;
312        out.lane_reserve = self.lane_reserve;
313        out
314    }
315
316    /// Checks the resolved values against each other, the worker count, and the
317    /// process's descriptor limit.
318    ///
319    /// # Errors
320    ///
321    /// `InvalidConfig { field, reason }` for the first rule broken.
322    ///
323    /// ```
324    /// let limits = net_io::Limits::builder().max_connections(1_000_000).build()?;
325    /// assert!(limits.validate(4, Some(1024)).is_err());
326    /// # Ok::<(), net_io::NetError>(())
327    /// ```
328    pub fn validate(&self, workers: u16, descriptor_limit: Option<u64>) -> Result<(), NetError> {
329        // Rules that need only the values themselves.
330        LimitsBuilder {
331            limits: self.clone(),
332        }
333        .check()?;
334        let e = self.effective(DriverKind::Blocking);
335        let need_budget = u64::from(workers)
336            .saturating_mul(u64::from(e.read_class_max.bytes()))
337            .saturating_mul(2);
338        if e.buffer_budget < need_budget {
339            return Err(invalid(
340                "buffer_budget",
341                ConfigReason::BudgetBelowWorkers { need: need_budget },
342            ));
343        }
344        if let Some(have) = descriptor_limit {
345            let need = descriptors_needed(e.max_connections, workers);
346            if need > have {
347                return Err(invalid(
348                    "max_connections",
349                    ConfigReason::DescriptorLimit { have, need },
350                ));
351            }
352        }
353        Ok(())
354    }
355
356    /// The resolved values for `driver`.
357    pub(crate) fn effective(&self, driver: DriverKind) -> Effective {
358        let d = Effective::defaults(driver);
359        let max_connections = self.max_connections.unwrap_or(d.max_connections);
360        let (max_per_source, source_policy) = self
361            .max_per_source
362            .unwrap_or((d.max_per_source.min(max_connections), d.source_policy));
363        let (read_class, read_class_max) =
364            self.read_class.unwrap_or((d.read_class, d.read_class_max));
365        let (graceful_linger, graceful_linger_bytes) = self
366            .graceful_linger
367            .unwrap_or((d.graceful_linger, d.graceful_linger_bytes));
368        Effective {
369            max_connections,
370            max_per_listener: self.max_per_listener.unwrap_or(max_connections),
371            max_per_source,
372            source_policy,
373            backlog: self.backlog.unwrap_or(d.backlog),
374            accept_batch: self.accept_batch.unwrap_or(d.accept_batch),
375            handshake_timeout: self.handshake_timeout.unwrap_or(d.handshake_timeout),
376            idle_timeout: self.idle_timeout.unwrap_or(d.idle_timeout),
377            read_timeout: self.read_timeout.unwrap_or(d.read_timeout),
378            write_timeout: self.write_timeout.unwrap_or(d.write_timeout),
379            park_timeout: self.park_timeout.unwrap_or(d.park_timeout),
380            min_rate: self.min_rate.unwrap_or(d.min_rate),
381            proxy_header_max: self.proxy_header_max.unwrap_or(d.proxy_header_max),
382            read_class,
383            read_class_max,
384            output_budget: self.output_budget.unwrap_or(d.output_budget),
385            completion_queue: self.completion_queue.unwrap_or(d.completion_queue),
386            buffer_budget: self.buffer_budget.unwrap_or(d.buffer_budget),
387            drain_timeout: self.drain_timeout.unwrap_or(d.drain_timeout),
388            graceful_linger,
389            graceful_linger_bytes,
390            refusal_bytes_max: self.refusal_bytes_max.unwrap_or(d.refusal_bytes_max),
391            tick: self.tick.unwrap_or(d.tick),
392        }
393    }
394
395    /// Values from `other` where it sets them, otherwise from `self` (listener overrides).
396    pub(crate) fn overlay(&self, other: &Limits) -> Limits {
397        macro_rules! pick {
398            ($($f:ident),*) => { Limits { $($f: other.$f.or(self.$f),)* lane_reserve: {
399                let mut r = self.lane_reserve;
400                for (i, v) in other.lane_reserve.iter().enumerate() { if v.is_some() { r[i] = *v; } }
401                r
402            } } };
403        }
404        pick!(
405            max_connections,
406            max_per_listener,
407            max_per_source,
408            backlog,
409            accept_batch,
410            handshake_timeout,
411            idle_timeout,
412            read_timeout,
413            write_timeout,
414            park_timeout,
415            min_rate,
416            proxy_header_max,
417            read_class,
418            output_budget,
419            completion_queue,
420            buffer_budget,
421            drain_timeout,
422            graceful_linger,
423            refusal_bytes_max,
424            tick,
425            pressure
426        )
427    }
428
429    fn from_effective(e: &Effective) -> Limits {
430        Limits {
431            max_connections: Some(e.max_connections),
432            max_per_listener: Some(e.max_per_listener),
433            max_per_source: Some((e.max_per_source, e.source_policy)),
434            backlog: Some(e.backlog),
435            accept_batch: Some(e.accept_batch),
436            handshake_timeout: Some(e.handshake_timeout),
437            idle_timeout: Some(e.idle_timeout),
438            read_timeout: Some(e.read_timeout),
439            write_timeout: Some(e.write_timeout),
440            park_timeout: Some(e.park_timeout),
441            min_rate: Some(e.min_rate),
442            proxy_header_max: Some(e.proxy_header_max),
443            read_class: Some((e.read_class, e.read_class_max)),
444            output_budget: Some(e.output_budget),
445            completion_queue: Some(e.completion_queue),
446            buffer_budget: Some(e.buffer_budget),
447            drain_timeout: Some(e.drain_timeout),
448            graceful_linger: Some((e.graceful_linger, e.graceful_linger_bytes)),
449            refusal_bytes_max: Some(e.refusal_bytes_max),
450            tick: Some(e.tick),
451            pressure: None,
452            lane_reserve: [None; RESERVED_LANES],
453        }
454    }
455
456    /// The global connection cap.
457    #[must_use]
458    pub fn max_connections(&self) -> Option<u32> {
459        self.max_connections
460    }
461    /// The per-listener connection cap.
462    #[must_use]
463    pub fn max_per_listener(&self) -> Option<u32> {
464        self.max_per_listener
465    }
466    /// The per-source concurrency cap and how sources are grouped.
467    #[must_use]
468    pub fn max_per_source(&self) -> Option<(u32, PrefixPolicy)> {
469        self.max_per_source
470    }
471    /// The listen backlog.
472    #[must_use]
473    pub fn backlog(&self) -> Option<u32> {
474        self.backlog
475    }
476    /// Connections accepted per wake-up.
477    #[must_use]
478    pub fn accept_batch(&self) -> Option<u16> {
479        self.accept_batch
480    }
481    /// Time for the PROXY header and TLS handshake together.
482    #[must_use]
483    pub fn handshake_timeout(&self) -> Option<Duration> {
484        self.handshake_timeout
485    }
486    /// Time to wait for the next exchange.
487    #[must_use]
488    pub fn idle_timeout(&self) -> Option<Duration> {
489        self.idle_timeout
490    }
491    /// Time to wait for bytes during an exchange.
492    #[must_use]
493    pub fn read_timeout(&self) -> Option<Duration> {
494        self.read_timeout
495    }
496    /// Time to wait for the peer to take bytes.
497    #[must_use]
498    pub fn write_timeout(&self) -> Option<Duration> {
499        self.write_timeout
500    }
501    /// Time a parked connection may wait for its completion.
502    #[must_use]
503    pub fn park_timeout(&self) -> Option<Duration> {
504        self.park_timeout
505    }
506    /// The minimum transfer rate.
507    #[must_use]
508    pub fn min_rate(&self) -> Option<MinRate> {
509        self.min_rate
510    }
511    /// The PROXY v2 address-and-TLV cap.
512    #[must_use]
513    pub fn proxy_header_max(&self) -> Option<u16> {
514        self.proxy_header_max
515    }
516    /// The initial and maximum read buffer class.
517    #[must_use]
518    pub fn read_class(&self) -> Option<(SizeClass, SizeClass)> {
519        self.read_class
520    }
521    /// Pool memory a connection's output may hold.
522    #[must_use]
523    pub fn output_budget(&self) -> Option<u32> {
524        self.output_budget
525    }
526    /// A worker's completion queue length.
527    #[must_use]
528    pub fn completion_queue(&self) -> Option<u32> {
529        self.completion_queue
530    }
531    /// Total bytes of buffers all workers may allocate.
532    #[must_use]
533    pub fn buffer_budget(&self) -> Option<u64> {
534        self.buffer_budget
535    }
536    /// The drain deadline.
537    #[must_use]
538    pub fn drain_timeout(&self) -> Option<Duration> {
539        self.drain_timeout
540    }
541    /// The graceful-close linger: time and bytes read and discarded.
542    #[must_use]
543    pub fn graceful_linger(&self) -> Option<(Duration, u32)> {
544        self.graceful_linger
545    }
546    /// The largest refusal reply.
547    #[must_use]
548    pub fn refusal_bytes_max(&self) -> Option<u16> {
549        self.refusal_bytes_max
550    }
551    /// The timer resolution.
552    #[must_use]
553    pub fn tick(&self) -> Option<Duration> {
554        self.tick
555    }
556    /// Adaptive deadlines under pressure.
557    #[must_use]
558    pub fn pressure(&self) -> Option<Pressure> {
559        self.pressure
560    }
561    /// The connection slots reserved for `lane` (lanes 0 to 7).
562    #[must_use]
563    pub fn lane_reserve(&self, lane: Lane) -> Option<u32> {
564        self.lane_reserve
565            .get(usize::from(lane.0))
566            .copied()
567            .flatten()
568    }
569}
570
571/// Descriptors a configuration needs: connections, two wake descriptors per
572/// worker, and a fixed reserve for listeners and the spare descriptor.
573pub(crate) fn descriptors_needed(max_connections: u32, workers: u16) -> u64 {
574    u64::from(max_connections) + 2 * u64::from(workers) + 32
575}
576
577fn invalid(field: &'static str, reason: ConfigReason) -> NetError {
578    NetError::InvalidConfig { field, reason }
579}
580
581/// Builds [`Limits`]; every setter is optional.
582///
583/// ```
584/// use net_io::{ConfigReason, Limits, NetError};
585/// let err = Limits::builder().max_connections(100).max_per_listener(200).build().unwrap_err();
586/// assert!(matches!(err, NetError::InvalidConfig { field: "max_per_listener", reason: ConfigReason::AboveGlobal }));
587/// assert!(Limits::builder().max_connections(100).build().is_ok());
588/// ```
589#[must_use]
590#[derive(Clone, Debug, Default)]
591pub struct LimitsBuilder {
592    limits: Limits,
593}
594
595macro_rules! setter {
596    ($(#[$m:meta])* $name:ident, $t:ty) => {
597        $(#[$m])*
598        pub fn $name(mut self, value: $t) -> Self {
599            self.limits.$name = Some(value);
600            self
601        }
602    };
603}
604
605impl LimitsBuilder {
606    setter!(/// The global connection cap.
607        max_connections, u32);
608    setter!(/// The per-listener connection cap.
609        max_per_listener, u32);
610    setter!(/// The listen backlog.
611        backlog, u32);
612    setter!(/// Connections accepted per wake-up.
613        accept_batch, u16);
614    setter!(/// Time for the PROXY header and TLS handshake together.
615        handshake_timeout, Duration);
616    setter!(/// Time to wait for the next exchange.
617        idle_timeout, Duration);
618    setter!(/// Time to wait for bytes during an exchange.
619        read_timeout, Duration);
620    setter!(/// Time to wait for the peer to take bytes.
621        write_timeout, Duration);
622    setter!(/// Time a parked connection may wait for its completion.
623        park_timeout, Duration);
624    setter!(/// The minimum transfer rate ([`MinRate::off`] to disable).
625        min_rate, MinRate);
626    setter!(/// Pool memory a connection's output may hold.
627        output_budget, u32);
628    setter!(/// Total bytes of buffers all workers may allocate.
629        buffer_budget, u64);
630    setter!(/// The drain deadline.
631        drain_timeout, Duration);
632    setter!(/// The largest refusal reply (at most 512).
633        refusal_bytes_max, u16);
634    setter!(/// The timer resolution (1 ms to 100 ms).
635        tick, Duration);
636    setter!(/// Adaptive deadlines under pressure (applied from `0.6.0`).
637        pressure, Pressure);
638
639    /// The per-source concurrency cap and how sources are grouped.
640    pub fn max_per_source(mut self, n: u32, policy: PrefixPolicy) -> Self {
641        self.limits.max_per_source = Some((n, policy));
642        self
643    }
644
645    /// The PROXY v2 address-and-TLV cap (up to 65,519).
646    pub fn proxy_header_max(mut self, v2_body_bytes: u16) -> Self {
647        self.limits.proxy_header_max = Some(v2_body_bytes);
648        self
649    }
650
651    /// The initial read buffer class and the largest a session may grow it to.
652    pub fn read_class(mut self, initial: SizeClass, max: SizeClass) -> Self {
653        self.limits.read_class = Some((initial, max));
654        self
655    }
656
657    /// A worker's completion queue length, rounded up to a power of two.
658    pub fn completion_queue(mut self, n: u32) -> Self {
659        self.limits.completion_queue = Some(n);
660        self
661    }
662
663    /// The graceful-close linger: time and bytes read and discarded.
664    pub fn graceful_linger(mut self, time: Duration, bytes: u32) -> Self {
665        self.limits.graceful_linger = Some((time, bytes));
666        self
667    }
668
669    /// Connection slots reserved for `lane` (lanes 0 to 7; applied from `0.6.0`).
670    pub fn lane_reserve(mut self, lane: Lane, slots: u32) -> Self {
671        match self.limits.lane_reserve.get_mut(usize::from(lane.0)) {
672            Some(slot) => *slot = Some(slots),
673            // Rejected by `build` (field "lane_reserve").
674            None => self.limits.lane_reserve[RESERVED_LANES - 1] = Some(u32::MAX),
675        }
676        self
677    }
678
679    /// Checks the values that were set and returns the limits.
680    ///
681    /// # Errors
682    ///
683    /// `InvalidConfig { field, reason }` for the first rule broken, in the order
684    /// documented in `docs/API.md`.
685    pub fn build(self) -> Result<Limits, NetError> {
686        self.check()?;
687        let mut limits = self.limits;
688        if let Some(n) = limits.completion_queue {
689            limits.completion_queue = Some(n.next_power_of_two());
690        }
691        Ok(limits)
692    }
693
694    fn check(&self) -> Result<(), NetError> {
695        let l = &self.limits;
696        // Zero where zero is meaningless.
697        let zeros: [(&'static str, bool); 6] = [
698            ("max_connections", l.max_connections == Some(0)),
699            ("backlog", l.backlog == Some(0)),
700            ("accept_batch", l.accept_batch == Some(0)),
701            ("max_per_source", matches!(l.max_per_source, Some((0, _)))),
702            ("completion_queue", l.completion_queue == Some(0)),
703            ("output_budget", l.output_budget == Some(0)),
704        ];
705        for (field, zero) in zeros {
706            if zero {
707                return Err(invalid(field, ConfigReason::Zero));
708            }
709        }
710        if let Some(max) = l.max_connections {
711            if l.max_per_listener.is_some_and(|n| n > max) {
712                return Err(invalid("max_per_listener", ConfigReason::AboveGlobal));
713            }
714            if l.max_per_source.is_some_and(|(n, _)| n > max) {
715                return Err(invalid("max_per_source", ConfigReason::AboveGlobal));
716            }
717        }
718        if let Some(tick) = l.tick {
719            if tick < Duration::from_millis(1) {
720                return Err(invalid("tick", ConfigReason::TooSmall { min: 1 }));
721            }
722            if tick > Duration::from_millis(100) {
723                return Err(invalid("tick", ConfigReason::TooLarge { max: 100 }));
724            }
725        }
726        let tick = l.tick.unwrap_or(Duration::from_millis(1));
727        let timeouts: [(&'static str, Option<Duration>); 6] = [
728            ("handshake_timeout", l.handshake_timeout),
729            ("idle_timeout", l.idle_timeout),
730            ("read_timeout", l.read_timeout),
731            ("write_timeout", l.write_timeout),
732            ("park_timeout", l.park_timeout),
733            ("drain_timeout", l.drain_timeout),
734        ];
735        for (field, value) in timeouts {
736            if value.is_some_and(|d| d < tick) {
737                return Err(invalid(field, ConfigReason::BelowTick));
738            }
739        }
740        if let Some(rate) = l.min_rate {
741            if !rate.is_off() {
742                if rate.check.is_zero() || rate.grace.is_zero() {
743                    return Err(invalid("min_rate", ConfigReason::Zero));
744                }
745                if rate.window < rate.check {
746                    return Err(invalid("min_rate", ConfigReason::WindowBelowCheck));
747                }
748            }
749        }
750        if let Some((initial, max)) = l.read_class {
751            if max < initial {
752                return Err(invalid("read_class", ConfigReason::Inconsistent));
753            }
754        }
755        if l.refusal_bytes_max.is_some_and(|n| n > 512) {
756            return Err(invalid(
757                "refusal_bytes_max",
758                ConfigReason::TooLarge { max: 512 },
759            ));
760        }
761        if l.proxy_header_max.is_some_and(|n| n > 65_519) {
762            return Err(invalid(
763                "proxy_header_max",
764                ConfigReason::TooLarge { max: 65_519 },
765            ));
766        }
767        if l.completion_queue.is_some_and(|n| n > 1 << 20) {
768            return Err(invalid(
769                "completion_queue",
770                ConfigReason::TooLarge { max: 1 << 20 },
771            ));
772        }
773        if let Some(p) = l.pressure {
774            let factor_ok = p.factor > 0.0 && p.factor <= 1.0;
775            let ordered = p.high.partial_cmp(&p.low) == Some(std::cmp::Ordering::Greater);
776            if !ordered
777                || !factor_ok
778                || !(0.0..=1.0).contains(&p.high)
779                || !(0.0..=1.0).contains(&p.low)
780            {
781                return Err(invalid("pressure", ConfigReason::Inconsistent));
782            }
783        }
784        if l.lane_reserve[RESERVED_LANES - 1] == Some(u32::MAX) {
785            return Err(invalid("lane_reserve", ConfigReason::TooLarge { max: 7 }));
786        }
787        let reserved: u64 = l.lane_reserve.iter().flatten().map(|n| u64::from(*n)).sum();
788        if let Some(max) = l.max_connections {
789            if reserved > u64::from(max) {
790                return Err(invalid("lane_reserve", ConfigReason::AboveGlobal));
791            }
792        }
793        Ok(())
794    }
795}
796
797#[cfg(test)]
798mod tests {
799    use super::*;
800
801    #[test]
802    fn test_unset_values_take_driver_defaults() {
803        let l = Limits::builder().build().unwrap();
804        assert_eq!(
805            l.effective(DriverKind::Blocking).idle_timeout,
806            Duration::from_secs(5)
807        );
808        assert_eq!(
809            l.effective(DriverKind::Reactor).idle_timeout,
810            Duration::from_secs(60)
811        );
812    }
813
814    #[test]
815    fn test_dependent_defaults_follow_max_connections() {
816        let l = Limits::builder().max_connections(10).build().unwrap();
817        let e = l.effective(DriverKind::Reactor);
818        assert_eq!(e.max_per_listener, 10);
819        assert_eq!(e.max_per_source, 10);
820    }
821
822    #[test]
823    fn test_zero_values_rejected() {
824        for b in [
825            Limits::builder().max_connections(0),
826            Limits::builder().backlog(0),
827            Limits::builder().accept_batch(0),
828            Limits::builder().max_per_source(0, PrefixPolicy::DEFAULT),
829            Limits::builder().completion_queue(0),
830            Limits::builder().output_budget(0),
831        ] {
832            assert!(matches!(
833                b.build(),
834                Err(NetError::InvalidConfig {
835                    reason: ConfigReason::Zero,
836                    ..
837                })
838            ));
839        }
840    }
841
842    #[test]
843    fn test_timeout_below_tick_rejected() {
844        let err = Limits::builder()
845            .tick(Duration::from_millis(100))
846            .read_timeout(Duration::from_millis(50))
847            .build()
848            .unwrap_err();
849        assert!(matches!(
850            err,
851            NetError::InvalidConfig {
852                field: "read_timeout",
853                reason: ConfigReason::BelowTick
854            }
855        ));
856    }
857
858    #[test]
859    fn test_min_rate_window_below_check_rejected() {
860        let rate = MinRate {
861            bytes_per_sec: 10,
862            window: Duration::from_millis(500),
863            grace: Duration::from_secs(1),
864            check: Duration::from_secs(1),
865        };
866        assert!(matches!(
867            Limits::builder().min_rate(rate).build(),
868            Err(NetError::InvalidConfig {
869                reason: ConfigReason::WindowBelowCheck,
870                ..
871            })
872        ));
873        assert!(Limits::builder().min_rate(MinRate::off()).build().is_ok());
874    }
875
876    #[test]
877    fn test_bounds_too_large() {
878        assert!(Limits::builder().refusal_bytes_max(513).build().is_err());
879        assert!(Limits::builder().refusal_bytes_max(512).build().is_ok());
880        assert!(Limits::builder().proxy_header_max(65_520).build().is_err());
881        assert!(
882            Limits::builder()
883                .completion_queue((1 << 20) + 1)
884                .build()
885                .is_err()
886        );
887    }
888
889    #[test]
890    fn test_completion_queue_rounded_to_power_of_two() {
891        let l = Limits::builder().completion_queue(1000).build().unwrap();
892        assert_eq!(l.completion_queue(), Some(1024));
893    }
894
895    #[test]
896    fn test_pressure_and_lane_reserve_validation() {
897        let bad = Pressure {
898            high: 0.5,
899            low: 0.6,
900            factor: 0.5,
901        };
902        assert!(Limits::builder().pressure(bad).build().is_err());
903        assert!(Limits::builder().lane_reserve(Lane(8), 1).build().is_err());
904        assert!(
905            Limits::builder()
906                .max_connections(10)
907                .lane_reserve(Lane(0), 6)
908                .lane_reserve(Lane(1), 5)
909                .build()
910                .is_err()
911        );
912    }
913
914    #[test]
915    fn test_validate_descriptor_limit_and_budget() {
916        let l = Limits::builder().max_connections(1000).build().unwrap();
917        assert!(matches!(
918            l.validate(4, Some(500)),
919            Err(NetError::InvalidConfig {
920                reason: ConfigReason::DescriptorLimit { have: 500, .. },
921                ..
922            })
923        ));
924        assert!(l.validate(4, Some(100_000)).is_ok());
925        let small = Limits::builder().buffer_budget(1024).build().unwrap();
926        assert!(matches!(
927            small.validate(4, None),
928            Err(NetError::InvalidConfig {
929                reason: ConfigReason::BudgetBelowWorkers { .. },
930                ..
931            })
932        ));
933    }
934
935    #[test]
936    fn test_overlay_takes_listener_values() {
937        let base = Limits::builder()
938            .max_connections(100)
939            .idle_timeout(Duration::from_secs(9))
940            .build()
941            .unwrap();
942        let over = Limits::builder()
943            .idle_timeout(Duration::from_secs(1))
944            .build()
945            .unwrap();
946        let merged = base.overlay(&over);
947        assert_eq!(merged.max_connections(), Some(100));
948        assert_eq!(merged.idle_timeout(), Some(Duration::from_secs(1)));
949    }
950
951    #[test]
952    fn test_resolve_sets_every_value() {
953        let r = Limits::default().resolve(DriverKind::Blocking);
954        assert!(
955            r.max_connections().is_some() && r.tick().is_some() && r.graceful_linger().is_some()
956        );
957    }
958}