Files
hbb_common/src/rx_probe.rs
T
rustdeskandClaude Opus 5 c50c1fef32 webrtc: report receive progress and ICE's disconnected hint
A peer that is killed, logged out or rebooted sends no reset over UDP, so a
session only learns of it by timing out. Nothing here acts on that; these are
the two observations a caller needs to.

`rx_progress` counts arriving fragments. A message reaches the caller only once
it is whole, so a large one - a clipboard image is the case that occurs - leaves
the receive loop with nothing to observe for as long as the transfer takes, and
a peer sending steadily looks exactly like one that died. A counter, not a
timestamp: `Instant::now()` costs an order of magnitude more per read, and
callers only compare successive samples.

`webrtc_disconnected` exposes the ICE state webrtc-rs already raises ~5s after
it stops hearing from the peer. It is deliberately not a fourth
`WebRTCConnectionState`: that enum gates send and receive readiness and session
cache admissibility, and a transient state must change neither.

TCP and WebSocket answer for neither, on purpose: this change leaves their
existing close, error and timeout behaviour exactly as it was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019aokqJuhjvB3kijXtAg5Ns
2026-09-08 14:57:09 +08:00

52 lines
1.5 KiB
Rust

use std::sync::{
atomic::{AtomicU64, Ordering},
Arc,
};
/// Bytes received from the peer, counted before framing.
///
/// A message reaches the application only once it is complete, so a large one — a clipboard image
/// is the case that occurs in practice — leaves the receive loop with nothing to observe for as
/// long as the transfer takes, and a peer that is sending steadily looks identical to one that
/// died. This counter moves on every read, so liveness stays observable across such a message.
///
/// A counter rather than a timestamp on purpose: `Instant::now()` costs an order of magnitude more
/// than a relaxed add, and the reader only ever compares successive samples, so the clock is
/// needed once per sampling tick instead of once per read.
#[derive(Clone, Default)]
pub struct RxProbe(Arc<AtomicU64>);
impl RxProbe {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn add(&self, n: usize) {
self.0.fetch_add(n as u64, Ordering::Relaxed);
}
/// Changes whenever bytes arrived. Compare successive samples; the absolute value and its
/// wrap-around carry no meaning.
#[inline]
pub fn get(&self) -> u64 {
self.0.load(Ordering::Relaxed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clones_share_one_counter() {
let probe = RxProbe::new();
let cloned = probe.clone();
assert_eq!(probe.get(), 0);
cloned.add(7);
assert_eq!(probe.get(), 7);
probe.add(3);
assert_eq!(cloned.get(), 10);
}
}