WIP cleanup: split branch stash into 8 focused commits #2

Merged
osobh merged 8 commits from chore/wip-followups into main 2026-07-06 01:52:14 +00:00
Showing only changes of commit e2c82d9a93 - Show all commits
+22 -5
View File
@@ -60,8 +60,10 @@ struct NodeConn {
tx: mpsc::UnboundedSender<String>, tx: mpsc::UnboundedSender<String>,
pending: Mutex<HashMap<u64, oneshot::Sender<ExecOutput>>>, pending: Mutex<HashMap<u64, oneshot::Sender<ExecOutput>>>,
/// Live terminal sessions: sid → byte sink for the browser bridge (WS-relay /// Live terminal sessions: sid → byte sink for the browser bridge (WS-relay
/// PTY output). /// PTY output). Bounded: a runaway PTY (say `cat /var/log/huge`) with a
pty_sinks: Mutex<HashMap<u64, mpsc::UnboundedSender<Vec<u8>>>>, /// stalled browser must not accumulate megabytes here. On overflow the
/// session is closed instead of holding output indefinitely.
pty_sinks: Mutex<HashMap<u64, mpsc::Sender<Vec<u8>>>>,
/// WebRTC signaling: sid → text sink delivering the daemon's answer/ICE to /// WebRTC signaling: sid → text sink delivering the daemon's answer/ICE to
/// the browser bridge. /// the browser bridge.
signal_sinks: Mutex<HashMap<u64, mpsc::UnboundedSender<String>>>, signal_sinks: Mutex<HashMap<u64, mpsc::UnboundedSender<String>>>,
@@ -187,12 +189,13 @@ impl NodeHub {
id: NodeId, id: NodeId,
) -> Option<( ) -> Option<(
u64, u64,
mpsc::UnboundedReceiver<Vec<u8>>, mpsc::Receiver<Vec<u8>>,
mpsc::UnboundedReceiver<String>, mpsc::UnboundedReceiver<String>,
)> { )> {
let conn = self.get(id).await?; let conn = self.get(id).await?;
let sid = conn.next_id.fetch_add(1, Ordering::Relaxed); let sid = conn.next_id.fetch_add(1, Ordering::Relaxed);
let (ptx, prx) = mpsc::unbounded_channel(); // 256 × ~4KB PTY frames = ~1 MB per stalled session before we close it.
let (ptx, prx) = mpsc::channel(256);
let (stx, srx) = mpsc::unbounded_channel(); let (stx, srx) = mpsc::unbounded_channel();
conn.pty_sinks.lock().await.insert(sid, ptx); conn.pty_sinks.lock().await.insert(sid, ptx);
conn.signal_sinks.lock().await.insert(sid, stx); conn.signal_sinks.lock().await.insert(sid, stx);
@@ -462,7 +465,21 @@ pub async fn run_channel(pool: PgPool, hub: Arc<NodeHub>, node_id: NodeId, socke
if let Ok(bytes) = B64.decode(&data) { if let Ok(bytes) = B64.decode(&data) {
let sink = conn.pty_sinks.lock().await.get(&sid).cloned(); let sink = conn.pty_sinks.lock().await.get(&sid).cloned();
if let Some(s) = sink { if let Some(s) = sink {
let _ = s.send(bytes); // try_send so a stalled browser can't grow the
// per-session buffer without bound. On Full, the
// session is torn down: drop both sinks and tell
// the node to close its side, preventing an
// orphan PTY.
if let Err(err) = s.try_send(bytes) {
if matches!(err, mpsc::error::TrySendError::Full(_)) {
conn.pty_sinks.lock().await.remove(&sid);
conn.signal_sinks.lock().await.remove(&sid);
let _ = conn.tx.send(
json!({ "t": "pty_close", "sid": sid })
.to_string(),
);
}
}
} }
} }
} }