Skip to main content

tokio_client/
tokio_client.rs

1//! Low-level Tokio client example (`tokio` feature).
2//!
3//! Drives [`AsyncClient`] directly on a caller-owned runtime. This is the
4//! adapter to reach for when you already have a Tokio runtime and want to
5//! await sends/receives yourself, rather than use the higher-level `managed`
6//! driver (see the `managed_client` example).
7//!
8//! Run a server first, then run this example:
9//!
10//! ```text
11//! cargo run -p irtt-rs --bin irtt-server --features server
12//! cargo run -p irtt-client --example tokio_client --features tokio
13//! ```
14//!
15//! Without a reachable server this exits quickly with an open-timeout error
16//! instead of hanging, because the example shortens `open_timeouts` for a
17//! fast demonstration.
18
19use std::time::{Duration, Instant};
20
21use irtt_client::{AsyncClient, ClientConfig, ClientEvent};
22
23/// Upper bound on how long the receive wait can go without also calling
24/// `poll_timeouts()`. `AsyncClient` does not expose the earliest pending
25/// probe's own timeout deadline, so this example polls on a short fixed
26/// cadence instead of waiting for the full `probe_timeout()` from whenever
27/// this loop last ran — that would let poll_timeouts() classify a lost
28/// probe (and its dependent statistics/session state) far later than it
29/// actually became lost.
30const TIMEOUT_POLL_INTERVAL: Duration = Duration::from_millis(50);
31
32fn main() {
33    let runtime = tokio::runtime::Builder::new_current_thread()
34        .enable_io()
35        .enable_time()
36        .build()
37        .expect("failed to build Tokio runtime");
38
39    runtime.block_on(run());
40}
41
42async fn run() {
43    let config = ClientConfig {
44        server_addr: "127.0.0.1:2112".to_owned(),
45        duration: Some(Duration::from_secs(2)),
46        interval: Duration::from_millis(200),
47        // A single short attempt keeps this example fast when no server is
48        // reachable; production callers should generally keep the default.
49        open_timeouts: vec![Duration::from_millis(300)],
50        ..Default::default()
51    };
52
53    let mut client = match AsyncClient::connect(config).await {
54        Ok(client) => client,
55        Err(err) => {
56            eprintln!("failed to prepare client socket: {err}");
57            return;
58        }
59    };
60
61    match client.open().await {
62        Ok(outcome) => println!("session opened: {outcome:?}"),
63        Err(err) => {
64            eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
65            return;
66        }
67    }
68
69    while !client.is_run_complete() {
70        // Bound the receive wait by the next send/timeout deadline, rather
71        // than awaiting recv() unconditionally: a lost or rate-limited reply
72        // must not stall pacing or timeout classification indefinitely.
73        let wake_at = client
74            .next_send_deadline()
75            .into_iter()
76            .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
77            .min()
78            .expect("the fixed poll-interval deadline is always present");
79
80        let recv_result = tokio::select! {
81            events = client.recv() => Some(events),
82            () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
83        };
84        if let Some(result) = recv_result {
85            match result {
86                Ok(events) => events.iter().for_each(print_event),
87                Err(err) => {
88                    eprintln!("receive failed: {err}");
89                    break;
90                }
91            }
92        }
93
94        if client
95            .next_send_deadline()
96            .is_some_and(|send_at| Instant::now() >= send_at)
97        {
98            // A failed send retains its prepared probe without advancing the
99            // schedule, so a persistent error (e.g. the interface going
100            // down) would otherwise make this loop retry forever instead of
101            // reporting it.
102            match client.send_probe().await {
103                Ok(events) => events.iter().for_each(print_event),
104                Err(err) => {
105                    eprintln!("send failed: {err}");
106                    break;
107                }
108            }
109        }
110        match client.poll_timeouts() {
111            Ok(events) => events.iter().for_each(print_event),
112            Err(err) => {
113                eprintln!("timeout polling failed: {err}");
114                break;
115            }
116        }
117    }
118
119    match client.close().await {
120        Ok(events) => events.iter().for_each(print_event),
121        Err(err) => eprintln!("close failed: {err}"),
122    }
123}
124
125fn print_event(event: &ClientEvent) {
126    match event {
127        ClientEvent::EchoReply { seq, rtt, .. } => {
128            println!("seq {seq}: rtt {:?}", rtt.effective);
129        }
130        ClientEvent::EchoLoss { seq, .. } => println!("seq {seq}: lost"),
131        other => println!("{other:?}"),
132    }
133}