client log sends to proxy

This commit is contained in:
2026-06-30 19:49:50 +07:00
parent f7a76353ea
commit 582ad714ae
8 changed files with 221 additions and 1 deletions
+41
View File
@@ -76,6 +76,14 @@ const MAX_POLL_SLEEP: Duration = Duration::from_millis(2);
/// than this many queued frames is treated as a dead/stuck consumer and dropped,
/// so it can never stall the shared download pipe for other sockets.
const MAX_PENDING_FRAMES_PER_SOCKET: usize = 64;
/// Max diagnostics snapshots buffered locally awaiting upload to the server.
/// The most useful triggers (leg disconnect/reconnect) fire exactly when no leg
/// is up to carry them, so snapshots wait here and flush once a leg recovers.
/// Oldest is dropped past the cap — recent state matters more than ancient.
const DIAG_OUTBOX_CAP: usize = 128;
/// Diagnostics snapshots flushed to the server per engine tick. Bounds the cold
/// path so a large backlog can't monopolise a loop iteration after reconnect.
const DIAG_FLUSH_PER_TICK: usize = 16;
/// Движок клиентского стека: интерфейс smoltcp, реестр сокетов, мост в туннель
/// и диагностика. Живёт в одной задаче `tokio` (см. [`run`](Engine::run)).
@@ -98,6 +106,11 @@ pub struct Engine {
diag_rx: Option<DiagnosisRx>,
/// Shared store that holds the last N snapshots (readable via public API).
pub diag_store: Arc<DiagnosticsStore>,
/// Snapshots serialized to JSON and queued for upload to the connected
/// server (one `Diag` frame each). Filled on every trigger; drained whenever
/// a tunnel leg is available so reports survive the disconnect that produced
/// them. See [`DIAG_OUTBOX_CAP`] / [`DIAG_FLUSH_PER_TICK`].
diag_outbox: std::collections::VecDeque<Bytes>,
/// Per-socket backlog of download frames whose target channel was full.
/// Keyed by socket_id so a single slow/dead consumer can NEVER stall the
/// shared download pipe for other sockets — the old single global slot did
@@ -146,6 +159,7 @@ impl Engine {
muxer: None,
diag_rx: None,
diag_store: Arc::new(DiagnosticsStore::new(20)),
diag_outbox: std::collections::VecDeque::new(),
pending_download: std::collections::HashMap::new(),
dl_dispatched: 0,
dl_dropped_stuck: 0,
@@ -381,6 +395,12 @@ impl Engine {
match diag_rx.try_recv() {
Ok(event) => {
let snap = self.build_snapshot(event);
// Queue a compact JSON copy for upload to the server,
// then keep the snapshot in the local ring buffer.
if self.diag_outbox.len() >= DIAG_OUTBOX_CAP {
self.diag_outbox.pop_front();
}
self.diag_outbox.push_back(Bytes::from(snap.to_json_line()));
self.diag_store.push(snap);
}
Err(_) => break,
@@ -389,6 +409,27 @@ impl Engine {
self.diag_rx = Some(diag_rx);
}
// ── 6b. Ship queued diagnostics to the server ────────────────
// Best-effort, cold path: only runs when there's a backlog AND a leg
// is up to carry it. send_diag_report is a non-blocking try_send under
// the hood, so the await is cheap; on failure (no leg accepted it) we
// re-queue the snapshot and retry on a later tick.
if !self.diag_outbox.is_empty() {
if let Some(muxer) = self.muxer.clone() {
if muxer.active_legs_count() > 0 {
for _ in 0..DIAG_FLUSH_PER_TICK {
let Some(line) = self.diag_outbox.pop_front() else {
break;
};
if !muxer.send_diag_report(line.clone()).await {
self.diag_outbox.push_front(line);
break;
}
}
}
}
}
// ── 7. Adaptive timing ───────────────────────────────────────
if work_done {
tokio::task::yield_now().await;