Repository navigation
cmux-tui: each terminal gets a fair share of the terminal connection lane (on top of #27719's 48-of-64 cap) #28343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4637c8e
1141578
2a0c4f9
6aaecf6
0a0796c
5631798
703547c
bfff9fd
85ccb0b
21646a4
6d91a64
66808c6
8fc1e8a
20f7f9c
d0db479
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,8 +10,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::collections::HashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::panic::Location; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::sync::Mutex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::sync::{Mutex, PoisonError}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::time::{Duration, Instant}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| use serde::Serialize; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -657,6 +657,10 @@ pub struct ConnectionStats { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| /// terminal connection gives this slot back with its main one | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// (`connection_leases`). | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| terminal_active: AtomicU64, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Of `terminal_active`, the connections each terminal holds (by its | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// process session), for the terminal lane's fair share | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// (`try_claim_terminal`). A terminal with none is not listed. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| terminal_clients: Mutex<HashMap<u32, u64>>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// The socket these connections come in on, once a server attached the | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// session to it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| socket: std::sync::OnceLock<std::path::PathBuf>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -669,15 +673,18 @@ impl ConnectionStats { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| /// the slot back. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// At `limit` it first takes back the slots of clients that hung up | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// (`reclaim_hung_up`, pool bounded by `pool_limit`), as `try_claim` does. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn try_claim_terminal(&self, limit: u64, pool_limit: u64) -> bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let claim = |count: u64| (count < limit).then_some(count + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut claimed = | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.terminal_active.fetch_update(Ordering::AcqRel, Ordering::Acquire, claim).is_ok(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// `client`: the terminal the connection comes from (its process session). | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// A terminal's first connection gets in whenever the lane has room; a | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// further one only while that terminal then holds no more than the lane | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// has left free. One terminal (an agent's hooks in a loop, a script | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// running `cmux`) stops at half the lane, so a second terminal still finds | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// room; several terminals in a loop together can still fill the lane. A | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// connection whose terminal is unknown is bounded by the lane alone. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn try_claim_terminal(&self, limit: u64, pool_limit: u64, client: Option<u32>) -> bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut claimed = self.claim_terminal_slot(limit, client); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !claimed && self.reclaim_hung_up(pool_limit) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| claimed = self | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| .terminal_active | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| .fetch_update(Ordering::AcqRel, Ordering::Acquire, claim) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| .is_ok(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| claimed = self.claim_terminal_slot(limit, client); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !claimed { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.accepted.fetch_sub(1, Ordering::Relaxed); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -686,7 +693,38 @@ impl ConnectionStats { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| claimed | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn release_terminal(&self) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// One terminal slot under `limit`, within `client`'s fair share. The | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// share and the count change together under `terminal_clients`, so two | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// claims never both take the last place a share allows. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn claim_terminal_slot(&self, limit: u64, client: Option<u32>) -> bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut clients = self.terminal_clients.lock().unwrap_or_else(PoisonError::into_inner); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let active = self.terminal_active.load(Ordering::Acquire); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if active >= limit { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Some(client) = client { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let held = clients.get(&client).copied().unwrap_or(0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let free_after = limit.saturating_sub(active.saturating_add(1)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if held > 0 && held.saturating_add(1) > free_after { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let held = clients.entry(client).or_insert(0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| *held = held.saturating_add(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.terminal_active.fetch_add(1, Ordering::AcqRel); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| true | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| pub fn release_terminal(&self, client: Option<u32>) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Some(client) = client { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut clients = self.terminal_clients.lock().unwrap_or_else(PoisonError::into_inner); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Some(held) = clients.get_mut(&client) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| *held = held.saturating_sub(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if *held == 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| clients.remove(&client); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.terminal_active.fetch_sub(1, Ordering::AcqRel); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+718
to
728
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,7 +82,7 @@ pub(super) struct ConnectionPermit { | |
| pub(super) fn claim_connection( | ||
| connections: &Arc<crate::diagnostics::ConnectionStats>, | ||
| ) -> Option<ConnectionPermit> { | ||
| claim_connection_watching(connections, None, false) | ||
| claim_connection_watching(connections, None, None) | ||
| } | ||
|
|
||
| /// A slot for a local client, taken back if the client hangs up while its | ||
|
|
@@ -91,25 +91,37 @@ pub(super) fn claim_watched_connection( | |
| connections: &Arc<crate::diagnostics::ConnectionStats>, | ||
| stream: &dyn transport::Stream, | ||
| ) -> Option<ConnectionPermit> { | ||
| claim_connection_watching(connections, watch_peer(stream), false) | ||
| claim_connection_watching(connections, watch_peer(stream), None) | ||
| } | ||
|
|
||
| /// A slot for a process inside one of the session's terminals: it also takes | ||
| /// a terminal slot, and is refused (its slot given back) when terminals | ||
| /// already hold `MAX_TERMINAL_CONNECTIONS` (#27698). Watched like | ||
| /// `claim_watched_connection`; a hang-up gives back both slots. | ||
| /// `client`: the terminal the peer runs in (its process session), nil when | ||
| /// unknown. | ||
| pub(super) fn claim_watched_terminal_connection( | ||
| connections: &Arc<crate::diagnostics::ConnectionStats>, | ||
| stream: &dyn transport::Stream, | ||
| client: Option<u32>, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Prompt for AI agents |
||
| ) -> Option<ConnectionPermit> { | ||
| claim_connection_watching(connections, watch_peer(stream), true) | ||
| claim_connection_watching(connections, watch_peer(stream), Some(client)) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| pub(super) fn claim_terminal_connection( | ||
| connections: &Arc<crate::diagnostics::ConnectionStats>, | ||
| ) -> Option<ConnectionPermit> { | ||
| claim_connection_watching(connections, None, true) | ||
| claim_connection_watching(connections, None, Some(None)) | ||
| } | ||
|
|
||
| /// A terminal slot for a connection from terminal `client` (its process session). | ||
| #[cfg(test)] | ||
| pub(super) fn claim_terminal_connection_from( | ||
| connections: &Arc<crate::diagnostics::ConnectionStats>, | ||
| client: u32, | ||
| ) -> Option<ConnectionPermit> { | ||
| claim_connection_watching(connections, None, Some(Some(client))) | ||
| } | ||
|
|
||
| fn watch_peer(stream: &dyn transport::Stream) -> Option<crate::diagnostics::PeerClosed> { | ||
|
|
@@ -119,17 +131,22 @@ fn watch_peer(stream: &dyn transport::Stream) -> Option<crate::diagnostics::Peer | |
| } | ||
| } | ||
|
|
||
| /// `terminal`: nil for a connection from outside the session's terminals, | ||
| /// else the terminal it comes from (nil inside when unknown). | ||
| fn claim_connection_watching( | ||
| connections: &Arc<crate::diagnostics::ConnectionStats>, | ||
| peer_closed: Option<crate::diagnostics::PeerClosed>, | ||
| terminal: bool, | ||
| terminal: Option<Option<u32>>, | ||
| ) -> Option<ConnectionPermit> { | ||
| if !connections.try_claim(MAX_SERVER_CONNECTIONS as u64) { | ||
| return None; | ||
| } | ||
| if terminal | ||
| && !connections | ||
| .try_claim_terminal(MAX_TERMINAL_CONNECTIONS as u64, MAX_SERVER_CONNECTIONS as u64) | ||
| if let Some(client) = terminal | ||
| && !connections.try_claim_terminal( | ||
| MAX_TERMINAL_CONNECTIONS as u64, | ||
| MAX_SERVER_CONNECTIONS as u64, | ||
| client, | ||
| ) | ||
| { | ||
| connections.release(); | ||
| return None; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: This condition strands terminal-lane capacity once multiple terminals have leases: with two terminals holding 23 each, neither can claim either remaining slot. Compare the client count with its half-lane share so two terminals can use their fair shares.
Prompt for AI agents