pub struct AsyncClient { /* private fields */ }Expand description
Low-level Tokio IRTT client for one connected UDP target.
AsyncClient does not construct, own, or store a Tokio runtime. Its async
methods are polled by the caller and require a current Tokio runtime with I/O
and time enabled.
§Example
Use this low-level adapter when the application already owns Tokio and wants to drive the session lifecycle itself. This example type-checks without contacting a server.
use irtt_client::{AsyncClient, ClientConfig};
let mut client = AsyncClient::connect("127.0.0.1:2112", ClientConfig::default()).await?;
let outcome = client.open().await?;
println!("opened: {outcome:?}");
let sent = client.send_probe().await?;
println!("sent: {sent:?}");
let received = client.recv().await?;
println!("received: {received:?}");
let timed_out = client.poll_timeouts()?;
println!("timeouts: {timed_out:?}");
let closed = client.close().await?;
println!("closed: {closed:?}");Implementations§
Source§impl AsyncClient
impl AsyncClient
Sourcepub async fn connect(
endpoint: impl AsRef<str>,
config: ClientConfig,
) -> Result<Self, ClientError>
pub async fn connect( endpoint: impl AsRef<str>, config: ClientConfig, ) -> Result<Self, ClientError>
Resolve the endpoint and construct one connected Tokio UDP socket.
Polling this future without a current Tokio runtime returns
ClientError::NoTokioRuntime. A runtime without enabled I/O or time
drivers is outside this type’s runtime contract.
The endpoint accepts a name or address; an omitted port defaults to 2112. IPv6 literals may be bracketed or unbracketed with the default port. The reusable config contains no endpoint identity.
Examples found in repository?
44async fn run() {
45 let config = ClientConfig {
46 open: OpenPolicy {
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 },
52 request: SessionRequest {
53 duration: Some(Duration::from_secs(2)),
54 interval: Duration::from_millis(200),
55 ..Default::default()
56 },
57 ..Default::default()
58 };
59
60 let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61 Ok(client) => client,
62 Err(err) => {
63 eprintln!("failed to prepare client socket: {err}");
64 return;
65 }
66 };
67
68 let negotiated = match client.open().await {
69 Ok(OpenOutcome::Started(started)) => {
70 println!("session opened: {started:?}");
71 started.negotiation
72 }
73 Ok(OpenOutcome::NoTestCompleted(completed)) => {
74 println!("{completed:?}");
75 return;
76 }
77 Err(err) => {
78 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79 return;
80 }
81 };
82
83 // This loop owns cadence and duration. The transport sends whenever called.
84 let interval = negotiated.accepted.interval;
85 let start = Instant::now();
86 let end = start
87 + negotiated
88 .accepted
89 .duration
90 .expect("example requests a finite run");
91 let deadline = end + client.probe_timeout();
92 let mut next_send = start;
93 while Instant::now() < deadline && !client.is_peer_closed() {
94 if Instant::now() >= end && !client.has_pending_probes() {
95 break;
96 }
97 // Bound the receive wait by the next send/timeout deadline, rather
98 // than awaiting recv() unconditionally: a lost or rate-limited reply
99 // must not stall pacing or timeout classification indefinitely.
100 let wake_at = (next_send < end)
101 .then_some(next_send)
102 .into_iter()
103 .chain(client.next_probe_timeout_deadline())
104 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105 .min()
106 .unwrap();
107
108 let recv_result = tokio::select! {
109 events = client.recv() => Some(events),
110 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111 };
112 if let Some(result) = recv_result {
113 match result {
114 Ok(events) => events.iter().for_each(print_event),
115 Err(err) => {
116 eprintln!("receive failed: {err}");
117 break;
118 }
119 }
120 }
121
122 if Instant::now() < end && Instant::now() >= next_send {
123 match client.send_probe().await {
124 Ok(receipt) => {
125 print_event(&receipt.into());
126 while next_send <= Instant::now() {
127 next_send += interval;
128 }
129 }
130 Err(err) => {
131 if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132 print_event(&(*receipt).into());
133 }
134 eprintln!("send failed: {err}");
135 break;
136 }
137 }
138 }
139 match client.poll_timeouts() {
140 Ok(events) => events.iter().for_each(print_event),
141 Err(err) => {
142 eprintln!("timeout polling failed: {err}");
143 break;
144 }
145 }
146 }
147
148 if client.is_peer_closed() {
149 return;
150 }
151 match client.close().await {
152 Ok(events) => events.iter().for_each(print_event),
153 Err(err) => eprintln!("close failed: {err}"),
154 }
155}Sourcepub async fn open(&mut self) -> Result<OpenOutcome, ClientError>
pub async fn open(&mut self) -> Result<OpenOutcome, ClientError>
Perform the IRTT open exchange.
Each configured attempt uses one absolute deadline for one successful request submission and all replies inspected during that attempt. Malformed, unrelated, wrong-direction, and unauthenticated datagrams are ignored without resending the request.
Dropping this future before trusted acceptance leaves the local machine connected with no negotiated adapter state. If it is dropped during best-effort post-token cleanup, cleanup may remain incomplete because this low-level client never detaches cleanup work.
Examples found in repository?
44async fn run() {
45 let config = ClientConfig {
46 open: OpenPolicy {
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 },
52 request: SessionRequest {
53 duration: Some(Duration::from_secs(2)),
54 interval: Duration::from_millis(200),
55 ..Default::default()
56 },
57 ..Default::default()
58 };
59
60 let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61 Ok(client) => client,
62 Err(err) => {
63 eprintln!("failed to prepare client socket: {err}");
64 return;
65 }
66 };
67
68 let negotiated = match client.open().await {
69 Ok(OpenOutcome::Started(started)) => {
70 println!("session opened: {started:?}");
71 started.negotiation
72 }
73 Ok(OpenOutcome::NoTestCompleted(completed)) => {
74 println!("{completed:?}");
75 return;
76 }
77 Err(err) => {
78 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79 return;
80 }
81 };
82
83 // This loop owns cadence and duration. The transport sends whenever called.
84 let interval = negotiated.accepted.interval;
85 let start = Instant::now();
86 let end = start
87 + negotiated
88 .accepted
89 .duration
90 .expect("example requests a finite run");
91 let deadline = end + client.probe_timeout();
92 let mut next_send = start;
93 while Instant::now() < deadline && !client.is_peer_closed() {
94 if Instant::now() >= end && !client.has_pending_probes() {
95 break;
96 }
97 // Bound the receive wait by the next send/timeout deadline, rather
98 // than awaiting recv() unconditionally: a lost or rate-limited reply
99 // must not stall pacing or timeout classification indefinitely.
100 let wake_at = (next_send < end)
101 .then_some(next_send)
102 .into_iter()
103 .chain(client.next_probe_timeout_deadline())
104 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105 .min()
106 .unwrap();
107
108 let recv_result = tokio::select! {
109 events = client.recv() => Some(events),
110 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111 };
112 if let Some(result) = recv_result {
113 match result {
114 Ok(events) => events.iter().for_each(print_event),
115 Err(err) => {
116 eprintln!("receive failed: {err}");
117 break;
118 }
119 }
120 }
121
122 if Instant::now() < end && Instant::now() >= next_send {
123 match client.send_probe().await {
124 Ok(receipt) => {
125 print_event(&receipt.into());
126 while next_send <= Instant::now() {
127 next_send += interval;
128 }
129 }
130 Err(err) => {
131 if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132 print_event(&(*receipt).into());
133 }
134 eprintln!("send failed: {err}");
135 break;
136 }
137 }
138 }
139 match client.poll_timeouts() {
140 Ok(events) => events.iter().for_each(print_event),
141 Err(err) => {
142 eprintln!("timeout polling failed: {err}");
143 break;
144 }
145 }
146 }
147
148 if client.is_peer_closed() {
149 return;
150 }
151 match client.close().await {
152 Ok(events) => events.iter().for_each(print_event),
153 Err(err) => eprintln!("close failed: {err}"),
154 }
155}Sourcepub async fn send_probe(&mut self) -> Result<SendReceipt, SendProbeError>
pub async fn send_probe(&mut self) -> Result<SendReceipt, SendProbeError>
Send one echo probe now, committing protocol state only after acceptance. The caller owns pacing and run duration.
A prepared packet is retained across readiness false positives,
cancellation, and socket errors so a later call retries the same logical
probe without advancing state before kernel acceptance. Cancellation or
WouldBlock before submission is uncommitted. After acceptance there is
no suspension before infallible commitment, and the prepared probe is
cleared even if post-send processing returns SendProbeError::AfterCommit.
That failure retains the authoritative receipt; earlier failures return
SendProbeError::NotCommitted.
Examples found in repository?
44async fn run() {
45 let config = ClientConfig {
46 open: OpenPolicy {
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 },
52 request: SessionRequest {
53 duration: Some(Duration::from_secs(2)),
54 interval: Duration::from_millis(200),
55 ..Default::default()
56 },
57 ..Default::default()
58 };
59
60 let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61 Ok(client) => client,
62 Err(err) => {
63 eprintln!("failed to prepare client socket: {err}");
64 return;
65 }
66 };
67
68 let negotiated = match client.open().await {
69 Ok(OpenOutcome::Started(started)) => {
70 println!("session opened: {started:?}");
71 started.negotiation
72 }
73 Ok(OpenOutcome::NoTestCompleted(completed)) => {
74 println!("{completed:?}");
75 return;
76 }
77 Err(err) => {
78 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79 return;
80 }
81 };
82
83 // This loop owns cadence and duration. The transport sends whenever called.
84 let interval = negotiated.accepted.interval;
85 let start = Instant::now();
86 let end = start
87 + negotiated
88 .accepted
89 .duration
90 .expect("example requests a finite run");
91 let deadline = end + client.probe_timeout();
92 let mut next_send = start;
93 while Instant::now() < deadline && !client.is_peer_closed() {
94 if Instant::now() >= end && !client.has_pending_probes() {
95 break;
96 }
97 // Bound the receive wait by the next send/timeout deadline, rather
98 // than awaiting recv() unconditionally: a lost or rate-limited reply
99 // must not stall pacing or timeout classification indefinitely.
100 let wake_at = (next_send < end)
101 .then_some(next_send)
102 .into_iter()
103 .chain(client.next_probe_timeout_deadline())
104 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105 .min()
106 .unwrap();
107
108 let recv_result = tokio::select! {
109 events = client.recv() => Some(events),
110 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111 };
112 if let Some(result) = recv_result {
113 match result {
114 Ok(events) => events.iter().for_each(print_event),
115 Err(err) => {
116 eprintln!("receive failed: {err}");
117 break;
118 }
119 }
120 }
121
122 if Instant::now() < end && Instant::now() >= next_send {
123 match client.send_probe().await {
124 Ok(receipt) => {
125 print_event(&receipt.into());
126 while next_send <= Instant::now() {
127 next_send += interval;
128 }
129 }
130 Err(err) => {
131 if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132 print_event(&(*receipt).into());
133 }
134 eprintln!("send failed: {err}");
135 break;
136 }
137 }
138 }
139 match client.poll_timeouts() {
140 Ok(events) => events.iter().for_each(print_event),
141 Err(err) => {
142 eprintln!("timeout polling failed: {err}");
143 break;
144 }
145 }
146 }
147
148 if client.is_peer_closed() {
149 return;
150 }
151 match client.close().await {
152 Ok(events) => events.iter().for_each(print_event),
153 Err(err) => eprintln!("close failed: {err}"),
154 }
155}Sourcepub async fn recv(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub async fn recv(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Await and classify one complete UDP datagram.
State is validated before readiness is awaited. Readiness false positives retry without changing protocol state. Authenticated peer-close events remain authoritative even if best-effort DSCP cleanup fails.
Examples found in repository?
44async fn run() {
45 let config = ClientConfig {
46 open: OpenPolicy {
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 },
52 request: SessionRequest {
53 duration: Some(Duration::from_secs(2)),
54 interval: Duration::from_millis(200),
55 ..Default::default()
56 },
57 ..Default::default()
58 };
59
60 let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61 Ok(client) => client,
62 Err(err) => {
63 eprintln!("failed to prepare client socket: {err}");
64 return;
65 }
66 };
67
68 let negotiated = match client.open().await {
69 Ok(OpenOutcome::Started(started)) => {
70 println!("session opened: {started:?}");
71 started.negotiation
72 }
73 Ok(OpenOutcome::NoTestCompleted(completed)) => {
74 println!("{completed:?}");
75 return;
76 }
77 Err(err) => {
78 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79 return;
80 }
81 };
82
83 // This loop owns cadence and duration. The transport sends whenever called.
84 let interval = negotiated.accepted.interval;
85 let start = Instant::now();
86 let end = start
87 + negotiated
88 .accepted
89 .duration
90 .expect("example requests a finite run");
91 let deadline = end + client.probe_timeout();
92 let mut next_send = start;
93 while Instant::now() < deadline && !client.is_peer_closed() {
94 if Instant::now() >= end && !client.has_pending_probes() {
95 break;
96 }
97 // Bound the receive wait by the next send/timeout deadline, rather
98 // than awaiting recv() unconditionally: a lost or rate-limited reply
99 // must not stall pacing or timeout classification indefinitely.
100 let wake_at = (next_send < end)
101 .then_some(next_send)
102 .into_iter()
103 .chain(client.next_probe_timeout_deadline())
104 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105 .min()
106 .unwrap();
107
108 let recv_result = tokio::select! {
109 events = client.recv() => Some(events),
110 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111 };
112 if let Some(result) = recv_result {
113 match result {
114 Ok(events) => events.iter().for_each(print_event),
115 Err(err) => {
116 eprintln!("receive failed: {err}");
117 break;
118 }
119 }
120 }
121
122 if Instant::now() < end && Instant::now() >= next_send {
123 match client.send_probe().await {
124 Ok(receipt) => {
125 print_event(&receipt.into());
126 while next_send <= Instant::now() {
127 next_send += interval;
128 }
129 }
130 Err(err) => {
131 if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132 print_event(&(*receipt).into());
133 }
134 eprintln!("send failed: {err}");
135 break;
136 }
137 }
138 }
139 match client.poll_timeouts() {
140 Ok(events) => events.iter().for_each(print_event),
141 Err(err) => {
142 eprintln!("timeout polling failed: {err}");
143 break;
144 }
145 }
146 }
147
148 if client.is_peer_closed() {
149 return;
150 }
151 match client.close().await {
152 Ok(events) => events.iter().for_each(print_event),
153 Err(err) => eprintln!("close failed: {err}"),
154 }
155}Sourcepub fn poll_timeouts(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub fn poll_timeouts(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Poll protocol timeouts using a newly captured monotonic timestamp.
Examples found in repository?
44async fn run() {
45 let config = ClientConfig {
46 open: OpenPolicy {
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 },
52 request: SessionRequest {
53 duration: Some(Duration::from_secs(2)),
54 interval: Duration::from_millis(200),
55 ..Default::default()
56 },
57 ..Default::default()
58 };
59
60 let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61 Ok(client) => client,
62 Err(err) => {
63 eprintln!("failed to prepare client socket: {err}");
64 return;
65 }
66 };
67
68 let negotiated = match client.open().await {
69 Ok(OpenOutcome::Started(started)) => {
70 println!("session opened: {started:?}");
71 started.negotiation
72 }
73 Ok(OpenOutcome::NoTestCompleted(completed)) => {
74 println!("{completed:?}");
75 return;
76 }
77 Err(err) => {
78 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79 return;
80 }
81 };
82
83 // This loop owns cadence and duration. The transport sends whenever called.
84 let interval = negotiated.accepted.interval;
85 let start = Instant::now();
86 let end = start
87 + negotiated
88 .accepted
89 .duration
90 .expect("example requests a finite run");
91 let deadline = end + client.probe_timeout();
92 let mut next_send = start;
93 while Instant::now() < deadline && !client.is_peer_closed() {
94 if Instant::now() >= end && !client.has_pending_probes() {
95 break;
96 }
97 // Bound the receive wait by the next send/timeout deadline, rather
98 // than awaiting recv() unconditionally: a lost or rate-limited reply
99 // must not stall pacing or timeout classification indefinitely.
100 let wake_at = (next_send < end)
101 .then_some(next_send)
102 .into_iter()
103 .chain(client.next_probe_timeout_deadline())
104 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105 .min()
106 .unwrap();
107
108 let recv_result = tokio::select! {
109 events = client.recv() => Some(events),
110 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111 };
112 if let Some(result) = recv_result {
113 match result {
114 Ok(events) => events.iter().for_each(print_event),
115 Err(err) => {
116 eprintln!("receive failed: {err}");
117 break;
118 }
119 }
120 }
121
122 if Instant::now() < end && Instant::now() >= next_send {
123 match client.send_probe().await {
124 Ok(receipt) => {
125 print_event(&receipt.into());
126 while next_send <= Instant::now() {
127 next_send += interval;
128 }
129 }
130 Err(err) => {
131 if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132 print_event(&(*receipt).into());
133 }
134 eprintln!("send failed: {err}");
135 break;
136 }
137 }
138 }
139 match client.poll_timeouts() {
140 Ok(events) => events.iter().for_each(print_event),
141 Err(err) => {
142 eprintln!("timeout polling failed: {err}");
143 break;
144 }
145 }
146 }
147
148 if client.is_peer_closed() {
149 return;
150 }
151 match client.close().await {
152 Ok(events) => events.iter().for_each(print_event),
153 Err(err) => eprintln!("close failed: {err}"),
154 }
155}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>
Poll protocol timeouts using the caller’s monotonic timestamp.
Sourcepub async fn close(&mut self) -> Result<Vec<ClientEvent>, ClientError>
pub async fn close(&mut self) -> Result<Vec<ClientEvent>, ClientError>
Send the retained close packet and commit local close exactly once.
DSCP is cleared only after write readiness and immediately before the
nonblocking send attempt. A WouldBlock result restores it before the
next await, so dropping this future at a suspension point cannot leave
an otherwise-open session with cleared DSCP.
Examples found in repository?
44async fn run() {
45 let config = ClientConfig {
46 open: OpenPolicy {
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 },
52 request: SessionRequest {
53 duration: Some(Duration::from_secs(2)),
54 interval: Duration::from_millis(200),
55 ..Default::default()
56 },
57 ..Default::default()
58 };
59
60 let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61 Ok(client) => client,
62 Err(err) => {
63 eprintln!("failed to prepare client socket: {err}");
64 return;
65 }
66 };
67
68 let negotiated = match client.open().await {
69 Ok(OpenOutcome::Started(started)) => {
70 println!("session opened: {started:?}");
71 started.negotiation
72 }
73 Ok(OpenOutcome::NoTestCompleted(completed)) => {
74 println!("{completed:?}");
75 return;
76 }
77 Err(err) => {
78 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79 return;
80 }
81 };
82
83 // This loop owns cadence and duration. The transport sends whenever called.
84 let interval = negotiated.accepted.interval;
85 let start = Instant::now();
86 let end = start
87 + negotiated
88 .accepted
89 .duration
90 .expect("example requests a finite run");
91 let deadline = end + client.probe_timeout();
92 let mut next_send = start;
93 while Instant::now() < deadline && !client.is_peer_closed() {
94 if Instant::now() >= end && !client.has_pending_probes() {
95 break;
96 }
97 // Bound the receive wait by the next send/timeout deadline, rather
98 // than awaiting recv() unconditionally: a lost or rate-limited reply
99 // must not stall pacing or timeout classification indefinitely.
100 let wake_at = (next_send < end)
101 .then_some(next_send)
102 .into_iter()
103 .chain(client.next_probe_timeout_deadline())
104 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105 .min()
106 .unwrap();
107
108 let recv_result = tokio::select! {
109 events = client.recv() => Some(events),
110 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111 };
112 if let Some(result) = recv_result {
113 match result {
114 Ok(events) => events.iter().for_each(print_event),
115 Err(err) => {
116 eprintln!("receive failed: {err}");
117 break;
118 }
119 }
120 }
121
122 if Instant::now() < end && Instant::now() >= next_send {
123 match client.send_probe().await {
124 Ok(receipt) => {
125 print_event(&receipt.into());
126 while next_send <= Instant::now() {
127 next_send += interval;
128 }
129 }
130 Err(err) => {
131 if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132 print_event(&(*receipt).into());
133 }
134 eprintln!("send failed: {err}");
135 break;
136 }
137 }
138 }
139 match client.poll_timeouts() {
140 Ok(events) => events.iter().for_each(print_event),
141 Err(err) => {
142 eprintln!("timeout polling failed: {err}");
143 break;
144 }
145 }
146 }
147
148 if client.is_peer_closed() {
149 return;
150 }
151 match client.close().await {
152 Ok(events) => events.iter().for_each(print_event),
153 Err(err) => eprintln!("close failed: {err}"),
154 }
155}Sourcepub fn probe_timeout(&self) -> Duration
pub fn probe_timeout(&self) -> Duration
Return the configured local probe timeout.
Examples found in repository?
44async fn run() {
45 let config = ClientConfig {
46 open: OpenPolicy {
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 },
52 request: SessionRequest {
53 duration: Some(Duration::from_secs(2)),
54 interval: Duration::from_millis(200),
55 ..Default::default()
56 },
57 ..Default::default()
58 };
59
60 let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61 Ok(client) => client,
62 Err(err) => {
63 eprintln!("failed to prepare client socket: {err}");
64 return;
65 }
66 };
67
68 let negotiated = match client.open().await {
69 Ok(OpenOutcome::Started(started)) => {
70 println!("session opened: {started:?}");
71 started.negotiation
72 }
73 Ok(OpenOutcome::NoTestCompleted(completed)) => {
74 println!("{completed:?}");
75 return;
76 }
77 Err(err) => {
78 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79 return;
80 }
81 };
82
83 // This loop owns cadence and duration. The transport sends whenever called.
84 let interval = negotiated.accepted.interval;
85 let start = Instant::now();
86 let end = start
87 + negotiated
88 .accepted
89 .duration
90 .expect("example requests a finite run");
91 let deadline = end + client.probe_timeout();
92 let mut next_send = start;
93 while Instant::now() < deadline && !client.is_peer_closed() {
94 if Instant::now() >= end && !client.has_pending_probes() {
95 break;
96 }
97 // Bound the receive wait by the next send/timeout deadline, rather
98 // than awaiting recv() unconditionally: a lost or rate-limited reply
99 // must not stall pacing or timeout classification indefinitely.
100 let wake_at = (next_send < end)
101 .then_some(next_send)
102 .into_iter()
103 .chain(client.next_probe_timeout_deadline())
104 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105 .min()
106 .unwrap();
107
108 let recv_result = tokio::select! {
109 events = client.recv() => Some(events),
110 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111 };
112 if let Some(result) = recv_result {
113 match result {
114 Ok(events) => events.iter().for_each(print_event),
115 Err(err) => {
116 eprintln!("receive failed: {err}");
117 break;
118 }
119 }
120 }
121
122 if Instant::now() < end && Instant::now() >= next_send {
123 match client.send_probe().await {
124 Ok(receipt) => {
125 print_event(&receipt.into());
126 while next_send <= Instant::now() {
127 next_send += interval;
128 }
129 }
130 Err(err) => {
131 if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132 print_event(&(*receipt).into());
133 }
134 eprintln!("send failed: {err}");
135 break;
136 }
137 }
138 }
139 match client.poll_timeouts() {
140 Ok(events) => events.iter().for_each(print_event),
141 Err(err) => {
142 eprintln!("timeout polling failed: {err}");
143 break;
144 }
145 }
146 }
147
148 if client.is_peer_closed() {
149 return;
150 }
151 match client.close().await {
152 Ok(events) => events.iter().for_each(print_event),
153 Err(err) => eprintln!("close failed: {err}"),
154 }
155}Sourcepub fn negotiation(&self) -> Option<&NegotiationResult>
pub fn negotiation(&self) -> Option<&NegotiationResult>
Accepted semantics and exact peer parameters of the currently live session.
Returns None before Open, after Close, and after no-test completion.
Sourcepub fn has_pending_probes(&self) -> bool
pub fn has_pending_probes(&self) -> bool
Whether any sent probes still await a reply or timeout.
Examples found in repository?
44async fn run() {
45 let config = ClientConfig {
46 open: OpenPolicy {
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 },
52 request: SessionRequest {
53 duration: Some(Duration::from_secs(2)),
54 interval: Duration::from_millis(200),
55 ..Default::default()
56 },
57 ..Default::default()
58 };
59
60 let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61 Ok(client) => client,
62 Err(err) => {
63 eprintln!("failed to prepare client socket: {err}");
64 return;
65 }
66 };
67
68 let negotiated = match client.open().await {
69 Ok(OpenOutcome::Started(started)) => {
70 println!("session opened: {started:?}");
71 started.negotiation
72 }
73 Ok(OpenOutcome::NoTestCompleted(completed)) => {
74 println!("{completed:?}");
75 return;
76 }
77 Err(err) => {
78 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79 return;
80 }
81 };
82
83 // This loop owns cadence and duration. The transport sends whenever called.
84 let interval = negotiated.accepted.interval;
85 let start = Instant::now();
86 let end = start
87 + negotiated
88 .accepted
89 .duration
90 .expect("example requests a finite run");
91 let deadline = end + client.probe_timeout();
92 let mut next_send = start;
93 while Instant::now() < deadline && !client.is_peer_closed() {
94 if Instant::now() >= end && !client.has_pending_probes() {
95 break;
96 }
97 // Bound the receive wait by the next send/timeout deadline, rather
98 // than awaiting recv() unconditionally: a lost or rate-limited reply
99 // must not stall pacing or timeout classification indefinitely.
100 let wake_at = (next_send < end)
101 .then_some(next_send)
102 .into_iter()
103 .chain(client.next_probe_timeout_deadline())
104 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105 .min()
106 .unwrap();
107
108 let recv_result = tokio::select! {
109 events = client.recv() => Some(events),
110 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111 };
112 if let Some(result) = recv_result {
113 match result {
114 Ok(events) => events.iter().for_each(print_event),
115 Err(err) => {
116 eprintln!("receive failed: {err}");
117 break;
118 }
119 }
120 }
121
122 if Instant::now() < end && Instant::now() >= next_send {
123 match client.send_probe().await {
124 Ok(receipt) => {
125 print_event(&receipt.into());
126 while next_send <= Instant::now() {
127 next_send += interval;
128 }
129 }
130 Err(err) => {
131 if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132 print_event(&(*receipt).into());
133 }
134 eprintln!("send failed: {err}");
135 break;
136 }
137 }
138 }
139 match client.poll_timeouts() {
140 Ok(events) => events.iter().for_each(print_event),
141 Err(err) => {
142 eprintln!("timeout polling failed: {err}");
143 break;
144 }
145 }
146 }
147
148 if client.is_peer_closed() {
149 return;
150 }
151 match client.close().await {
152 Ok(events) => events.iter().for_each(print_event),
153 Err(err) => eprintln!("close failed: {err}"),
154 }
155}Sourcepub fn is_peer_closed(&self) -> bool
pub fn is_peer_closed(&self) -> bool
Return whether an authenticated peer close ended the session.
Examples found in repository?
44async fn run() {
45 let config = ClientConfig {
46 open: OpenPolicy {
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 },
52 request: SessionRequest {
53 duration: Some(Duration::from_secs(2)),
54 interval: Duration::from_millis(200),
55 ..Default::default()
56 },
57 ..Default::default()
58 };
59
60 let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61 Ok(client) => client,
62 Err(err) => {
63 eprintln!("failed to prepare client socket: {err}");
64 return;
65 }
66 };
67
68 let negotiated = match client.open().await {
69 Ok(OpenOutcome::Started(started)) => {
70 println!("session opened: {started:?}");
71 started.negotiation
72 }
73 Ok(OpenOutcome::NoTestCompleted(completed)) => {
74 println!("{completed:?}");
75 return;
76 }
77 Err(err) => {
78 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79 return;
80 }
81 };
82
83 // This loop owns cadence and duration. The transport sends whenever called.
84 let interval = negotiated.accepted.interval;
85 let start = Instant::now();
86 let end = start
87 + negotiated
88 .accepted
89 .duration
90 .expect("example requests a finite run");
91 let deadline = end + client.probe_timeout();
92 let mut next_send = start;
93 while Instant::now() < deadline && !client.is_peer_closed() {
94 if Instant::now() >= end && !client.has_pending_probes() {
95 break;
96 }
97 // Bound the receive wait by the next send/timeout deadline, rather
98 // than awaiting recv() unconditionally: a lost or rate-limited reply
99 // must not stall pacing or timeout classification indefinitely.
100 let wake_at = (next_send < end)
101 .then_some(next_send)
102 .into_iter()
103 .chain(client.next_probe_timeout_deadline())
104 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105 .min()
106 .unwrap();
107
108 let recv_result = tokio::select! {
109 events = client.recv() => Some(events),
110 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111 };
112 if let Some(result) = recv_result {
113 match result {
114 Ok(events) => events.iter().for_each(print_event),
115 Err(err) => {
116 eprintln!("receive failed: {err}");
117 break;
118 }
119 }
120 }
121
122 if Instant::now() < end && Instant::now() >= next_send {
123 match client.send_probe().await {
124 Ok(receipt) => {
125 print_event(&receipt.into());
126 while next_send <= Instant::now() {
127 next_send += interval;
128 }
129 }
130 Err(err) => {
131 if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132 print_event(&(*receipt).into());
133 }
134 eprintln!("send failed: {err}");
135 break;
136 }
137 }
138 }
139 match client.poll_timeouts() {
140 Ok(events) => events.iter().for_each(print_event),
141 Err(err) => {
142 eprintln!("timeout polling failed: {err}");
143 break;
144 }
145 }
146 }
147
148 if client.is_peer_closed() {
149 return;
150 }
151 match client.close().await {
152 Ok(events) => events.iter().for_each(print_event),
153 Err(err) => eprintln!("close failed: {err}"),
154 }
155}Sourcepub fn next_probe_timeout_deadline(&self) -> Option<Instant>
pub fn next_probe_timeout_deadline(&self) -> Option<Instant>
Earliest timeout deadline among probes still awaiting a reply.
Examples found in repository?
44async fn run() {
45 let config = ClientConfig {
46 open: OpenPolicy {
47 // A single short attempt keeps this example fast when no server is
48 // reachable; production callers should generally keep the default.
49 timeouts: vec![Duration::from_millis(300)],
50 ..Default::default()
51 },
52 request: SessionRequest {
53 duration: Some(Duration::from_secs(2)),
54 interval: Duration::from_millis(200),
55 ..Default::default()
56 },
57 ..Default::default()
58 };
59
60 let mut client = match AsyncClient::connect("127.0.0.1:2112", config).await {
61 Ok(client) => client,
62 Err(err) => {
63 eprintln!("failed to prepare client socket: {err}");
64 return;
65 }
66 };
67
68 let negotiated = match client.open().await {
69 Ok(OpenOutcome::Started(started)) => {
70 println!("session opened: {started:?}");
71 started.negotiation
72 }
73 Ok(OpenOutcome::NoTestCompleted(completed)) => {
74 println!("{completed:?}");
75 return;
76 }
77 Err(err) => {
78 eprintln!("open failed (is a server running at 127.0.0.1:2112?): {err}");
79 return;
80 }
81 };
82
83 // This loop owns cadence and duration. The transport sends whenever called.
84 let interval = negotiated.accepted.interval;
85 let start = Instant::now();
86 let end = start
87 + negotiated
88 .accepted
89 .duration
90 .expect("example requests a finite run");
91 let deadline = end + client.probe_timeout();
92 let mut next_send = start;
93 while Instant::now() < deadline && !client.is_peer_closed() {
94 if Instant::now() >= end && !client.has_pending_probes() {
95 break;
96 }
97 // Bound the receive wait by the next send/timeout deadline, rather
98 // than awaiting recv() unconditionally: a lost or rate-limited reply
99 // must not stall pacing or timeout classification indefinitely.
100 let wake_at = (next_send < end)
101 .then_some(next_send)
102 .into_iter()
103 .chain(client.next_probe_timeout_deadline())
104 .chain(std::iter::once(Instant::now() + TIMEOUT_POLL_INTERVAL))
105 .min()
106 .unwrap();
107
108 let recv_result = tokio::select! {
109 events = client.recv() => Some(events),
110 () = tokio::time::sleep_until(tokio::time::Instant::from_std(wake_at)) => None,
111 };
112 if let Some(result) = recv_result {
113 match result {
114 Ok(events) => events.iter().for_each(print_event),
115 Err(err) => {
116 eprintln!("receive failed: {err}");
117 break;
118 }
119 }
120 }
121
122 if Instant::now() < end && Instant::now() >= next_send {
123 match client.send_probe().await {
124 Ok(receipt) => {
125 print_event(&receipt.into());
126 while next_send <= Instant::now() {
127 next_send += interval;
128 }
129 }
130 Err(err) => {
131 if let irtt_client::SendProbeError::AfterCommit { receipt, .. } = &err {
132 print_event(&(*receipt).into());
133 }
134 eprintln!("send failed: {err}");
135 break;
136 }
137 }
138 }
139 match client.poll_timeouts() {
140 Ok(events) => events.iter().for_each(print_event),
141 Err(err) => {
142 eprintln!("timeout polling failed: {err}");
143 break;
144 }
145 }
146 }
147
148 if client.is_peer_closed() {
149 return;
150 }
151 match client.close().await {
152 Ok(events) => events.iter().for_each(print_event),
153 Err(err) => eprintln!("close failed: {err}"),
154 }
155}