mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-08 21:41:02 +03:00
drm: address the self-review findings on the review rework
Five defects an adversarial pass found in the previous commits: - refresh_available_async set the single-flight probe guard, then relied on the detached thread to clear it; if thread creation failed (EAGAIN) or the closure unwound, the guard leaked true and froze every future probe. Release it via RAII inside the closure and on a Builder::spawn error. - The _drm per-frame re-auth called the cached active_uid(), which on a cache miss (exactly during a session switch) falls back to a blocking loginctl seat0 lookup -- on the single-threaded _drm runtime, once per frame, a subprocess storm. Use a new cache-only accessor that never blocks and fails closed on a miss, and correct the comment: the stop is bounded by the active-uid cache cadence, not one frame. - set_drm_cursor inserted unconditionally, so a still-draining predecessor stream could overwrite (then delete on teardown) the cursor a replacement stream published for the same index. Make it a compare-and-set that ignores an older epoch. - recv_msg_timeout2 treated a spurious readable() wakeup with nothing consumed as a mid-frame stall and tore the stream down. Track whether any byte was consumed (drm_read_full sets it) and map a zero-progress deadline back to None (re-poll), reserving the hard error for a genuine partial-frame stall.
This commit is contained in:
@@ -464,7 +464,17 @@ fn next_cursor_epoch() -> u64 {
|
||||
}
|
||||
|
||||
fn set_drm_cursor(display: i32, epoch: u64, c: DrmCursorData) {
|
||||
DRM_CURSOR.lock().unwrap().insert(display, (epoch, c));
|
||||
// Compare-and-set: a still-draining predecessor stream (older epoch) must not overwrite the entry a
|
||||
// replacement stream (newer epoch) already published for the same display index -- otherwise it
|
||||
// would re-stamp the slot with its old epoch and then delete it on teardown via remove_drm_cursor,
|
||||
// erasing the fresh cursor. Only accept a write whose epoch is at least the stored one.
|
||||
let mut map = DRM_CURSOR.lock().unwrap();
|
||||
match map.get(&display) {
|
||||
Some((stored, _)) if *stored > epoch => {}
|
||||
_ => {
|
||||
map.insert(display, (epoch, c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compare-and-remove: drop the entry only if THIS stream (epoch) still owns it. A replacement stream
|
||||
@@ -653,9 +663,21 @@ fn refresh_available_async() {
|
||||
if DRM_PROBE_IN_FLIGHT.swap(true, Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
std::thread::spawn(|| {
|
||||
let result = query_displays();
|
||||
{
|
||||
// Release the single-flight guard via RAII so it clears on EVERY exit -- normal return, a panic in
|
||||
// query_displays, or a poisoned DRM_STATE. Without this, one dropped release (e.g. the closure never
|
||||
// runs because thread creation failed, or it unwinds) would leave the guard stuck true and freeze
|
||||
// every future probe and refresh for the process lifetime.
|
||||
struct ReleaseGuard;
|
||||
impl Drop for ReleaseGuard {
|
||||
fn drop(&mut self) {
|
||||
DRM_PROBE_IN_FLIGHT.store(false, Ordering::Release);
|
||||
}
|
||||
}
|
||||
let spawned = std::thread::Builder::new()
|
||||
.name("drm-avail-refresh".into())
|
||||
.spawn(|| {
|
||||
let _release = ReleaseGuard;
|
||||
let result = query_displays();
|
||||
let mut st = DRM_STATE.lock().unwrap();
|
||||
if let ProbeState::Available(since, list) = &mut *st {
|
||||
*since = Instant::now();
|
||||
@@ -665,9 +687,12 @@ fn refresh_available_async() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Thread creation itself can fail (EAGAIN under thread/RLIMIT pressure); if so the closure never
|
||||
// runs, so release the guard here instead of leaking it.
|
||||
if spawned.is_err() {
|
||||
DRM_PROBE_IN_FLIGHT.store(false, Ordering::Release);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Warm the availability cache at `--server` startup so the first client connection does not race a
|
||||
|
||||
Reference in New Issue
Block a user