Skip to content

Commit 177e183

Browse files
committed
feat(server): expose NetworkAutoDetect RTT via a shared handle
The server measures network RTT in its AutoDetectManager, but after run() takes ownership a backend can no longer call rtt_snapshot(): the measurement is stranded in the running server. Mirror the existing display_suppressed handle so the value can be shared out. Add an Arc<AtomicU32> holding the latest measured RTT in milliseconds (u32::MAX until the first measurement), a with_autodetect_rtt_handle builder injector, and an autodetect_rtt_handle() getter. The AutoDetectRsp handler stores the measured rtt_ms into it. A display backend can then read a fresh, frame-traffic-independent network RTT for flow control without polling the server object. Drop the cfg_attr(egfx) gate on the new() too_many_arguments expect: the added parameter makes the non-egfx parameter count 8 (was 7), so the lint now fires in both feature configs and the expect is satisfied unconditionally.
1 parent 3905d17 commit 177e183

3 files changed

Lines changed: 79 additions & 11 deletions

File tree

crates/ironrdp-server/src/builder.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use core::net::SocketAddr;
2-
use core::sync::atomic::AtomicBool;
2+
use core::sync::atomic::{AtomicBool, AtomicU32};
33
use std::sync::Arc;
44

55
use anyhow::Result;
@@ -40,6 +40,7 @@ pub struct BuilderDone {
4040
#[cfg(feature = "egfx")]
4141
gfx_factory: Option<Box<dyn GfxServerFactory>>,
4242
display_suppressed: Option<Arc<AtomicBool>>,
43+
autodetect_rtt: Option<Arc<AtomicU32>>,
4344
}
4445

4546
pub struct RdpServerBuilder<State> {
@@ -138,6 +139,7 @@ impl RdpServerBuilder<WantsDisplay> {
138139
#[cfg(feature = "egfx")]
139140
gfx_factory: None,
140141
display_suppressed: None,
142+
autodetect_rtt: None,
141143
},
142144
}
143145
}
@@ -157,6 +159,7 @@ impl RdpServerBuilder<WantsDisplay> {
157159
#[cfg(feature = "egfx")]
158160
gfx_factory: None,
159161
display_suppressed: None,
162+
autodetect_rtt: None,
160163
},
161164
}
162165
}
@@ -223,6 +226,17 @@ impl RdpServerBuilder<BuilderDone> {
223226
self
224227
}
225228

