pub struct Client { /* private fields */ }Expand description
Low-level synchronous IRTT client.
Client exposes the protocol steps directly: connect a UDP socket, open a
session, send probes, receive replies, poll timeouts, and close. Callers
that do not need to own this loop can use the unified managed API in
crate::managed when the tokio feature is enabled.
§Example
Client is the runtime-free adapter. The caller drives receives and timeout
polling between sends; see the standalone example for a complete loop.
This example type-checks without contacting a server:
use irtt_client::{Client, ClientConfig};
let mut client = Client::connect(ClientConfig::default())?;
let outcome = client.open()?;
println!("opened: {outcome:?}");
let sent = client.send_probe()?;
println!("sent: {sent:?}");
let received = client.recv_once()?;
println!("received: {received:?}");
let timed_out = client.poll_timeouts()?;
println!("timeouts: {timed_out:?}");
let closed = client.close()?;
println!("closed: {closed:?}");Implementations§
Source§impl Client
impl Client
Sourcepub fn connect(config: ClientConfig) -> Result<Self, ClientError>
pub fn connect(config: ClientConfig) -> Result<Self, ClientError>
Resolve the configured server and create a connected UDP socket.
This validates local configuration and prepares the open request, but it
does not contact the server. Call open to perform the
IRTT open exchange.
Examples found in repository?
25fn main() {
26 let config = ClientConfig {
27 server_addr: "127.0.0.1:2112".to_owned(),
28 duration: Some(RUN_DURATION),
29 interval: Duration::from_millis(200),
30 // A single short attempt keeps this example fast when no server is
31 // reachable; production callers should generally keep the default.
32 open_timeouts: vec![Duration::from_millis(300)],
33 socket_config: irtt_client::SocketConfig {
34 recv_timeout: Some(Duration::from_millis(100)),
35 ..Default::default()
36 },
37 ..Default::default()
38 };
39
40 let mut client = match Client::connect(config) {
41 Ok(client) => client,
42 Err(err) => {
43 eprintln!("failed to prepare client socket: {err}");
44 return;
45 }
46 };
47
48 match client.open() {
49 Ok(outcome) => println!("session opened: {outcome:?}"),
50 Err(err) => {
51 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
52 return;
53 }
54 }
55
56 // The run itself lasts `duration`, but a lost probe isn't classified as
57 // `EchoLoss` until `probe_timeout()` after it was sent; wait at least
58 // that much past the run's own end so a loss near the end can still be
59 // observed and printed before this example gives up and closes.
60 let deadline = Instant::now() + RUN_DURATION + client.probe_timeout();
61 while Instant::now() < deadline && !client.is_run_complete() {
62 // send_probe is caller-paced: only call it once its own schedule
63 // says a probe is due, rather than as fast as the loop spins.
64 if client
65 .next_send_deadline()
66 .is_some_and(|send_at| Instant::now() >= send_at)
67 {
68 // A failed send retains its prepared probe without advancing the
69 // schedule, so a persistent error (e.g. the interface going
70 // down) would otherwise make this loop retry silently until the
71 // safety deadline instead of reporting it.
72 match client.send_probe() {
73 Ok(events) => events.iter().for_each(print_event),
74 Err(err) => {
75 eprintln!("send failed: {err}");
76 break;
77 }
78 }
79 }
80 match client.recv_once() {
81 Ok(events) => events.iter().for_each(print_event),
82 Err(err) => {
83 eprintln!("receive failed: {err}");
84 break;
85 }
86 }
87 match client.poll_timeouts() {
88 Ok(events) => events.iter().for_each(print_event),
89 Err(err) => {
90 eprintln!("timeout polling failed: {err}");
91 break;
92 }
93 }
94 }
95
96 match client.close() {
97 Ok(events) => events.iter().for_each(print_event),
98 Err(err) => eprintln!("close failed: {err}"),
99 }
100}Sourcepub fn open(&mut self) -> Result<OpenOutcome, ClientError>
pub fn open(&mut self) -> Result<OpenOutcome, ClientError>
Perform the IRTT open exchange.
On success, returns the negotiated open outcome and transitions the
client into either an open probe session or completed no-test state.
Open attempts use ClientConfig::open_timeouts. Malformed or
unrelated datagrams are ignored until the current attempt’s absolute
deadline, so one attempt may consume several datagrams without
retransmitting. Silence or ignored traffic eventually produces
ClientError::OpenTimeout. A structurally recognizable malformed or
incompatible Open reply from the connected peer is terminal; when HMAC
is configured, that recognition additionally requires authentication.
When a trusted reply allocates a token but later negotiation or socket preparation fails, the client sends a best-effort cleanup close and preserves the original failure. Failed opening never leaves the session machine open.
Examples found in repository?
25fn main() {
26 let config = ClientConfig {
27 server_addr: "127.0.0.1:2112".to_owned(),
28 duration: Some(RUN_DURATION),
29 interval: Duration::from_millis(200),
30 // A single short attempt keeps this example fast when no server is
31 // reachable; production callers should generally keep the default.
32 open_timeouts: vec![Duration::from_millis(300)],
33 socket_config: irtt_client::SocketConfig {
34 recv_timeout: Some(Duration::from_millis(100)),
35 ..Default::default()
36 },
37 ..Default::default()
38 };
39
40 let mut client = match Client::connect(config) {
41 Ok(client) => client,
42 Err(err) => {
43 eprintln!("failed to prepare client socket: {err}");
44 return;
45 }
46 };
47
48 match client.open() {
49 Ok(outcome) => println!("session opened: {outcome:?}"),
50 Err(err) => {
51 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
52 return;
53 }
54 }
55
56 // The run itself lasts `duration`, but a lost probe isn't classified as
57 // `EchoLoss` until `probe_timeout()` after it was sent; wait at least
58 // that much past the run's own end so a loss near the end can still be
59 // observed and printed before this example gives up and closes.
60 let deadline = Instant::now() + RUN_DURATION + client.probe_timeout();
61 while Instant::now() < deadline && !client.is_run_complete() {
62 // send_probe is caller-paced: only call it once its own schedule
63 // says a probe is due, rather than as fast as the loop spins.
64 if client
65 .next_send_deadline()
66 .is_some_and(|send_at| Instant::now() >= send_at)
67 {
68 // A failed send retains its prepared probe without advancing the
69 // schedule, so a persistent error (e.g. the interface going
70 // down) would otherwise make this loop retry silently until the
71 // safety deadline instead of reporting it.
72 match client.send_probe() {
73 Ok(events) => events.iter().for_each(print_event),
74 Err(err) => {
75 eprintln!("send failed: {err}");
76 break;
77 }
78 }
79 }
80 match client.recv_once() {
81 Ok(events) => events.iter().for_each(print_event),
82 Err(err) => {
83 eprintln!("receive failed: {err}");
84 break;
85 }
86 }
87 match client.poll_timeouts() {
88 Ok(events) => events.iter().for_each(print_event),
89 Err(err) => {
90 eprintln!("timeout polling failed: {err}");
91 break;
92 }
93 }
94 }
95
96 match client.close() {
97 Ok(events) => events.iter().for_each(print_event),
98 Err(err) => eprintln!("close failed: {err}"),
99 }
100}Sourcepub fn close(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub fn close(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Send a close request and emit a ClientEvent::SessionClosed event.
The close event means the client has sent its close packet and stopped tracking the session locally; it is not a server acknowledgement. If the send fails, the negotiated DSCP is restored best-effort and the session and probe schedule remain open.
Examples found in repository?
25fn main() {
26 let config = ClientConfig {
27 server_addr: "127.0.0.1:2112".to_owned(),
28 duration: Some(RUN_DURATION),
29 interval: Duration::from_millis(200),
30 // A single short attempt keeps this example fast when no server is
31 // reachable; production callers should generally keep the default.
32 open_timeouts: vec![Duration::from_millis(300)],
33 socket_config: irtt_client::SocketConfig {
34 recv_timeout: Some(Duration::from_millis(100)),
35 ..Default::default()
36 },
37 ..Default::default()
38 };
39
40 let mut client = match Client::connect(config) {
41 Ok(client) => client,
42 Err(err) => {
43 eprintln!("failed to prepare client socket: {err}");
44 return;
45 }
46 };
47
48 match client.open() {
49 Ok(outcome) => println!("session opened: {outcome:?}"),
50 Err(err) => {
51 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
52 return;
53 }
54 }
55
56 // The run itself lasts `duration`, but a lost probe isn't classified as
57 // `EchoLoss` until `probe_timeout()` after it was sent; wait at least
58 // that much past the run's own end so a loss near the end can still be
59 // observed and printed before this example gives up and closes.
60 let deadline = Instant::now() + RUN_DURATION + client.probe_timeout();
61 while Instant::now() < deadline && !client.is_run_complete() {
62 // send_probe is caller-paced: only call it once its own schedule
63 // says a probe is due, rather than as fast as the loop spins.
64 if client
65 .next_send_deadline()
66 .is_some_and(|send_at| Instant::now() >= send_at)
67 {
68 // A failed send retains its prepared probe without advancing the
69 // schedule, so a persistent error (e.g. the interface going
70 // down) would otherwise make this loop retry silently until the
71 // safety deadline instead of reporting it.
72 match client.send_probe() {
73 Ok(events) => events.iter().for_each(print_event),
74 Err(err) => {
75 eprintln!("send failed: {err}");
76 break;
77 }
78 }
79 }
80 match client.recv_once() {
81 Ok(events) => events.iter().for_each(print_event),
82 Err(err) => {
83 eprintln!("receive failed: {err}");
84 break;
85 }
86 }
87 match client.poll_timeouts() {
88 Ok(events) => events.iter().for_each(print_event),
89 Err(err) => {
90 eprintln!("timeout polling failed: {err}");
91 break;
92 }
93 }
94 }
95
96 match client.close() {
97 Ok(events) => events.iter().for_each(print_event),
98 Err(err) => eprintln!("close failed: {err}"),
99 }
100}Sourcepub fn next_send_deadline(&self) -> Option<Instant>
pub fn next_send_deadline(&self) -> Option<Instant>
Return the monotonic deadline for the next probe send, if another probe is scheduled.
Examples found in repository?
25fn main() {
26 let config = ClientConfig {
27 server_addr: "127.0.0.1:2112".to_owned(),
28 duration: Some(RUN_DURATION),
29 interval: Duration::from_millis(200),
30 // A single short attempt keeps this example fast when no server is
31 // reachable; production callers should generally keep the default.
32 open_timeouts: vec![Duration::from_millis(300)],
33 socket_config: irtt_client::SocketConfig {
34 recv_timeout: Some(Duration::from_millis(100)),
35 ..Default::default()
36 },
37 ..Default::default()
38 };
39
40 let mut client = match Client::connect(config) {
41 Ok(client) => client,
42 Err(err) => {
43 eprintln!("failed to prepare client socket: {err}");
44 return;
45 }
46 };
47
48 match client.open() {
49 Ok(outcome) => println!("session opened: {outcome:?}"),
50 Err(err) => {
51 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
52 return;
53 }
54 }
55
56 // The run itself lasts `duration`, but a lost probe isn't classified as
57 // `EchoLoss` until `probe_timeout()` after it was sent; wait at least
58 // that much past the run's own end so a loss near the end can still be
59 // observed and printed before this example gives up and closes.
60 let deadline = Instant::now() + RUN_DURATION + client.probe_timeout();
61 while Instant::now() < deadline && !client.is_run_complete() {
62 // send_probe is caller-paced: only call it once its own schedule
63 // says a probe is due, rather than as fast as the loop spins.
64 if client
65 .next_send_deadline()
66 .is_some_and(|send_at| Instant::now() >= send_at)
67 {
68 // A failed send retains its prepared probe without advancing the
69 // schedule, so a persistent error (e.g. the interface going
70 // down) would otherwise make this loop retry silently until the
71 // safety deadline instead of reporting it.
72 match client.send_probe() {
73 Ok(events) => events.iter().for_each(print_event),
74 Err(err) => {
75 eprintln!("send failed: {err}");
76 break;
77 }
78 }
79 }
80 match client.recv_once() {
81 Ok(events) => events.iter().for_each(print_event),
82 Err(err) => {
83 eprintln!("receive failed: {err}");
84 break;
85 }
86 }
87 match client.poll_timeouts() {
88 Ok(events) => events.iter().for_each(print_event),
89 Err(err) => {
90 eprintln!("timeout polling failed: {err}");
91 break;
92 }
93 }
94 }
95
96 match client.close() {
97 Ok(events) => events.iter().for_each(print_event),
98 Err(err) => eprintln!("close failed: {err}"),
99 }
100}Sourcepub fn probe_timeout(&self) -> Duration
pub fn probe_timeout(&self) -> Duration
Return the local timeout used to classify pending probes as lost.
Examples found in repository?
25fn main() {
26 let config = ClientConfig {
27 server_addr: "127.0.0.1:2112".to_owned(),
28 duration: Some(RUN_DURATION),
29 interval: Duration::from_millis(200),
30 // A single short attempt keeps this example fast when no server is
31 // reachable; production callers should generally keep the default.
32 open_timeouts: vec![Duration::from_millis(300)],
33 socket_config: irtt_client::SocketConfig {
34 recv_timeout: Some(Duration::from_millis(100)),
35 ..Default::default()
36 },
37 ..Default::default()
38 };
39
40 let mut client = match Client::connect(config) {
41 Ok(client) => client,
42 Err(err) => {
43 eprintln!("failed to prepare client socket: {err}");
44 return;
45 }
46 };
47
48 match client.open() {
49 Ok(outcome) => println!("session opened: {outcome:?}"),
50 Err(err) => {
51 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
52 return;
53 }
54 }
55
56 // The run itself lasts `duration`, but a lost probe isn't classified as
57 // `EchoLoss` until `probe_timeout()` after it was sent; wait at least
58 // that much past the run's own end so a loss near the end can still be
59 // observed and printed before this example gives up and closes.
60 let deadline = Instant::now() + RUN_DURATION + client.probe_timeout();
61 while Instant::now() < deadline && !client.is_run_complete() {
62 // send_probe is caller-paced: only call it once its own schedule
63 // says a probe is due, rather than as fast as the loop spins.
64 if client
65 .next_send_deadline()
66 .is_some_and(|send_at| Instant::now() >= send_at)
67 {
68 // A failed send retains its prepared probe without advancing the
69 // schedule, so a persistent error (e.g. the interface going
70 // down) would otherwise make this loop retry silently until the
71 // safety deadline instead of reporting it.
72 match client.send_probe() {
73 Ok(events) => events.iter().for_each(print_event),
74 Err(err) => {
75 eprintln!("send failed: {err}");
76 break;
77 }
78 }
79 }
80 match client.recv_once() {
81 Ok(events) => events.iter().for_each(print_event),
82 Err(err) => {
83 eprintln!("receive failed: {err}");
84 break;
85 }
86 }
87 match client.poll_timeouts() {
88 Ok(events) => events.iter().for_each(print_event),
89 Err(err) => {
90 eprintln!("timeout polling failed: {err}");
91 break;
92 }
93 }
94 }
95
96 match client.close() {
97 Ok(events) => events.iter().for_each(print_event),
98 Err(err) => eprintln!("close failed: {err}"),
99 }
100}Sourcepub fn send_probe(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub fn send_probe(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Send one echo probe if the negotiated run is still active.
Returns an EchoSent event when a probe is sent. Returns an empty event
list when the run duration has elapsed and no further probe should be
sent.
Examples found in repository?
25fn main() {
26 let config = ClientConfig {
27 server_addr: "127.0.0.1:2112".to_owned(),
28 duration: Some(RUN_DURATION),
29 interval: Duration::from_millis(200),
30 // A single short attempt keeps this example fast when no server is
31 // reachable; production callers should generally keep the default.
32 open_timeouts: vec![Duration::from_millis(300)],
33 socket_config: irtt_client::SocketConfig {
34 recv_timeout: Some(Duration::from_millis(100)),
35 ..Default::default()
36 },
37 ..Default::default()
38 };
39
40 let mut client = match Client::connect(config) {
41 Ok(client) => client,
42 Err(err) => {
43 eprintln!("failed to prepare client socket: {err}");
44 return;
45 }
46 };
47
48 match client.open() {
49 Ok(outcome) => println!("session opened: {outcome:?}"),
50 Err(err) => {
51 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
52 return;
53 }
54 }
55
56 // The run itself lasts `duration`, but a lost probe isn't classified as
57 // `EchoLoss` until `probe_timeout()` after it was sent; wait at least
58 // that much past the run's own end so a loss near the end can still be
59 // observed and printed before this example gives up and closes.
60 let deadline = Instant::now() + RUN_DURATION + client.probe_timeout();
61 while Instant::now() < deadline && !client.is_run_complete() {
62 // send_probe is caller-paced: only call it once its own schedule
63 // says a probe is due, rather than as fast as the loop spins.
64 if client
65 .next_send_deadline()
66 .is_some_and(|send_at| Instant::now() >= send_at)
67 {
68 // A failed send retains its prepared probe without advancing the
69 // schedule, so a persistent error (e.g. the interface going
70 // down) would otherwise make this loop retry silently until the
71 // safety deadline instead of reporting it.
72 match client.send_probe() {
73 Ok(events) => events.iter().for_each(print_event),
74 Err(err) => {
75 eprintln!("send failed: {err}");
76 break;
77 }
78 }
79 }
80 match client.recv_once() {
81 Ok(events) => events.iter().for_each(print_event),
82 Err(err) => {
83 eprintln!("receive failed: {err}");
84 break;
85 }
86 }
87 match client.poll_timeouts() {
88 Ok(events) => events.iter().for_each(print_event),
89 Err(err) => {
90 eprintln!("timeout polling failed: {err}");
91 break;
92 }
93 }
94 }
95
96 match client.close() {
97 Ok(events) => events.iter().for_each(print_event),
98 Err(err) => eprintln!("close failed: {err}"),
99 }
100}Sourcepub fn recv_once(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub fn recv_once(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Receive and classify at most one datagram from the socket.
Returns an empty event list when the socket read would block or times out. Malformed or unrelated datagrams are reported as warning events.
Examples found in repository?
25fn main() {
26 let config = ClientConfig {
27 server_addr: "127.0.0.1:2112".to_owned(),
28 duration: Some(RUN_DURATION),
29 interval: Duration::from_millis(200),
30 // A single short attempt keeps this example fast when no server is
31 // reachable; production callers should generally keep the default.
32 open_timeouts: vec![Duration::from_millis(300)],
33 socket_config: irtt_client::SocketConfig {
34 recv_timeout: Some(Duration::from_millis(100)),
35 ..Default::default()
36 },
37 ..Default::default()
38 };
39
40 let mut client = match Client::connect(config) {
41 Ok(client) => client,
42 Err(err) => {
43 eprintln!("failed to prepare client socket: {err}");
44 return;
45 }
46 };
47
48 match client.open() {
49 Ok(outcome) => println!("session opened: {outcome:?}"),
50 Err(err) => {
51 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
52 return;
53 }
54 }
55
56 // The run itself lasts `duration`, but a lost probe isn't classified as
57 // `EchoLoss` until `probe_timeout()` after it was sent; wait at least
58 // that much past the run's own end so a loss near the end can still be
59 // observed and printed before this example gives up and closes.
60 let deadline = Instant::now() + RUN_DURATION + client.probe_timeout();
61 while Instant::now() < deadline && !client.is_run_complete() {
62 // send_probe is caller-paced: only call it once its own schedule
63 // says a probe is due, rather than as fast as the loop spins.
64 if client
65 .next_send_deadline()
66 .is_some_and(|send_at| Instant::now() >= send_at)
67 {
68 // A failed send retains its prepared probe without advancing the
69 // schedule, so a persistent error (e.g. the interface going
70 // down) would otherwise make this loop retry silently until the
71 // safety deadline instead of reporting it.
72 match client.send_probe() {
73 Ok(events) => events.iter().for_each(print_event),
74 Err(err) => {
75 eprintln!("send failed: {err}");
76 break;
77 }
78 }
79 }
80 match client.recv_once() {
81 Ok(events) => events.iter().for_each(print_event),
82 Err(err) => {
83 eprintln!("receive failed: {err}");
84 break;
85 }
86 }
87 match client.poll_timeouts() {
88 Ok(events) => events.iter().for_each(print_event),
89 Err(err) => {
90 eprintln!("timeout polling failed: {err}");
91 break;
92 }
93 }
94 }
95
96 match client.close() {
97 Ok(events) => events.iter().for_each(print_event),
98 Err(err) => eprintln!("close failed: {err}"),
99 }
100}Sourcepub fn recv_available(
&mut self,
budget: RecvBudget,
) -> Result<Vec<ClientEvent>, ClientError>
pub fn recv_available( &mut self, budget: RecvBudget, ) -> Result<Vec<ClientEvent>, ClientError>
Receive and classify datagrams until a receive produces no events or the receive budget is exhausted.
Sourcepub fn poll_timeouts(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub fn poll_timeouts(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Polls for probes that have timed out as of the current monotonic time.
Examples found in repository?
25fn main() {
26 let config = ClientConfig {
27 server_addr: "127.0.0.1:2112".to_owned(),
28 duration: Some(RUN_DURATION),
29 interval: Duration::from_millis(200),
30 // A single short attempt keeps this example fast when no server is
31 // reachable; production callers should generally keep the default.
32 open_timeouts: vec![Duration::from_millis(300)],
33 socket_config: irtt_client::SocketConfig {
34 recv_timeout: Some(Duration::from_millis(100)),
35 ..Default::default()
36 },
37 ..Default::default()
38 };
39
40 let mut client = match Client::connect(config) {
41 Ok(client) => client,
42 Err(err) => {
43 eprintln!("failed to prepare client socket: {err}");
44 return;
45 }
46 };
47
48 match client.open() {
49 Ok(outcome) => println!("session opened: {outcome:?}"),
50 Err(err) => {
51 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
52 return;
53 }
54 }
55
56 // The run itself lasts `duration`, but a lost probe isn't classified as
57 // `EchoLoss` until `probe_timeout()` after it was sent; wait at least
58 // that much past the run's own end so a loss near the end can still be
59 // observed and printed before this example gives up and closes.
60 let deadline = Instant::now() + RUN_DURATION + client.probe_timeout();
61 while Instant::now() < deadline && !client.is_run_complete() {
62 // send_probe is caller-paced: only call it once its own schedule
63 // says a probe is due, rather than as fast as the loop spins.
64 if client
65 .next_send_deadline()
66 .is_some_and(|send_at| Instant::now() >= send_at)
67 {
68 // A failed send retains its prepared probe without advancing the
69 // schedule, so a persistent error (e.g. the interface going
70 // down) would otherwise make this loop retry silently until the
71 // safety deadline instead of reporting it.
72 match client.send_probe() {
73 Ok(events) => events.iter().for_each(print_event),
74 Err(err) => {
75 eprintln!("send failed: {err}");
76 break;
77 }
78 }
79 }
80 match client.recv_once() {
81 Ok(events) => events.iter().for_each(print_event),
82 Err(err) => {
83 eprintln!("receive failed: {err}");
84 break;
85 }
86 }
87 match client.poll_timeouts() {
88 Ok(events) => events.iter().for_each(print_event),
89 Err(err) => {
90 eprintln!("timeout polling failed: {err}");
91 break;
92 }
93 }
94 }
95
96 match client.close() {
97 Ok(events) => events.iter().for_each(print_event),
98 Err(err) => eprintln!("close failed: {err}"),
99 }
100}Sourcepub fn poll_timeouts_at(
&mut self,
now: Instant,
) -> Result<Vec<ClientEvent>, ClientError>
pub fn poll_timeouts_at( &mut self, now: Instant, ) -> Result<Vec<ClientEvent>, ClientError>
Polls for probes that have timed out as of now.
This is useful for callers that drive Client from their own event loop and
want timeout decisions to use the same sampled Instant as their scheduling
logic.
now is monotonic time only; wall-clock time is not used for timeout expiry.
Sourcepub fn is_run_complete(&self) -> bool
pub fn is_run_complete(&self) -> bool
Return whether the current run has completed.
A normal run is complete once no more probes will be sent and all pending probes have either replied or timed out. No-test and closed sessions are also considered complete.
Examples found in repository?
25fn main() {
26 let config = ClientConfig {
27 server_addr: "127.0.0.1:2112".to_owned(),
28 duration: Some(RUN_DURATION),
29 interval: Duration::from_millis(200),
30 // A single short attempt keeps this example fast when no server is
31 // reachable; production callers should generally keep the default.
32 open_timeouts: vec![Duration::from_millis(300)],
33 socket_config: irtt_client::SocketConfig {
34 recv_timeout: Some(Duration::from_millis(100)),
35 ..Default::default()
36 },
37 ..Default::default()
38 };
39
40 let mut client = match Client::connect(config) {
41 Ok(client) => client,
42 Err(err) => {
43 eprintln!("failed to prepare client socket: {err}");
44 return;
45 }
46 };
47
48 match client.open() {
49 Ok(outcome) => println!("session opened: {outcome:?}"),
50 Err(err) => {
51 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
52 return;
53 }
54 }
55
56 // The run itself lasts `duration`, but a lost probe isn't classified as
57 // `EchoLoss` until `probe_timeout()` after it was sent; wait at least
58 // that much past the run's own end so a loss near the end can still be
59 // observed and printed before this example gives up and closes.
60 let deadline = Instant::now() + RUN_DURATION + client.probe_timeout();
61 while Instant::now() < deadline && !client.is_run_complete() {
62 // send_probe is caller-paced: only call it once its own schedule
63 // says a probe is due, rather than as fast as the loop spins.
64 if client
65 .next_send_deadline()
66 .is_some_and(|send_at| Instant::now() >= send_at)
67 {
68 // A failed send retains its prepared probe without advancing the
69 // schedule, so a persistent error (e.g. the interface going
70 // down) would otherwise make this loop retry silently until the
71 // safety deadline instead of reporting it.
72 match client.send_probe() {
73 Ok(events) => events.iter().for_each(print_event),
74 Err(err) => {
75 eprintln!("send failed: {err}");
76 break;
77 }
78 }
79 }
80 match client.recv_once() {
81 Ok(events) => events.iter().for_each(print_event),
82 Err(err) => {
83 eprintln!("receive failed: {err}");
84 break;
85 }
86 }
87 match client.poll_timeouts() {
88 Ok(events) => events.iter().for_each(print_event),
89 Err(err) => {
90 eprintln!("timeout polling failed: {err}");
91 break;
92 }
93 }
94 }
95
96 match client.close() {
97 Ok(events) => events.iter().for_each(print_event),
98 Err(err) => eprintln!("close failed: {err}"),
99 }
100}Sourcepub fn is_peer_closed(&self) -> bool
pub fn is_peer_closed(&self) -> bool
Return whether the session was closed by a peer close-flagged reply.
Direct operations on a closed client still return
ClientError::AlreadyClosed. This method lets higher-level run loops
avoid treating a successfully observed peer close as a local cleanup
failure.