Skip to main content

ntex_io/
cfg.rs

1//! I/O buffer, timeout, and frame-rate configuration.
2
3use ntex_bytes::BytePageSize;
4use ntex_service::cfg::{CfgContext, Configuration};
5use ntex_util::{time::Millis, time::Seconds};
6
7#[derive(Debug)]
8/// Shared configuration for an [`crate::Io`] stream.
9pub struct IoConfig {
10    connect_timeout: Millis,
11    keepalive_timeout: Seconds,
12    shutdown_timeout: Seconds,
13    frame_read_rate: Option<FrameReadRate>,
14    write_timeout: Seconds,
15
16    // read side configuration
17    read_size_min: BytePageSize,
18    read_size_max: BytePageSize,
19    read_backpressure: usize,
20
21    // write side configuration
22    write_size: BytePageSize,
23    write_backpressure: usize,
24    write_buf_threshold: usize,
25
26    // shared config
27    pub(crate) config: CfgContext,
28}
29
30impl Default for IoConfig {
31    fn default() -> Self {
32        IoConfig::new()
33    }
34}
35
36impl Configuration for IoConfig {
37    const NAME: &str = "IO Configuration";
38
39    fn ctx(&self) -> &CfgContext {
40        &self.config
41    }
42
43    fn set_ctx(&mut self, ctx: CfgContext) {
44        self.config = ctx;
45    }
46}
47
48/// Minimum read rate required while decoding one frame.
49#[derive(Copy, Clone, Debug)]
50pub struct FrameReadRate {
51    /// Initial read timeout.
52    pub timeout: Seconds,
53    /// Maximum cumulative timeout for the frame.
54    pub max_timeout: Seconds,
55    /// Byte-progress threshold that must be exceeded to extend the deadline.
56    ///
57    /// Progress must be strictly greater than this value for another `timeout`
58    /// period to be granted.
59    pub rate: u32,
60}
61
62impl IoConfig {
63    #[inline]
64    #[must_use]
65    /// Creates an I/O configuration with default settings.
66    pub fn new() -> IoConfig {
67        IoConfig {
68            config: CfgContext::default(),
69            connect_timeout: Millis::ZERO,
70            keepalive_timeout: Seconds(0),
71            shutdown_timeout: Seconds(1),
72            frame_read_rate: None,
73
74            read_size_min: BytePageSize::Size4,
75            read_size_max: BytePageSize::Size64,
76            read_backpressure: BytePageSize::Size32.capacity(),
77
78            write_timeout: Seconds(0),
79            write_size: BytePageSize::Size16,
80            write_backpressure: BytePageSize::Size16.capacity(),
81            write_buf_threshold: BytePageSize::Size16.half_capacity(),
82        }
83    }
84
85    #[inline]
86    /// Returns the shared configuration tag.
87    pub fn tag(&self) -> &str {
88        self.config.tag()
89    }
90
91    #[inline]
92    /// Returns the connection timeout.
93    pub fn connect_timeout(&self) -> Millis {
94        self.connect_timeout
95    }
96
97    #[inline]
98    /// Returns the keep-alive timeout.
99    pub fn keepalive_timeout(&self) -> Seconds {
100        self.keepalive_timeout
101    }
102
103    #[inline]
104    /// Returns the graceful shutdown timeout.
105    pub fn shutdown_timeout(&self) -> Seconds {
106        self.shutdown_timeout
107    }
108
109    #[inline]
110    /// Returns the frame read-rate configuration.
111    pub fn frame_read_rate(&self) -> Option<&FrameReadRate> {
112        self.frame_read_rate.as_ref()
113    }
114
115    #[inline]
116    /// Returns the smallest page size of read buffers.
117    ///
118    /// Connections start reading into pages of this size, see
119    /// [`set_read_size`](Self::set_read_size).
120    pub fn read_size_min(&self) -> BytePageSize {
121        self.read_size_min
122    }
123
124    #[inline]
125    /// Returns the largest page size of read buffers.
126    ///
127    /// The read page size of a connection adapts to its input up to this
128    /// size, see [`set_read_size`](Self::set_read_size).
129    pub fn read_size_max(&self) -> BytePageSize {
130        self.read_size_max
131    }
132
133    #[inline]
134    /// Returns the read backpressure high watermark.
135    ///
136    /// Read backpressure is enabled once buffered input reaches it and
137    /// released once it falls to half of it, see
138    /// [`set_read_backpressure`](Self::set_read_backpressure).
139    pub fn read_backpressure(&self) -> usize {
140        self.read_backpressure
141    }
142
143    #[inline]
144    /// Returns the write backpressure timeout.
145    ///
146    /// A zero value means the timeout is disabled.
147    pub fn write_timeout(&self) -> Seconds {
148        self.write_timeout
149    }
150
151    #[inline]
152    /// Returns the write backpressure high watermark.
153    ///
154    /// Write backpressure is released once outstanding output falls to half of
155    /// it, see [`set_write_backpressure`](Self::set_write_backpressure).
156    pub fn write_backpressure(&self) -> usize {
157        self.write_backpressure
158    }
159
160    #[inline]
161    /// Buffered input size that releases read backpressure.
162    pub(crate) fn read_half(&self) -> usize {
163        self.read_backpressure >> 1
164    }
165
166    #[inline]
167    /// Outstanding output size that releases write backpressure.
168    pub(crate) fn write_half(&self) -> usize {
169        self.write_backpressure >> 1
170    }
171
172    #[inline]
173    /// Returns the write-buffer page size.
174    pub fn write_size(&self) -> BytePageSize {
175        self.write_size
176    }
177
178    #[inline]
179    /// Returns the buffered write size that triggers an earlier send.
180    pub fn write_buf_threshold(&self) -> usize {
181        self.write_buf_threshold
182    }
183
184    /// Sets the connection timeout.
185    ///
186    /// A zero duration disables the timeout. It is disabled by default.
187    #[must_use]
188    pub fn set_connect_timeout<T: Into<Millis>>(mut self, timeout: T) -> Self {
189        self.connect_timeout = timeout.into();
190        self
191    }
192
193    /// Sets the keep-alive timeout.
194    ///
195    /// The dispatcher runs the timer only while the connection is idle: no
196    /// input is buffered, no partial frame is being read, and no decoded
197    /// frames are being handled. It starts when the dispatcher enters this
198    /// idle state, including after all decoded frames have been handled.
199    /// Partial frames are bounded by
200    /// [frame read-rate](Self::set_frame_read_rate) limits instead, and write
201    /// backpressure by the [write timeout](Self::set_write_timeout).
202    ///
203    /// A zero duration disables the timeout. It is disabled by default.
204    #[must_use]
205    pub fn set_keepalive_timeout<T: Into<Seconds>>(mut self, timeout: T) -> Self {
206        self.keepalive_timeout = timeout.into();
207        self
208    }
209
210    /// Sets the graceful shutdown timeout.
211    ///
212    /// A graceful shutdown runs in two phases, and this single timeout bounds
213    /// them together rather than applying to each one:
214    ///
215    /// 1. **Filter shutdown.** Both directions stay open, so a filter can emit
216    ///    its closing data and still read the peer's. A TLS filter sends its
217    ///    `close_notify` here, and a WebSocket filter its close frame.
218    /// 2. **Transport shutdown.** The remaining output is drained to the peer.
219    ///    The read side is paused, and whatever the peer still sent is
220    ///    discarded, then the connection is closed.
221    ///
222    /// The deadline is armed when the first phase begins and is not restarted
223    /// for the second, so a filter that shuts down slowly leaves less time to
224    /// drain. Expiry in the first phase moves on to the second rather than
225    /// giving up; only expiry in the second terminates the connection, and
226    /// output that has not reached the transport is then lost. Either way
227    /// [`crate::Io::shutdown`] reports a timed-out error once the transport
228    /// has stopped.
229    ///
230    /// The timeout also applies when no application output is pending: a
231    /// filter may still be waiting for the peer to complete its shutdown
232    /// exchange. If both phases can finish immediately, the deadline has no
233    /// observable effect.
234    ///
235    /// The default is one second.
236    ///
237    /// # Panics
238    ///
239    /// Panics if `timeout` is zero. Without a deadline, a peer that never
240    /// completes the exchange, or never reads the remaining output, could
241    /// hold the connection open forever.
242    #[must_use]
243    pub fn set_shutdown_timeout<T: Into<Seconds>>(mut self, timeout: T) -> Self {
244        let timeout = timeout.into();
245        assert!(
246            timeout.non_zero(),
247            "shutdown timeout must be greater than zero"
248        );
249        self.shutdown_timeout = timeout;
250        self
251    }
252
253    /// Sets read-rate parameters for a single decoded frame.
254    ///
255    /// Rate tracking starts when a new connection arrives, for its first
256    /// frame, and later whenever a decoder returns no complete item after
257    /// receiving partial frame data, whether the data is left in the read
258    /// buffer or consumed into the decoder's own state. The dispatcher then
259    /// allows one `timeout` period for additional data to arrive.
260    ///
261    /// When that period expires, the dispatcher compares the bytes received
262    /// for the frame since the previous check with `rate`:
263    ///
264    /// - If the progress is greater than `rate`, the deadline is extended by
265    ///   another `timeout` period.
266    /// - If the progress is at most `rate`, frame decoding fails with a read
267    ///   timeout.
268    /// - Completing the frame clears the timer and resets rate tracking for the
269    ///   next frame.
270    ///
271    /// `max_timeout` limits the cumulative time allowed for one frame. A zero
272    /// value permits an unlimited number of extensions while the required rate
273    /// is maintained. A non-zero value is enforced in whole `timeout` periods,
274    /// so the effective limit is rounded up to a multiple of `timeout` and is
275    /// never shorter than the initial period.
276    ///
277    /// A zero `timeout` disables frame read-rate enforcement and ignores
278    /// `max_timeout` and `rate`. With a non-zero timeout and `rate` set to zero,
279    /// any positive byte progress permits another period.
280    ///
281    /// A new connection must therefore deliver its first frame within these
282    /// limits. After a frame has been decoded, idle connections with no
283    /// partial frame are governed separately by
284    /// [`set_keepalive_timeout`](Self::set_keepalive_timeout).
285    ///
286    /// The timer is stopped while write backpressure is active, when frames
287    /// are not decoded, and a new period starts once decoding resumes. While
288    /// the service is not ready the timer is stopped as well, and tracking
289    /// restarts with a fresh period and `max_timeout` budget once the service
290    /// is ready.
291    ///
292    /// Frame read-rate enforcement is disabled by default.
293    #[must_use]
294    pub fn set_frame_read_rate(
295        mut self,
296        timeout: Seconds,
297        max_timeout: Seconds,
298        rate: u32,
299    ) -> Self {
300        self.frame_read_rate = if timeout.is_zero() {
301            None
302        } else {
303            Some(FrameReadRate {
304                timeout,
305                max_timeout,
306                rate,
307            })
308        };
309        self
310    }
311
312    /// Sets the write backpressure timeout.
313    ///
314    /// Write backpressure is enabled when outstanding output reaches the
315    /// [write buffer](Self::set_write_backpressure) high watermark and disabled once the
316    /// peer has accepted enough of it. The timeout covers that whole period:
317    /// if backpressure is still enabled when it expires, the dispatcher stops
318    /// with a write timeout. Each backpressure period starts a fresh timeout,
319    /// however much the peer read during the previous one. Without a write
320    /// timeout, a peer that stops reading during backpressure can hold the
321    /// connection open indefinitely.
322    ///
323    /// The timeout does not apply once backpressure is disabled, even though
324    /// output is still outstanding. A peer that stops reading at that point
325    /// can leave up to half of the high watermark unwritten; only the
326    /// [keep-alive timeout](Self::set_keepalive_timeout), when enabled, bounds
327    /// such a connection until it is shut down.
328    ///
329    /// Reads paused because output produced by reading, for example replies
330    /// to peer pings, has not drained are not covered either. While the
331    /// dispatcher is idle, the keep-alive timeout bounds them. Application
332    /// output written during such a pause enables write backpressure.
333    ///
334    /// Outside the dispatcher, the timeout also bounds each wait for output in
335    /// [`Io::send`](crate::Io::send), [`Io::flush`](crate::Io::flush) and
336    /// [`IoRef::write_ready`](crate::IoRef::write_ready). A wait that does not
337    /// complete in time fails with [`io::ErrorKind::TimedOut`](std::io::ErrorKind::TimedOut),
338    /// and the connection is left open for the caller to close. The polling
339    /// methods, such as [`Io::poll_flush`](crate::Io::poll_flush), are not
340    /// bounded.
341    ///
342    /// A zero duration disables the timeout. It is disabled by default, so
343    /// a peer that does not read can pin the connection and its buffered
344    /// output. Servers that accept untrusted peers should set it.
345    #[must_use]
346    pub fn set_write_timeout(mut self, timeout: Seconds) -> Self {
347        self.write_timeout = timeout;
348        self
349    }
350
351    /// Sets the range of read-buffer page sizes.
352    ///
353    /// Each connection reads into pages of its own page size, which starts at
354    /// `min` and adapts to the connection's input between `min` and `max`.
355    /// Input that arrives in batches larger than the page capacity, such as
356    /// streamed bodies or large frames, grows the page size of the next read
357    /// buffers to fit a batch, so it is read with fewer and larger transport
358    /// reads. After several batches in a row that would fit in half of the
359    /// next smaller page size, the page size shrinks by one step. Connections
360    /// with small messages keep small pages, which also bounds the memory a
361    /// partial frame or a split-off frame holds.
362    ///
363    /// Within a batch a buffer grows with [`BytesMut::reserve_more`](ntex_bytes::BytesMut::reserve_more) once less
364    /// than [`BytePageSize::low`] of its page size remains: the data is
365    /// compacted within its page when that leaves room for half a page,
366    /// otherwise the buffer moves to the next page size.
367    ///
368    /// The page size does not depend on the read backpressure watermark, see
369    /// [`set_read_backpressure`](Self::set_read_backpressure). A single read
370    /// can fill a page larger than the watermark, reads are paused only after
371    /// the buffered input reaches it.
372    ///
373    /// Read buffers come from the per-thread page cache of `ntex-bytes`,
374    /// shared with write buffers and all configurations, see
375    /// [`ntex_bytes::set_page_cache_size`]. A buffer returns to the cache of
376    /// the thread that drops its last reference. Buffers grown beyond the
377    /// largest page size are freed. A connection holding
378    /// unconsumed input, such as the start of a frame that has not fully
379    /// arrived, keeps its whole read buffer, so each such connection uses at
380    /// least one page until the rest arrives. Read-rate timeouts,
381    /// see [`set_frame_read_rate`](Self::set_frame_read_rate), bound how long
382    /// a slow peer can hold it.
383    ///
384    /// Frames that a codec splits off the read buffer, such as `Bytes`
385    /// payloads, share its allocation. A frame kept alive keeps the whole
386    /// read buffer allocated, it returns to the cache only once the last such
387    /// frame is dropped, so retaining many small frames can use far more
388    /// memory than their size.
389    /// Copy long-lived frames or call [`Bytes::trimdown`](ntex_bytes::Bytes::trimdown)
390    /// on them to release the rest of the buffer.
391    ///
392    /// Set `min` and `max` to the same size to read into pages of a fixed
393    /// size. The default range is 4 KiB to 64 KiB.
394    ///
395    /// # Panics
396    ///
397    /// Panics if `min` or `max` is [`BytePageSize::Unset`], or `min` is
398    /// larger than `max`.
399    #[must_use]
400    pub fn set_read_size(mut self, min: BytePageSize, max: BytePageSize) -> Self {
401        assert!(
402            min != BytePageSize::Unset && max != BytePageSize::Unset,
403            "read buffer page size must be set"
404        );
405        assert!(
406            min.capacity() <= max.capacity(),
407            "read buffer min page size must not be larger than max"
408        );
409        self.read_size_min = min;
410        self.read_size_max = max;
411        self
412    }
413
414    /// Sets the read backpressure watermark.
415    ///
416    /// Read backpressure is enabled when the application-facing read buffer
417    /// reaches `size` bytes and released once it falls to half of it. It does
418    /// not affect the read page size, see [`set_read_size`](Self::set_read_size).
419    ///
420    /// The default watermark is the capacity of a 32 KiB page.
421    ///
422    /// # Panics
423    ///
424    /// Panics if `size` is zero.
425    #[must_use]
426    pub fn set_read_backpressure(mut self, size: usize) -> Self {
427        assert!(size > 0, "read backpressure must be greater than zero");
428        self.read_backpressure = size;
429        self
430    }
431
432    /// Sets the write-buffer page size.
433    ///
434    /// Write buffers are represented as a sequence of reusable byte pages.
435    /// `size` selects the capacity category used when those buffers allocate
436    /// new internal pages, including the intermediate write buffers created
437    /// between filter layers. [`BytePages`](ntex_bytes::BytePages) may also
438    /// contain externally supplied [`BytePage`](ntex_bytes::BytePage),
439    /// [`Bytes`](ntex_bytes::Bytes), or `Vec<u8>` segments; this setting does
440    /// not resize or copy those segments.
441    ///
442    /// Smaller pages reduce unused capacity for connections that usually
443    /// produce small writes. Larger pages can reduce allocation and page-list
444    /// overhead for connections that regularly buffer larger writes. This
445    /// setting does not limit the total amount of buffered data or determine
446    /// the size of individual transport write operations.
447    ///
448    /// Changing the page size on an active connection through
449    /// [`Io::set_config`](crate::Io::set_config) updates the allocation
450    /// category for future pages in every existing write buffer. Pages that
451    /// have already been allocated retain their current capacity. Filter
452    /// layers added later use the new page size.
453    ///
454    /// The page size is independent of the eager-write threshold and write
455    /// backpressure watermarks. Changing it does not update values configured
456    /// by [`set_write_buf_threshold`](Self::set_write_buf_threshold) or
457    /// [`set_write_backpressure`](Self::set_write_backpressure).
458    ///
459    /// The default page size is 16 KiB.
460    ///
461    /// # Panics
462    ///
463    /// Panics if `size` is [`BytePageSize::Unset`].
464    #[must_use]
465    pub fn set_write_size(mut self, size: BytePageSize) -> Self {
466        assert!(
467            size != BytePageSize::Unset,
468            "write buffer page size must be set"
469        );
470        self.write_size = size;
471        self
472    }
473
474    /// Sets the write buffer threshold.
475    ///
476    /// Application code can encode multiple items during one dispatcher turn.
477    /// Normally, the transport write task is scheduled to run after that work
478    /// yields, so all items produced during the turn may accumulate in the
479    /// write buffer and be sent as one large burst.
480    ///
481    /// When the buffered size reaches `size` while the transport write task is
482    /// paused, `Io` asks the transport handle to start writing immediately.
483    /// This eager write attempt occurs synchronously with buffer consolidation,
484    /// before the current application or dispatcher turn necessarily
485    /// completes. If bytes remain afterward, the normal write task is
486    /// scheduled to continue delivery. Data below the threshold is delivered
487    /// through that normal scheduled path.
488    ///
489    /// The threshold is a latency and write-burst tuning parameter. It does not
490    /// limit write-buffer growth, provide a flush guarantee, or control write
491    /// backpressure; use [`set_write_backpressure`](Self::set_write_backpressure) for
492    /// backpressure watermarks and [`Io::flush`](crate::Io::flush) when a
493    /// caller must wait for buffered data to be written.
494    ///
495    /// Set `size` to zero to disable eager writes when this configuration is
496    /// used to construct an [`Io`](crate::Io). The default is 8 KiB, derived
497    /// from the default 16 KiB page size when [`IoConfig::new`] is called.
498    /// Changing the page size later does not recalculate this threshold.
499    ///
500    /// Replacing an active connection's configuration with
501    /// [`Io::set_config`](crate::Io::set_config) enables or disables eager
502    /// writes according to the replacement threshold.
503    #[must_use]
504    pub fn set_write_buf_threshold(mut self, size: usize) -> Self {
505        self.write_buf_threshold = size;
506        self
507    }
508
509    /// Sets the write-buffer backpressure watermark.
510    ///
511    /// Write backpressure is enabled at `size` bytes of outstanding output and
512    /// must be greater than zero. Backpressure is released after the
513    /// outstanding size falls to half of this value. Outstanding output is the
514    /// buffered output plus any output a transport has taken ownership of but
515    /// not yet written to the peer.
516    ///
517    /// Output is held in [`BytePages`](ntex_bytes::BytePages), which are sized
518    /// by [`set_write_size`](Self::set_write_size).
519    ///
520    /// By default, the high watermark is approximately 16 KiB.
521    ///
522    /// # Panics
523    ///
524    /// Panics if `size` is zero.
525    #[must_use]
526    pub fn set_write_backpressure(mut self, size: usize) -> Self {
527        assert!(size > 0, "write backpressure must be greater than zero");
528        self.write_backpressure = size;
529        self
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use ntex_service::cfg::SharedCfg;
536
537    use super::*;
538
539    #[test]
540    fn buffer_configuration() {
541        let cfg = IoConfig::new()
542            .set_read_backpressure(4096)
543            .set_write_backpressure(2048);
544
545        assert_eq!(cfg.read_backpressure(), 4096);
546        assert_eq!(cfg.read_half(), 2048);
547        assert_eq!(cfg.write_backpressure(), 2048);
548        assert_eq!(cfg.write_half(), 1024);
549    }
550
551    #[test]
552    fn read_size_configuration() {
553        let default = BytePageSize::Size32.capacity();
554        let cfg = IoConfig::new();
555        assert_eq!(cfg.read_size_min(), BytePageSize::Size4);
556        assert_eq!(cfg.read_size_max(), BytePageSize::Size64);
557        assert_eq!(cfg.read_backpressure(), default);
558        assert_eq!(cfg.read_half(), default / 2);
559        assert_eq!(cfg.write_backpressure(), BytePageSize::Size16.capacity());
560
561        // the read size does not change the backpressure watermark
562        let cfg = cfg
563            .set_read_size(BytePageSize::Size8, BytePageSize::Size8)
564            .set_write_backpressure(2048);
565        assert_eq!(cfg.read_size_min(), BytePageSize::Size8);
566        assert_eq!(cfg.read_size_max(), BytePageSize::Size8);
567        assert_eq!(cfg.read_backpressure(), default);
568        assert_eq!(cfg.write_backpressure(), 2048);
569        assert_eq!(cfg.write_half(), 1024);
570
571        let cfg = cfg.set_read_size(BytePageSize::Size16, BytePageSize::Size256);
572        assert_eq!(cfg.read_size_min(), BytePageSize::Size16);
573        assert_eq!(cfg.read_size_max(), BytePageSize::Size256);
574        assert_eq!(cfg.read_backpressure(), default);
575    }
576
577    #[test]
578    fn read_backpressure_configuration() {
579        let cfg = IoConfig::new()
580            .set_read_backpressure(64 * 1024)
581            .set_read_size(BytePageSize::Size4, BytePageSize::Size4);
582        assert_eq!(cfg.read_size_min(), BytePageSize::Size4);
583        assert_eq!(cfg.read_backpressure(), 64 * 1024);
584        assert_eq!(cfg.read_half(), 32 * 1024);
585    }
586
587    #[test]
588    #[should_panic(expected = "read buffer page size must be set")]
589    fn unset_read_size() {
590        let _ = IoConfig::new().set_read_size(BytePageSize::Size4, BytePageSize::Unset);
591    }
592
593    #[test]
594    #[should_panic(expected = "read buffer min page size must not be larger than max")]
595    fn inverted_read_size() {
596        let _ = IoConfig::new().set_read_size(BytePageSize::Size16, BytePageSize::Size8);
597    }
598    #[test]
599    #[should_panic(expected = "write buffer page size must be set")]
600    fn unset_write_size() {
601        let _ = IoConfig::new().set_write_size(BytePageSize::Unset);
602    }
603
604    #[test]
605    #[should_panic(expected = "read backpressure must be greater than zero")]
606    fn zero_read_backpressure() {
607        let _ = IoConfig::new().set_read_backpressure(0);
608    }
609
610    #[test]
611    fn frame_read_rate_configuration() {
612        let cfg = IoConfig::new().set_frame_read_rate(Seconds(1), Seconds(3), 128);
613        let rate = cfg.frame_read_rate().unwrap();
614        assert_eq!(rate.timeout, Seconds(1));
615        assert_eq!(rate.max_timeout, Seconds(3));
616        assert_eq!(rate.rate, 128);
617
618        let cfg = cfg.set_frame_read_rate(Seconds::ZERO, Seconds(10), 1024);
619        assert!(cfg.frame_read_rate().is_none());
620    }
621
622    #[test]
623    fn config_accessors() {
624        let cfg = IoConfig::new();
625        assert_eq!(cfg.connect_timeout(), Millis::ZERO);
626        assert_eq!(cfg.keepalive_timeout(), Seconds(0));
627        assert_eq!(cfg.write_size(), BytePageSize::Size16);
628
629        let cfg = cfg
630            .set_connect_timeout(Millis(500))
631            .set_keepalive_timeout(Seconds(7))
632            .set_write_size(BytePageSize::Size4);
633        assert_eq!(cfg.connect_timeout(), Millis(500));
634        assert_eq!(cfg.keepalive_timeout(), Seconds(7));
635        assert_eq!(cfg.write_size(), BytePageSize::Size4);
636
637        let shared = SharedCfg::new("CFG-TAG").add(cfg).build();
638        let cfg = shared.get::<IoConfig>();
639        assert_eq!(cfg.tag(), "CFG-TAG");
640        assert_eq!(cfg.keepalive_timeout(), Seconds(7));
641    }
642
643    #[test]
644    fn write_timeout_configuration() {
645        let cfg = IoConfig::new();
646        assert!(cfg.write_timeout().is_zero());
647
648        let cfg = cfg.set_write_timeout(Seconds(3));
649        assert_eq!(cfg.write_timeout(), Seconds(3));
650
651        let cfg = cfg.set_write_timeout(Seconds::ZERO);
652        assert!(cfg.write_timeout().is_zero());
653    }
654
655    #[test]
656    #[should_panic(expected = "write backpressure must be greater than zero")]
657    fn zero_write_backpressure() {
658        let _ = IoConfig::new().set_write_backpressure(0);
659    }
660
661    #[test]
662    #[should_panic(expected = "shutdown timeout must be greater than zero")]
663    fn zero_shutdown_timeout_is_rejected() {
664        let _ = IoConfig::new().set_shutdown_timeout(Seconds::ZERO);
665    }
666}