Skip to content

fix(relay): decouple global circuit ceiling from the per-peer limit - #701

Merged
varex83 merged 1 commit into
mainfrom
fix/relay-global-max-circuits
Sep 18, 2026
Merged

varex83 merged 1 commit into
mainfrom
fix/relay-global-max-circuits

Conversation

@varex83agent

Copy link
Copy Markdown
Collaborator

Fixes #484

Problem

crates/relay-server/src/config.rs set both circuit limits to max_res_per_peer:

max_circuits: config.max_res_per_peer,          // 512 — relay-wide
max_circuits_per_peer: config.max_res_per_peer, // 512 — per-peer

In rust-libp2p max_circuits is a global ceiling. A circuit request is denied with RESOURCE_LIMIT_EXCEEDED when either bound trips (libp2p-relay-0.21.1/src/behaviour.rs:539-541):

self.circuits.num_circuits_of_peer(event_source) > self.config.max_circuits_per_peer
    || self.circuits.len() >= self.config.max_circuits

So the whole relay was capped at 512 concurrent circuits regardless of --p2p-max-connections (default 16384).

Why this diverges from Charon

go-libp2p has no global circuit cap. Resources.MaxCircuits is documented as "the maximum number of open relay connections for each peer" (default 16) and is enforced only against the source and destination peer counts — go-libp2p@v0.41.1 p2p/protocol/circuitv2/relay/relay.go:293,301.

Charon's relayResources.MaxCircuits = config.MaxResPerPeer (charon@v1.7.1 cmd/relay/p2p.go:67) is therefore a per-peer 512 with nothing behind it.

Limit Pluto (before) Charon / go-libp2p
global reservations max_conns (16384) MaxReservations = MaxConns (16384)
per-peer reservations max_res_per_peer (512) MaxReservationsPerIP = MaxResPerPeer (512)
per-peer circuits max_res_per_peer (512) MaxCircuits = MaxResPerPeer (512, per-peer)
global circuits max_res_per_peer (512) (no equivalent) ❌ artificial throttle

Fix

max_circuits: config.max_conns,                 // 16384
max_circuits_per_peer: config.max_res_per_peer, // 512, unchanged — mirrors Charon

The per-peer value keeps mirroring Charon. The global ceiling is sized from max_conns, the same connection budget that already feeds max_reservations (and which Charon likewise routes only into MaxReservations — its relay runs a NullResourceManager, so there is no connection-manager cap either). That keeps a safety valve rust-libp2p offers and go-libp2p lacks, without throttling a healthy mesh.

usize::MAX was considered to mirror "no global cap" exactly, but that drops the safety valve for no benefit. No new CLI flag: it would diverge from Charon's flag set with no current need.

Tests

  • count_limits_from_config updated — global and per-peer values now differ.
  • New global_circuit_ceiling_is_decoupled_from_per_peer_limit asserts 16384 / 512 at the production CLI defaults, guarding against a re-coupling regression.

Notes

This is a latent scaling limit, not an active incident — dev-cluster circuit counts are well below 512. Being a resource-policy change, the chosen default is worth a look before merge.

Two stale details in the issue, neither affecting the fix: the todo(varex83) it cites was removed in #514, and PR #629's branch was named feat/fix-484 but actually closed #624 — this issue was never touched.

Gates

  • cargo +nightly fmt --all -- --check
  • cargo clippy -p pluto-relay-server --all-targets --all-features -- -D warnings
  • cargo test -p pluto-relay-server --all-features ✅ (44 passed)

🤖 Generated with Claude Code

`relay::Config.max_circuits` is a relay-wide ceiling in rust-libp2p, but it
was set to `max_res_per_peer` (default 512), capping the entire relay at 512
concurrent circuits regardless of `--p2p-max-connections` (default 16384).
Past that, every further circuit request is denied with
RESOURCE_LIMIT_EXCEEDED (libp2p-relay-0.21.1 behaviour.rs:539-541 denies on
`circuits.len() >= max_circuits`).

go-libp2p has no global circuit cap at all: `Resources.MaxCircuits` is
documented as the per-peer limit and is enforced only against the source and
destination peer counts (go-libp2p@v0.41.1
p2p/protocol/circuitv2/relay/relay.go:293,301). Charon's
`relayResources.MaxCircuits = config.MaxResPerPeer` (charon@v1.7.1
cmd/relay/p2p.go:67) is therefore a *per-peer* 512, with no relay-wide
ceiling behind it.

Keep `max_circuits_per_peer = max_res_per_peer` (mirrors Charon) and size the
global ceiling from `max_conns`, the same connection budget that already
feeds `max_reservations`. That preserves a safety valve rust-libp2p offers
and go-libp2p lacks, without the artificial throttle.

Fixes #484

Co-Authored-By: Bohdan Ohorodnii <35969035+varex83@users.noreply.github.com>
@varex83
varex83 merged commit 928cd50 into main Sep 18, 2026
16 checks passed
@varex83
varex83 deleted the fix/relay-global-max-circuits branch September 18, 2026 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve Relay server parity with Charon relay: global max_circuits pinned to per-peer value (512) throttles total relay throughput

3 participants