pub const MAX_CONCURRENT_REQUESTS: usize = 512;Expand description
How many requests this server answers at once before a new one waits for a slot to free up.
Backpressure rather than an outright refusal — tower::limit::ConcurrencyLimit
makes a request wait for a permit instead of failing it — because a
caller within its rate-limit budget (crate::rate_limit) asking at a bad
moment should be slowed, not bounced. The two are complementary: this
bounds how much concurrent work every route together may impose (memory,
open file descriptors, threads blocked on I/O), where a rate limiter
bounds how much one caller may ask for.
Applied by limited_make_service, which both serve_router and
serve_tls serve through, not as a layer on the [Router] itself. That
placement is load-bearing, not a style choice: axum::Router::layer
re-applies whatever [tower::Layer] it is given at more than one internal
dispatch point, each application constructing its own middleware
instance. A stateless layer does not care — a fresh Duration behaves
identically every time, which is why the router’s own request timeout is
layered directly onto it. ConcurrencyLimitLayer is not
stateless: Layer::layer allocates a fresh Semaphore on every call, so
a limit applied through Router::layer ends up enforced against several
independent, unshared semaphores instead of the one number a deployment
configured — the bound would compile, look correct in a code review, and
do nothing. Wrapping the already-built Router once, there, and turning
the result into a MakeService the way [tower::make::Shared] would is
what keeps every per-connection clone pointing at the same semaphore. See
this crate’s tests::concurrency_past_the_limit_waits_instead_of_running_unbounded
for the regression this would otherwise be.