mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 08:14:42 +00:00
tcp: drive inbound connection cap from node.limits.max_connections
The per-transport TCP inbound cap was hardwired to 256 and never read node.limits.max_connections, so raising max_connections was a silent no-op for inbound TCP. Resolve the effective cap with precedence: explicit per-transport max_inbound_connections, then node-wide max_connections, then the built-in default of 256. Established peers remain bounded node-wide by add_connection, so deriving the per-transport raw-accept ceiling from max_connections does not admit more real peers across multiple transports. Add effective_max_inbound on the TCP transport with a node_max_connections setter wired from create_transports, plus a precedence unit test.
This commit is contained in:
+7
-1
@@ -832,9 +832,15 @@ impl Node {
|
||||
.map(|(name, config)| (name.map(|s| s.to_string()), config.clone()))
|
||||
.collect();
|
||||
|
||||
// Node-wide connection budget — used as the TCP inbound-cap fallback
|
||||
// when a TCP instance has no explicit `max_inbound_connections`, so
|
||||
// raising `node.limits.max_connections` actually raises the inbound
|
||||
// ceiling rather than being silently capped at the transport default.
|
||||
let node_max_connections = self.config.node.limits.max_connections;
|
||||
for (name, tcp_config) in tcp_instances {
|
||||
let transport_id = self.allocate_transport_id();
|
||||
let tcp = TcpTransport::new(transport_id, name, tcp_config, packet_tx.clone());
|
||||
let mut tcp = TcpTransport::new(transport_id, name, tcp_config, packet_tx.clone());
|
||||
tcp.set_node_max_connections(node_max_connections);
|
||||
transports.push(TransportHandle::Tcp(tcp));
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,10 @@ pub struct TcpTransport {
|
||||
accept_task: Option<JoinHandle<()>>,
|
||||
/// Local listener address (after start, if bind_addr configured).
|
||||
local_addr: Option<SocketAddr>,
|
||||
/// Node-wide `node.limits.max_connections`, used as the inbound cap
|
||||
/// fallback when this transport has no explicit `max_inbound_connections`.
|
||||
/// `None` means "not provided" — fall through to the built-in default.
|
||||
node_max_connections: Option<usize>,
|
||||
/// Transport statistics.
|
||||
stats: Arc<TcpStats>,
|
||||
}
|
||||
@@ -144,10 +148,45 @@ impl TcpTransport {
|
||||
packet_tx,
|
||||
accept_task: None,
|
||||
local_addr: None,
|
||||
node_max_connections: None,
|
||||
stats: Arc::new(TcpStats::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the node-wide `node.limits.max_connections` value.
|
||||
///
|
||||
/// Used as the inbound-cap fallback when this transport instance has no
|
||||
/// explicit `transports.tcp.*.max_inbound_connections` set, so raising
|
||||
/// `node.limits.max_connections` actually raises the per-transport TCP
|
||||
/// accept ceiling instead of silently capping at the built-in default.
|
||||
pub fn set_node_max_connections(&mut self, max: usize) {
|
||||
self.node_max_connections = Some(max);
|
||||
}
|
||||
|
||||
/// Resolve the effective inbound connection cap for the accept loop.
|
||||
///
|
||||
/// Precedence: explicit per-transport `max_inbound_connections` >
|
||||
/// node-wide `node.limits.max_connections` > built-in default. This is a
|
||||
/// per-transport *raw-accept* ceiling; the true node-wide peer budget is
|
||||
/// still enforced downstream by the handshake-phase `max_connections`
|
||||
/// admission check (`Node::add_connection`), so deriving this ceiling
|
||||
/// from `max_connections` does not let multiple transports exceed the
|
||||
/// node-wide total — it only stops the transport from rejecting inbound
|
||||
/// below the configured node budget.
|
||||
fn effective_max_inbound(&self) -> usize {
|
||||
match (
|
||||
self.config.max_inbound_connections,
|
||||
self.node_max_connections,
|
||||
) {
|
||||
// Explicit per-transport key always wins.
|
||||
(Some(explicit), _) => explicit,
|
||||
// No per-transport key: fall back to the node-wide budget.
|
||||
(None, Some(node_max)) => node_max,
|
||||
// Neither set: the transport's built-in default (256).
|
||||
(None, None) => self.config.max_inbound_connections(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the instance name (if configured as a named instance).
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
@@ -197,7 +236,7 @@ impl TcpTransport {
|
||||
let stats = self.stats.clone();
|
||||
let cfg = AcceptConfig {
|
||||
mtu: self.config.mtu(),
|
||||
max_inbound: self.config.max_inbound_connections(),
|
||||
max_inbound: self.effective_max_inbound(),
|
||||
nodelay: self.config.nodelay(),
|
||||
keepalive_secs: self.config.keepalive_secs(),
|
||||
recv_buf: self.config.recv_buf_size(),
|
||||
@@ -1156,6 +1195,30 @@ mod tests {
|
||||
transport.stop_async().await.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effective_max_inbound_precedence() {
|
||||
let (tx, _rx) = packet_channel(100);
|
||||
|
||||
// Neither set: built-in transport default (256).
|
||||
let t = TcpTransport::new(TransportId::new(1), None, make_config(), tx.clone());
|
||||
assert_eq!(t.effective_max_inbound(), 256);
|
||||
|
||||
// Node-wide max_connections drives the cap when no per-transport key.
|
||||
let mut t = TcpTransport::new(TransportId::new(1), None, make_config(), tx.clone());
|
||||
t.set_node_max_connections(512);
|
||||
assert_eq!(t.effective_max_inbound(), 512);
|
||||
|
||||
// Explicit per-transport key wins over the node-wide value.
|
||||
let cfg = TcpConfig {
|
||||
bind_addr: Some("127.0.0.1:0".to_string()),
|
||||
max_inbound_connections: Some(64),
|
||||
..Default::default()
|
||||
};
|
||||
let mut t = TcpTransport::new(TransportId::new(1), None, cfg, tx);
|
||||
t.set_node_max_connections(512);
|
||||
assert_eq!(t.effective_max_inbound(), 64);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_double_start_fails() {
|
||||
let (tx, _rx) = packet_channel(100);
|
||||
|
||||
Reference in New Issue
Block a user