transport: cap max_inbound_connections on inbound count, not combined pool

Add per-direction pool_inbound/pool_outbound counters to TcpStats and
TorStats, updated at every pool-insert, receive-loop-exit, transport-stop,
and send-failure removal site. Compare the max_inbound_connections cap
against pool_inbound rather than the combined pool length, so outbound
connect-on-send connections no longer consume the operator-facing inbound
budget. The configuration field name and operator semantics are preserved;
only the cap-check comparison and accounting change.
This commit is contained in:
Johnathan Corgan
2026-05-28 20:13:20 +00:00
parent d575c1f986
commit 6dee6dfe27
5 changed files with 222 additions and 27 deletions
+14
View File
@@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- `pool_inbound` and `pool_outbound` counters on the TCP and Tor
transport stats (`TcpStats`, `TorStats`). Per-direction accounting
is updated at every pool-insert and receive-loop-exit site, plus on
transport stop and on send-failure-driven removal. Surfaces through
`TcpStatsSnapshot` and `TorStatsSnapshot` for `show_transports`.
- [`PR-REVIEW.md`](PR-REVIEW.md) — the 13-criteria PR review checklist - [`PR-REVIEW.md`](PR-REVIEW.md) — the 13-criteria PR review checklist
the maintainer runs against every incoming PR, published at the the maintainer runs against every incoming PR, published at the
repo root so contributors can run the same pass on their own change repo root so contributors can run the same pass on their own change
@@ -60,6 +65,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- TCP and Tor `max_inbound_connections` admission cap is now compared
against the per-direction inbound count (`pool_inbound`) rather than
the combined pool size. Outbound connect-on-send connections share
the same pool data structure but no longer consume slots against the
operator-facing inbound cap. The configuration field name and
operator semantics are preserved; only the cap-check comparison and
accounting change. Operators with mixed outbound + inbound
deployments no longer see legitimate inbound peers rejected once
outbound connections fill the pool past the configured cap.
- Outbound connection initiation now honors the `node.limits.max_peers` - Outbound connection initiation now honors the `node.limits.max_peers`
cap that was previously only checked on inbound msg1 admission. Four cap that was previously only checked on inbound msg1 admission. Four
paths gated: auto-reconnect retries (`process_pending_retries`), paths gated: auto-reconnect retries (`process_pending_retries`),
+72 -17
View File
@@ -52,6 +52,17 @@ use tracing::{debug, info, trace, warn};
// Connection Pool // Connection Pool
// ============================================================================ // ============================================================================
/// Direction of a pooled connection, used to drive separate
/// `pool_inbound` / `pool_outbound` accounting for the
/// `max_inbound_connections` admission cap.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Direction {
/// Inbound — accepted by the listener.
Inbound,
/// Outbound — initiated by connect-on-send or background connect.
Outbound,
}
/// State for a single TCP connection to a peer. /// State for a single TCP connection to a peer.
struct TcpConnection { struct TcpConnection {
/// Write half of the split stream. /// Write half of the split stream.
@@ -64,6 +75,8 @@ struct TcpConnection {
/// When the connection was established. /// When the connection was established.
#[allow(dead_code)] #[allow(dead_code)]
established_at: Instant, established_at: Instant,
/// Direction of the connection — drives pool-inbound/outbound accounting.
direction: Direction,
} }
/// Shared connection pool. /// Shared connection pool.
@@ -241,14 +254,22 @@ impl TcpTransport {
} }
drop(connecting); drop(connecting);
// Close all established connections // Close all established connections. The receive-loop cleanup
// would normally decrement pool_inbound / pool_outbound, but
// aborting the task skips that path; decrement explicitly here
// using the direction we stored on the connection record.
let mut pool = self.pool.lock().await; let mut pool = self.pool.lock().await;
for (addr, conn) in pool.drain() { for (addr, conn) in pool.drain() {
conn.recv_task.abort(); conn.recv_task.abort();
let _ = conn.recv_task.await; let _ = conn.recv_task.await;
match conn.direction {
Direction::Inbound => self.stats.record_pool_inbound_removed(),
Direction::Outbound => self.stats.record_pool_outbound_removed(),
}
debug!( debug!(
transport_id = %self.transport_id, transport_id = %self.transport_id,
remote_addr = %addr, remote_addr = %addr,
direction = ?conn.direction,
"TCP connection closed (transport stopping)" "TCP connection closed (transport stopping)"
); );
} }
@@ -326,6 +347,10 @@ impl TcpTransport {
let mut pool = self.pool.lock().await; let mut pool = self.pool.lock().await;
if let Some(conn) = pool.remove(addr) { if let Some(conn) = pool.remove(addr) {
conn.recv_task.abort(); conn.recv_task.abort();
match conn.direction {
Direction::Inbound => self.stats.record_pool_inbound_removed(),
Direction::Outbound => self.stats.record_pool_outbound_removed(),
}
} }
Err(TransportError::SendFailed(format!("{}", e))) Err(TransportError::SendFailed(format!("{}", e)))
} }
@@ -394,6 +419,7 @@ impl TcpTransport {
pool, pool,
mtu, mtu,
recv_stats, recv_stats,
Direction::Outbound,
) )
.await; .await;
}); });
@@ -403,12 +429,14 @@ impl TcpTransport {
recv_task, recv_task,
mtu: mss_mtu, mtu: mss_mtu,
established_at: Instant::now(), established_at: Instant::now(),
direction: Direction::Outbound,
}; };
let mut pool = self.pool.lock().await; let mut pool = self.pool.lock().await;
pool.insert(addr.clone(), conn); pool.insert(addr.clone(), conn);
self.stats.record_connection_established(); self.stats.record_connection_established();
self.stats.record_pool_outbound_added();
debug!( debug!(
transport_id = %self.transport_id, transport_id = %self.transport_id,
@@ -428,9 +456,14 @@ impl TcpTransport {
let mut pool = self.pool.lock().await; let mut pool = self.pool.lock().await;
if let Some(conn) = pool.remove(addr) { if let Some(conn) = pool.remove(addr) {
conn.recv_task.abort(); conn.recv_task.abort();
match conn.direction {
Direction::Inbound => self.stats.record_pool_inbound_removed(),
Direction::Outbound => self.stats.record_pool_outbound_removed(),
}
debug!( debug!(
transport_id = %self.transport_id, transport_id = %self.transport_id,
remote_addr = %addr, remote_addr = %addr,
direction = ?conn.direction,
"TCP connection closed (close_connection)" "TCP connection closed (close_connection)"
); );
} }
@@ -635,6 +668,7 @@ impl TcpTransport {
pool, pool,
mss_mtu, mss_mtu,
recv_stats, recv_stats,
Direction::Outbound,
) )
.await; .await;
}); });
@@ -644,6 +678,7 @@ impl TcpTransport {
recv_task, recv_task,
mtu: mss_mtu, mtu: mss_mtu,
established_at: Instant::now(), established_at: Instant::now(),
direction: Direction::Outbound,
}; };
// Use try_lock since we're in a sync context and the pool // Use try_lock since we're in a sync context and the pool
@@ -651,6 +686,7 @@ impl TcpTransport {
if let Ok(mut pool) = self.pool.try_lock() { if let Ok(mut pool) = self.pool.try_lock() {
pool.insert(addr.clone(), conn); pool.insert(addr.clone(), conn);
self.stats.record_connection_established(); self.stats.record_connection_established();
self.stats.record_pool_outbound_added();
debug!( debug!(
transport_id = %self.transport_id, transport_id = %self.transport_id,
remote_addr = %addr, remote_addr = %addr,
@@ -759,19 +795,19 @@ async fn accept_loop(
loop { loop {
match listener.accept().await { match listener.accept().await {
Ok((stream, peer_addr)) => { Ok((stream, peer_addr)) => {
// Check connection limit // Check inbound connection cap. Counts only inbound (accepted)
{ // connections currently held in the pool; outbound (connect-on-send)
let pool_guard = pool.lock().await; // connections live in the same pool but are not subject to the
if pool_guard.len() >= max_inbound { // operator-facing inbound cap.
stats.record_connection_rejected(); if stats.pool_inbound_count() >= max_inbound as u64 {
warn!( stats.record_connection_rejected();
transport_id = %transport_id, warn!(
peer_addr = %peer_addr, transport_id = %transport_id,
max = max_inbound, peer_addr = %peer_addr,
"Rejecting inbound TCP connection (max_inbound_connections reached)" max = max_inbound,
); "Rejecting inbound TCP connection (max_inbound_connections reached)"
continue; );
} continue;
} }
// Configure socket options // Configure socket options
@@ -838,6 +874,7 @@ async fn accept_loop(
recv_pool, recv_pool,
conn_mtu, conn_mtu,
recv_stats, recv_stats,
Direction::Inbound,
) )
.await; .await;
}); });
@@ -847,12 +884,14 @@ async fn accept_loop(
recv_task, recv_task,
mtu: conn_mtu, mtu: conn_mtu,
established_at: Instant::now(), established_at: Instant::now(),
direction: Direction::Inbound,
}; };
let mut pool_guard = pool.lock().await; let mut pool_guard = pool.lock().await;
pool_guard.insert(remote_addr.clone(), conn); pool_guard.insert(remote_addr.clone(), conn);
stats.record_connection_accepted(); stats.record_connection_accepted();
stats.record_pool_inbound_added();
debug!( debug!(
transport_id = %transport_id, transport_id = %transport_id,
@@ -880,7 +919,11 @@ async fn accept_loop(
/// ///
/// Reads complete FMP packets using the stream reader, delivers them to /// Reads complete FMP packets using the stream reader, delivers them to
/// the node via the packet channel. On error or EOF, removes the /// the node via the packet channel. On error or EOF, removes the
/// connection from the pool and exits. /// connection from the pool and exits. `direction` is captured here so
/// the cleanup path can decrement the correct `pool_inbound` /
/// `pool_outbound` counter regardless of whether the matching pool
/// entry survived to be removed.
#[allow(clippy::too_many_arguments)]
async fn tcp_receive_loop( async fn tcp_receive_loop(
mut reader: tokio::net::tcp::OwnedReadHalf, mut reader: tokio::net::tcp::OwnedReadHalf,
transport_id: TransportId, transport_id: TransportId,
@@ -889,6 +932,7 @@ async fn tcp_receive_loop(
pool: ConnectionPool, pool: ConnectionPool,
mtu: u16, mtu: u16,
stats: Arc<TcpStats>, stats: Arc<TcpStats>,
direction: Direction,
) { ) {
debug!( debug!(
transport_id = %transport_id, transport_id = %transport_id,
@@ -932,13 +976,24 @@ async fn tcp_receive_loop(
} }
} }
// Clean up: remove ourselves from the pool // Clean up: remove ourselves from the pool, then decrement the
// direction-specific pool counter. Decrement is conditional on the
// entry actually being removed so a double-cleanup never drives
// the counter below zero.
let mut pool_guard = pool.lock().await; let mut pool_guard = pool.lock().await;
pool_guard.remove(&remote_addr); let removed = pool_guard.remove(&remote_addr).is_some();
drop(pool_guard);
if removed {
match direction {
Direction::Inbound => stats.record_pool_inbound_removed(),
Direction::Outbound => stats.record_pool_outbound_removed(),
}
}
debug!( debug!(
transport_id = %transport_id, transport_id = %transport_id,
remote_addr = %remote_addr, remote_addr = %remote_addr,
direction = ?direction,
"TCP receive loop stopped" "TCP receive loop stopped"
); );
} }
+37
View File
@@ -21,6 +21,12 @@ pub struct TcpStats {
pub connections_rejected: AtomicU64, pub connections_rejected: AtomicU64,
pub connect_timeouts: AtomicU64, pub connect_timeouts: AtomicU64,
pub connect_refused: AtomicU64, pub connect_refused: AtomicU64,
/// Current number of inbound (accepted) connections held in the pool.
/// Drives the `max_inbound_connections` admission check.
pub pool_inbound: AtomicU64,
/// Current number of outbound (connect-on-send / promoted) connections
/// held in the pool. Independent of the inbound cap.
pub pool_outbound: AtomicU64,
} }
impl TcpStats { impl TcpStats {
@@ -39,6 +45,8 @@ impl TcpStats {
connections_rejected: AtomicU64::new(0), connections_rejected: AtomicU64::new(0),
connect_timeouts: AtomicU64::new(0), connect_timeouts: AtomicU64::new(0),
connect_refused: AtomicU64::new(0), connect_refused: AtomicU64::new(0),
pool_inbound: AtomicU64::new(0),
pool_outbound: AtomicU64::new(0),
} }
} }
@@ -94,6 +102,31 @@ impl TcpStats {
self.connect_refused.fetch_add(1, Ordering::Relaxed); self.connect_refused.fetch_add(1, Ordering::Relaxed);
} }
/// Increment the inbound pool count (called on accept).
pub fn record_pool_inbound_added(&self) {
self.pool_inbound.fetch_add(1, Ordering::Relaxed);
}
/// Decrement the inbound pool count (called on inbound receive-loop exit).
pub fn record_pool_inbound_removed(&self) {
self.pool_inbound.fetch_sub(1, Ordering::Relaxed);
}
/// Increment the outbound pool count (called on connect-on-send / promote).
pub fn record_pool_outbound_added(&self) {
self.pool_outbound.fetch_add(1, Ordering::Relaxed);
}
/// Decrement the outbound pool count (called on outbound receive-loop exit).
pub fn record_pool_outbound_removed(&self) {
self.pool_outbound.fetch_sub(1, Ordering::Relaxed);
}
/// Load the current inbound pool count for the admission gate.
pub fn pool_inbound_count(&self) -> u64 {
self.pool_inbound.load(Ordering::Relaxed)
}
/// Take a snapshot of all counters. /// Take a snapshot of all counters.
pub fn snapshot(&self) -> TcpStatsSnapshot { pub fn snapshot(&self) -> TcpStatsSnapshot {
TcpStatsSnapshot { TcpStatsSnapshot {
@@ -109,6 +142,8 @@ impl TcpStats {
connections_rejected: self.connections_rejected.load(Ordering::Relaxed), connections_rejected: self.connections_rejected.load(Ordering::Relaxed),
connect_timeouts: self.connect_timeouts.load(Ordering::Relaxed), connect_timeouts: self.connect_timeouts.load(Ordering::Relaxed),
connect_refused: self.connect_refused.load(Ordering::Relaxed), connect_refused: self.connect_refused.load(Ordering::Relaxed),
pool_inbound: self.pool_inbound.load(Ordering::Relaxed),
pool_outbound: self.pool_outbound.load(Ordering::Relaxed),
} }
} }
} }
@@ -134,4 +169,6 @@ pub struct TcpStatsSnapshot {
pub connections_rejected: u64, pub connections_rejected: u64,
pub connect_timeouts: u64, pub connect_timeouts: u64,
pub connect_refused: u64, pub connect_refused: u64,
pub pool_inbound: u64,
pub pool_outbound: u64,
} }
+62 -10
View File
@@ -111,6 +111,17 @@ fn parse_tor_addr(addr: &TransportAddr) -> Result<TorAddr, TransportError> {
// Connection Pool // Connection Pool
// ============================================================================ // ============================================================================
/// Direction of a pooled Tor connection, used to drive separate
/// `pool_inbound` / `pool_outbound` accounting for the
/// `max_inbound_connections` admission cap on the onion-service side.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Direction {
/// Inbound — accepted by the onion service listener.
Inbound,
/// Outbound — initiated via SOCKS5 connect to a remote onion.
Outbound,
}
/// State for a single Tor connection to a peer. /// State for a single Tor connection to a peer.
struct TorConnection { struct TorConnection {
/// Write half of the split stream. /// Write half of the split stream.
@@ -123,6 +134,8 @@ struct TorConnection {
/// When the connection was established. /// When the connection was established.
#[allow(dead_code)] #[allow(dead_code)]
established_at: Instant, established_at: Instant,
/// Direction of the connection — drives pool-inbound/outbound accounting.
direction: Direction,
} }
/// Shared connection pool. /// Shared connection pool.
@@ -516,14 +529,22 @@ impl TorTransport {
} }
drop(connecting); drop(connecting);
// Close all connections // Close all connections. The receive-loop cleanup would
// normally decrement pool_inbound / pool_outbound, but
// aborting the task skips that path; decrement explicitly
// here using the direction we stored on the connection record.
let mut pool = self.pool.lock().await; let mut pool = self.pool.lock().await;
for (addr, conn) in pool.drain() { for (addr, conn) in pool.drain() {
conn.recv_task.abort(); conn.recv_task.abort();
let _ = conn.recv_task.await; let _ = conn.recv_task.await;
match conn.direction {
Direction::Inbound => self.stats.record_pool_inbound_removed(),
Direction::Outbound => self.stats.record_pool_outbound_removed(),
}
debug!( debug!(
transport_id = %self.transport_id, transport_id = %self.transport_id,
remote_addr = %addr, remote_addr = %addr,
direction = ?conn.direction,
"Tor connection closed (transport stopping)" "Tor connection closed (transport stopping)"
); );
} }
@@ -690,6 +711,10 @@ impl TorTransport {
let mut pool = self.pool.lock().await; let mut pool = self.pool.lock().await;
if let Some(conn) = pool.remove(addr) { if let Some(conn) = pool.remove(addr) {
conn.recv_task.abort(); conn.recv_task.abort();
match conn.direction {
Direction::Inbound => self.stats.record_pool_inbound_removed(),
Direction::Outbound => self.stats.record_pool_outbound_removed(),
}
} }
Err(TransportError::SendFailed(format!("{}", e))) Err(TransportError::SendFailed(format!("{}", e)))
} }
@@ -802,6 +827,7 @@ impl TorTransport {
pool, pool,
mtu, mtu,
recv_stats, recv_stats,
Direction::Outbound,
) )
.await; .await;
}); });
@@ -811,12 +837,14 @@ impl TorTransport {
recv_task, recv_task,
mtu, mtu,
established_at: Instant::now(), established_at: Instant::now(),
direction: Direction::Outbound,
}; };
let mut pool = self.pool.lock().await; let mut pool = self.pool.lock().await;
pool.insert(addr.clone(), conn); pool.insert(addr.clone(), conn);
self.stats.record_connection_established(); self.stats.record_connection_established();
self.stats.record_pool_outbound_added();
info!( info!(
transport_id = %self.transport_id, transport_id = %self.transport_id,
@@ -1023,6 +1051,7 @@ impl TorTransport {
pool, pool,
mtu, mtu,
recv_stats, recv_stats,
Direction::Outbound,
) )
.await; .await;
}); });
@@ -1032,6 +1061,7 @@ impl TorTransport {
recv_task, recv_task,
mtu, mtu,
established_at: Instant::now(), established_at: Instant::now(),
direction: Direction::Outbound,
}; };
// Use try_lock since we're in a sync context and the pool // Use try_lock since we're in a sync context and the pool
@@ -1039,6 +1069,7 @@ impl TorTransport {
if let Ok(mut pool) = self.pool.try_lock() { if let Ok(mut pool) = self.pool.try_lock() {
pool.insert(addr.clone(), conn); pool.insert(addr.clone(), conn);
self.stats.record_connection_established(); self.stats.record_connection_established();
self.stats.record_pool_outbound_added();
debug!( debug!(
transport_id = %self.transport_id, transport_id = %self.transport_id,
remote_addr = %addr, remote_addr = %addr,
@@ -1060,6 +1091,10 @@ impl TorTransport {
let mut pool = self.pool.lock().await; let mut pool = self.pool.lock().await;
if let Some(conn) = pool.remove(addr) { if let Some(conn) = pool.remove(addr) {
conn.recv_task.abort(); conn.recv_task.abort();
match conn.direction {
Direction::Inbound => self.stats.record_pool_inbound_removed(),
Direction::Outbound => self.stats.record_pool_outbound_removed(),
}
debug!( debug!(
transport_id = %self.transport_id, transport_id = %self.transport_id,
remote_addr = %addr, remote_addr = %addr,
@@ -1125,7 +1160,10 @@ impl Transport for TorTransport {
/// ///
/// Reads complete FMP packets using the stream reader, delivers them to /// Reads complete FMP packets using the stream reader, delivers them to
/// the node via the packet channel. On error or EOF, removes the /// the node via the packet channel. On error or EOF, removes the
/// connection from the pool and exits. /// connection from the pool and exits. `direction` is captured so the
/// cleanup path can decrement the correct `pool_inbound` /
/// `pool_outbound` counter.
#[allow(clippy::too_many_arguments)]
async fn tor_receive_loop( async fn tor_receive_loop(
mut reader: tokio::net::tcp::OwnedReadHalf, mut reader: tokio::net::tcp::OwnedReadHalf,
transport_id: TransportId, transport_id: TransportId,
@@ -1134,6 +1172,7 @@ async fn tor_receive_loop(
pool: ConnectionPool, pool: ConnectionPool,
mtu: u16, mtu: u16,
stats: Arc<TorStats>, stats: Arc<TorStats>,
direction: Direction,
) { ) {
debug!( debug!(
transport_id = %transport_id, transport_id = %transport_id,
@@ -1176,13 +1215,24 @@ async fn tor_receive_loop(
} }
} }
// Clean up: remove ourselves from the pool // Clean up: remove ourselves from the pool, then decrement the
// direction-specific pool counter. Decrement is conditional on the
// entry actually being removed so a double-cleanup never drives
// the counter below zero.
let mut pool_guard = pool.lock().await; let mut pool_guard = pool.lock().await;
pool_guard.remove(&remote_addr); let removed = pool_guard.remove(&remote_addr).is_some();
drop(pool_guard);
if removed {
match direction {
Direction::Inbound => stats.record_pool_inbound_removed(),
Direction::Outbound => stats.record_pool_outbound_removed(),
}
}
debug!( debug!(
transport_id = %transport_id, transport_id = %transport_id,
remote_addr = %remote_addr, remote_addr = %remote_addr,
direction = ?direction,
"Tor receive loop stopped" "Tor receive loop stopped"
); );
} }
@@ -1254,12 +1304,11 @@ async fn tor_accept_loop(
} }
}; };
// Check inbound connection limit // Check inbound connection cap. Counts only inbound (accepted)
let current_count = { // connections currently held in the pool; outbound (SOCKS5-connect)
let pool_guard = pool.lock().await; // connections live in the same pool but are not subject to the
pool_guard.len() // operator-facing inbound cap.
}; if stats.pool_inbound_count() >= max_inbound as u64 {
if current_count >= max_inbound {
stats.record_connection_rejected(); stats.record_connection_rejected();
debug!( debug!(
transport_id = %transport_id, transport_id = %transport_id,
@@ -1321,6 +1370,7 @@ async fn tor_accept_loop(
recv_pool, recv_pool,
mtu, mtu,
recv_stats, recv_stats,
Direction::Inbound,
) )
.await; .await;
}); });
@@ -1330,6 +1380,7 @@ async fn tor_accept_loop(
recv_task, recv_task,
mtu, mtu,
established_at: Instant::now(), established_at: Instant::now(),
direction: Direction::Inbound,
}; };
{ {
@@ -1338,6 +1389,7 @@ async fn tor_accept_loop(
} }
stats.record_connection_accepted(); stats.record_connection_accepted();
stats.record_pool_inbound_added();
debug!( debug!(
transport_id = %transport_id, transport_id = %transport_id,
+37
View File
@@ -23,6 +23,12 @@ pub struct TorStats {
pub connections_accepted: AtomicU64, pub connections_accepted: AtomicU64,
pub connections_rejected: AtomicU64, pub connections_rejected: AtomicU64,
pub control_errors: AtomicU64, pub control_errors: AtomicU64,
/// Current number of inbound (accepted via onion service) connections
/// held in the pool. Drives the `max_inbound_connections` admission check.
pub pool_inbound: AtomicU64,
/// Current number of outbound (SOCKS5 connect) connections held in
/// the pool. Independent of the inbound cap.
pub pool_outbound: AtomicU64,
} }
impl TorStats { impl TorStats {
@@ -43,6 +49,8 @@ impl TorStats {
connections_accepted: AtomicU64::new(0), connections_accepted: AtomicU64::new(0),
connections_rejected: AtomicU64::new(0), connections_rejected: AtomicU64::new(0),
control_errors: AtomicU64::new(0), control_errors: AtomicU64::new(0),
pool_inbound: AtomicU64::new(0),
pool_outbound: AtomicU64::new(0),
} }
} }
@@ -108,6 +116,31 @@ impl TorStats {
self.control_errors.fetch_add(1, Ordering::Relaxed); self.control_errors.fetch_add(1, Ordering::Relaxed);
} }
/// Increment the inbound pool count (called on accept).
pub fn record_pool_inbound_added(&self) {
self.pool_inbound.fetch_add(1, Ordering::Relaxed);
}
/// Decrement the inbound pool count (called on inbound receive-loop exit).
pub fn record_pool_inbound_removed(&self) {
self.pool_inbound.fetch_sub(1, Ordering::Relaxed);
}
/// Increment the outbound pool count (called on SOCKS5-connect promote).
pub fn record_pool_outbound_added(&self) {
self.pool_outbound.fetch_add(1, Ordering::Relaxed);
}
/// Decrement the outbound pool count (called on outbound receive-loop exit).
pub fn record_pool_outbound_removed(&self) {
self.pool_outbound.fetch_sub(1, Ordering::Relaxed);
}
/// Load the current inbound pool count for the admission gate.
pub fn pool_inbound_count(&self) -> u64 {
self.pool_inbound.load(Ordering::Relaxed)
}
/// Take a snapshot of all counters. /// Take a snapshot of all counters.
pub fn snapshot(&self) -> TorStatsSnapshot { pub fn snapshot(&self) -> TorStatsSnapshot {
TorStatsSnapshot { TorStatsSnapshot {
@@ -125,6 +158,8 @@ impl TorStats {
connections_accepted: self.connections_accepted.load(Ordering::Relaxed), connections_accepted: self.connections_accepted.load(Ordering::Relaxed),
connections_rejected: self.connections_rejected.load(Ordering::Relaxed), connections_rejected: self.connections_rejected.load(Ordering::Relaxed),
control_errors: self.control_errors.load(Ordering::Relaxed), control_errors: self.control_errors.load(Ordering::Relaxed),
pool_inbound: self.pool_inbound.load(Ordering::Relaxed),
pool_outbound: self.pool_outbound.load(Ordering::Relaxed),
} }
} }
} }
@@ -152,4 +187,6 @@ pub struct TorStatsSnapshot {
pub connections_accepted: u64, pub connections_accepted: u64,
pub connections_rejected: u64, pub connections_rejected: u64,
pub control_errors: u64, pub control_errors: u64,
pub pool_inbound: u64,
pub pool_outbound: u64,
} }