Skip to main content

hopr_transport/
constants.rs

1use std::time::Duration;
2
3/// The maximum waiting time for a message send to produce a half-key challenge reply
4pub const PACKET_QUEUE_TIMEOUT_MILLISECONDS: std::time::Duration = std::time::Duration::from_millis(15000);
5
6/// Maximum number of outgoing application-layer packets buffered before the writer
7/// observes backpressure (`Poll::Pending` on `poll_write`).
8///
9/// Caps the burst submitted to `buffered(8×N_cpus)` so tail packets never
10/// exceed `PACKET_ENCODING_TIMEOUT` (150 ms). With Sphinx encoding at ~21 ms/packet
11/// and 256 slots, the worst-case Rayon queue depth stays well under the timeout on
12/// machines with up to ~36 cores. Safe formula: `≤ 7 × available_parallelism()`.
13pub(crate) const MAXIMUM_MSG_OUTGOING_BUFFER_SIZE: usize = 256;
14
15/// Time within Start protocol must finish session initiation.
16/// This base value is always multiplied by the (max) number of hops, times 2 (for both-ways).
17pub(crate) const SESSION_INITIATION_TIMEOUT_BASE: Duration = Duration::from_secs(5);
18
19#[cfg(test)]
20mod tests {
21    use super::MAXIMUM_MSG_OUTGOING_BUFFER_SIZE;
22
23    /// Guard against accidentally inflating `MAXIMUM_MSG_OUTGOING_BUFFER_SIZE` back to a large
24    /// value that would overflow the Rayon encoding queue.
25    ///
26    /// Safe formula: ≤ 7 × available_parallelism. 256 covers machines with up to ~36 cores.
27    #[test]
28    fn outgoing_buffer_size_within_backpressure_limit() {
29        assert!(
30            MAXIMUM_MSG_OUTGOING_BUFFER_SIZE <= 256,
31            "MAXIMUM_MSG_OUTGOING_BUFFER_SIZE={MAXIMUM_MSG_OUTGOING_BUFFER_SIZE} exceeds the safe threshold; tail \
32             packets will exceed PACKET_ENCODING_TIMEOUT under burst writes"
33        );
34    }
35
36    /// Verify that `CrossfireSink` signals backpressure (`Poll::Pending`) once the channel
37    /// reaches `MAXIMUM_MSG_OUTGOING_BUFFER_SIZE`, preventing the Rayon encoding queue from
38    /// receiving an unbounded burst.
39    #[test]
40    fn outgoing_channel_signals_backpressure_when_full() {
41        use std::{
42            pin::Pin,
43            task::{Context, Poll},
44        };
45
46        use futures::Sink;
47        use hopr_utils::network_types::crossfire_sink::bounded_sink_channel;
48
49        let (mut sink, _rx) = bounded_sink_channel::<usize>(MAXIMUM_MSG_OUTGOING_BUFFER_SIZE);
50        let waker = futures::task::noop_waker_ref();
51        let mut cx = Context::from_waker(waker);
52
53        // Standard Sink protocol: each poll_ready → start_send pair sends one item.
54        // At i=0 the channel is empty; at i=N-1 the final item is buffered.
55        for i in 0..MAXIMUM_MSG_OUTGOING_BUFFER_SIZE {
56            assert!(
57                matches!(Pin::new(&mut sink).poll_ready(&mut cx), Poll::Ready(Ok(()))),
58                "poll_ready must be Ready before capacity is reached (item {i})"
59            );
60            Pin::new(&mut sink).start_send(i).unwrap();
61        }
62        // This poll_ready sends the last buffered item; channel is now exactly full.
63        assert!(
64            matches!(Pin::new(&mut sink).poll_ready(&mut cx), Poll::Ready(Ok(()))),
65            "poll_ready must be Ready when flushing the final item into a full-but-not-yet-full channel"
66        );
67
68        // One extra item: buffer it, then poll_ready must indicate the channel is saturated.
69        Pin::new(&mut sink)
70            .start_send(MAXIMUM_MSG_OUTGOING_BUFFER_SIZE)
71            .unwrap();
72        assert!(
73            matches!(Pin::new(&mut sink).poll_ready(&mut cx), Poll::Pending),
74            "CrossfireSink must return Poll::Pending when channel is at capacity ({})",
75            MAXIMUM_MSG_OUTGOING_BUFFER_SIZE
76        );
77    }
78}