229+
/// Inject a shared NetworkAutoDetect RTT handle (milliseconds, `u32::MAX`
230+
/// until the first measurement). The server writes the latest measured RTT
231+
/// to the same instance the backend reads. When not called, the server
232+
/// allocates its own (still readable via
233+
/// [`RdpServer::autodetect_rtt_handle`]). The value stays `u32::MAX` unless
234+
/// auto-detect is enabled via [`RdpServer::enable_autodetect`].
235+
pub fn with_autodetect_rtt_handle(mut self, handle: Arc<AtomicU32>) -> Self {
236+
self.state.autodetect_rtt = Some(handle);
237+
self
238+
}
239+
226240
pub fn build(self) -> RdpServer {
227241
RdpServer::new(
228242
RdpServerOptions {
@@ -239,6 +253,7 @@ impl RdpServerBuilder<BuilderDone> {
239253
#[cfg(feature = "egfx")]
240254
self.state.gfx_factory,
241255
self.state.display_suppressed,
256+
self.state.autodetect_rtt,
242257
)
243258
}
244259
}

crates/ironrdp-server/src/server.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use core::net::SocketAddr;
2-
use core::sync::atomic::{AtomicBool, Ordering};
2+
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
33
use core::time::Duration;
44
use std::rc::Rc;
55
use std::sync::Arc;
@@ -304,6 +304,14 @@ pub struct RdpServer {
304304
/// and locks up its input dispatch for seconds on refocus while it
305305
/// chews through the backlog.
306306
display_suppressed: Arc<AtomicBool>,
307+
308+
/// Latest NetworkAutoDetect round-trip time in milliseconds, or `u32::MAX`
309+
/// until the first measurement (and while auto-detect is disabled). Updated
310+
/// on each RTT Measure Response when auto-detect is enabled (see
311+
/// [`Self::enable_autodetect`]). Exposed via [`Self::autodetect_rtt_handle`]
312+
/// so display backends can read a fresh, frame-traffic-independent network
313+
/// RTT for flow control.
314+
autodetect_rtt: Arc<AtomicU32>,
307315
}
308316

309317
#[derive(Debug)]
@@ -338,15 +346,9 @@ enum RunState {
338346
}
339347

340348
impl RdpServer {
341-
// The lint only fires with the `egfx` feature on (8 args including
342-
// `gfx_factory`); without it the parameter count is 7 and the lint
343-
// is satisfied. `cfg_attr` keeps `#[expect]` strict in both modes.
344-
#[cfg_attr(
345-
feature = "egfx",
346-
expect(
347-
clippy::too_many_arguments,
348-
reason = "called via the builder; positional parameters are an internal detail"
349-
)
349+
#[expect(
350+
clippy::too_many_arguments,
351+
reason = "called via the builder; positional parameters are an internal detail"
350352
)]
351353
pub fn new(
352354
opts: RdpServerOptions,
@@ -357,6 +359,7 @@ impl RdpServer {
357359
connection_handler: Option<Box<dyn ConnectionHandler>>,
358360
#[cfg(feature = "egfx")] mut gfx_factory: Option<Box<dyn GfxServerFactory>>,
359361
display_suppressed: Option<Arc<AtomicBool>>,
362+
autodetect_rtt: Option<Arc<AtomicU32>>,
360363
) -> Self {
361364
let (ev_sender, ev_receiver) = ServerEvent::create_channel();
362365
if let Some(cliprdr) = cliprdr_factory.as_mut() {
@@ -388,6 +391,7 @@ impl RdpServer {
388391
autodetect: None,
389392
connection_handler,
390393
display_suppressed: display_suppressed.unwrap_or_else(|| Arc::new(AtomicBool::new(false))),
394+
autodetect_rtt: autodetect_rtt.unwrap_or_else(|| Arc::new(AtomicU32::new(u32::MAX))),
391395
}
392396
}
393397

@@ -432,6 +436,16 @@ impl RdpServer {
432436
Arc::clone(&self.display_suppressed)
433437
}
434438

439+
/// Returns a handle to the latest NetworkAutoDetect RTT in milliseconds
440+
/// (`u32::MAX` until the first measurement, and while auto-detect is
441+
/// disabled). The server updates it on each RTT Measure Response; backends
442+
/// clone the handle to read a fresh network RTT for flow control. Inject a
443+
/// shared instance at construction with
444+
/// [`RdpServerBuilder::with_autodetect_rtt_handle`](crate::RdpServerBuilder::with_autodetect_rtt_handle).
445+
pub fn autodetect_rtt_handle(&self) -> Arc<AtomicU32> {
446+
Arc::clone(&self.autodetect_rtt)
447+
}
448+
435449
/// Returns the shared ECHO server handle for runtime probe requests and RTT measurements.
436450
pub fn echo_handle(&self) -> &EchoServerHandle {
437451
&self.echo_handle
@@ -1218,6 +1232,7 @@ impl RdpServer {
12181232
rdp::headers::ShareDataPdu::AutoDetectRsp(response) => {
12191233
if let Some(ref mut ad) = self.autodetect {
12201234
if let Some(rtt_ms) = ad.handle_response(&response) {
1235+
self.autodetect_rtt.store(rtt_ms, Ordering::Relaxed);
12211236
debug!(rtt_ms, seq = response.sequence_number(), "RTT measured");
12221237
} else {
12231238
trace!(seq = response.sequence_number(), "Unmatched auto-detect response");

crates/ironrdp-testsuite-core/tests/server/autodetect.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,44 @@ fn sequence_number_wraps_at_u16_max() {
7777
assert_eq!(req2.sequence_number(), 0, "should wrap around");
7878
}
7979

80+
#[test]
81+
fn autodetect_rtt_handle_defaults_to_sentinel() {
82+
use core::net::{Ipv4Addr, SocketAddr};
83+
use core::sync::atomic::Ordering;
84+
85+
use ironrdp_server::RdpServer;
86+
87+
let server = RdpServer::builder()
88+
.with_addr(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
89+
.with_no_security()
90+
.with_no_input()
91+
.with_no_display()
92+
.build();
93+
94+
assert_eq!(server.autodetect_rtt_handle().load(Ordering::Relaxed), u32::MAX);
95+
}
96+
97+
#[test]
98+
fn with_autodetect_rtt_handle_round_trips_the_same_arc() {
99+
use core::net::{Ipv4Addr, SocketAddr};
100+
use core::sync::atomic::{AtomicU32, Ordering};
101+
use std::sync::Arc;
102+
103+
use ironrdp_server::RdpServer;
104+
105+
let handle = Arc::new(AtomicU32::new(42));
106+
let server = RdpServer::builder()
107+
.with_addr(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
108+
.with_no_security()
109+
.with_no_input()
110+
.with_no_display()
111+
.with_autodetect_rtt_handle(Arc::clone(&handle))
112+
.build();
113+
114+
assert!(Arc::ptr_eq(&handle, &server.autodetect_rtt_handle()));
115+
assert_eq!(server.autodetect_rtt_handle().load(Ordering::Relaxed), 42);
116+
}
117+
80118
#[test]
81119
fn stale_probe_expiry() {
82120
let mut mgr = AutoDetectManager::new();

0 commit comments

Comments
 (0)