Skip to content

feat(server): expose NetworkAutoDetect RTT via a shared handle#1346

Open
Greg Lamberson (glamberson) wants to merge 1 commit into
Devolutions:masterfrom
lamco-admin:feat/server-autodetect-rtt-handle
Open

feat(server): expose NetworkAutoDetect RTT via a shared handle#1346
Greg Lamberson (glamberson) wants to merge 1 commit into
Devolutions:masterfrom
lamco-admin:feat/server-autodetect-rtt-handle

Conversation

@glamberson
Copy link
Copy Markdown
Contributor

Summary

The server measures network RTT in its AutoDetectManager (added in #1177), but once run() takes ownership a backend can no longer call rtt_snapshot() to read it: the measurement is stranded in the running server. This mirrors the existing display_suppressed shared-handle pattern (#1319) so the value can be shared out.

Adds an Arc holding the latest measured RTT in milliseconds (u32::MAX until the first measurement, and while auto-detect is disabled), 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 (for example to size an encoder's in-flight window) without polling the server object.

This also drops the cfg_attr(egfx) gate on new()'s too_many_arguments expect: the added parameter makes the non-egfx parameter count 8 (it was exactly 7, where the lint was silent), so the lint now fires in both feature configs and the expect is satisfied unconditionally.

Validation

cargo xtask check fmt/lints/tests/typos/locks all pass. New tests in ironrdp-testsuite-core cover the u32::MAX default and that with_autodetect_rtt_handle round-trips the same Arc.

Notes

Additive; mirrors #1319's handle pattern. new() gains a parameter the same way #1319 added display_suppressed (feat, not feat!, per that precedent). Exposes only the generic RTT value; no flow-control policy.

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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR exposes the server’s NetworkAutoDetect RTT measurement via a shared Arc<AtomicU32> handle so display backends can read a fresh RTT value even after run() takes ownership of the server.

Changes:

  • Add autodetect_rtt: Arc<AtomicU32> to RdpServer, plus an autodetect_rtt_handle() getter and builder injection with_autodetect_rtt_handle(...).
  • Update the AutoDetectRsp handler to store the latest measured rtt_ms into the shared atomic handle.
  • Add tests to cover the u32::MAX sentinel default and builder handle injection behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
crates/ironrdp-server/src/server.rs Adds the shared RTT handle to RdpServer, exposes it via an accessor, and updates it on auto-detect responses.
crates/ironrdp-server/src/builder.rs Extends the builder to allow injecting a shared RTT handle used by both server and backend.
crates/ironrdp-testsuite-core/tests/server/autodetect.rs Adds tests for the new RTT handle API and sentinel behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 391 to 395
autodetect: None,
connection_handler,
display_suppressed: display_suppressed.unwrap_or_else(|| Arc::new(AtomicBool::new(false))),
autodetect_rtt: autodetect_rtt.unwrap_or_else(|| Arc::new(AtomicU32::new(u32::MAX))),
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, we could load the sentinel RTT when the user provides its own handle

Comment on lines +105 to +116
let handle = Arc::new(AtomicU32::new(42));
let server = RdpServer::builder()
.with_addr(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
.with_no_security()
.with_no_input()
.with_no_display()
.with_autodetect_rtt_handle(Arc::clone(&handle))
.build();

assert!(Arc::ptr_eq(&handle, &server.autodetect_rtt_handle()));
assert_eq!(server.autodetect_rtt_handle().load(Ordering::Relaxed), 42);
}
Comment on lines 391 to 395
autodetect: None,
connection_handler,
display_suppressed: display_suppressed.unwrap_or_else(|| Arc::new(AtomicBool::new(false))),
autodetect_rtt: autodetect_rtt.unwrap_or_else(|| Arc::new(AtomicU32::new(u32::MAX))),
}
Comment on lines +105 to +116
let handle = Arc::new(AtomicU32::new(42));
let server = RdpServer::builder()
.with_addr(SocketAddr::from((Ipv4Addr::LOCALHOST, 0)))
.with_no_security()
.with_no_input()
.with_no_display()
.with_autodetect_rtt_handle(Arc::clone(&handle))
.build();

assert!(Arc::ptr_eq(&handle, &server.autodetect_rtt_handle()));
assert_eq!(server.autodetect_rtt_handle().load(Ordering::Relaxed), 42);
}
Copy link
Copy Markdown
Member

@CBenoit Benoît Cortier (CBenoit) left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good to me, but I have a question for you.

Using an Arc<AtomicU32> to share a state is of course perfectly fine, but don’t you end up polling the value repeatedly? I was wondering if a callback-based API would not be superior.

I don’t have the full consumer context, so maybe a simple Arc<Atomic*> is enough and does not perform poorly at all (e.g.: it’s optimal for a "read on a per-need basis model).

clippy::too_many_arguments,
reason = "called via the builder; positional parameters are an internal detail"
)]
pub fn new(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: We need to make this function private

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants