pub struct Quota { /* private fields */ }Expand description
One connection’s tunnel budget.
Every tunnel — TCP or UDP — costs a file descriptor on the target side, and a single client multiplexes as many as it likes onto one QUIC connection. This is the bound that keeps one client from exhausting the process fd limit, so both tunnel types draw on the same budget rather than one each.
A semaphore for the admission decision, plus a signal for the drain: the shutdown path needs to observe the count reaching zero without competing for it.
The two used to be one thing — wait_until_idle was acquire_many(limit),
on the reading that taking every permit at once is the same as waiting for
every tunnel to end. It is not. tokio’s semaphore is fair, so a waiter that
cannot be satisfied yet takes the permits that are free and queues for the
rest; while the drain waited, available_permits() was zero, so live()
reported the full limit and acquire() refused every caller. The connection
then answered 503 connection_limit_reached to requests below the GOAWAY
identifier — ones the peer had been told “might have been processed”,
RFC 9114 §5.2, and this server means to serve — with one tunnel open out of
a hundred (adversarial pass 2026-08-29).
Implementations§
Source§impl Quota
impl Quota
Sourcepub fn acquire(&self) -> Option<Slot>
pub fn acquire(&self) -> Option<Slot>
Takes a slot, or None when the connection is at its limit.
Sourcepub fn enter(&self) -> Pending
pub fn enter(&self) -> Pending
Records that a request has been accepted and is being served.
Held by the task from the moment the request stream is accepted until
that task ends, which is the span Self::acquire cannot cover: a
request only reaches the semaphore once its headers have arrived and its
credentials have been checked, and the ones that never get that far –
the 400s, the 407s – never reach it at all. Nothing is rationed here;
the count exists so the drain can tell “no work left” from “no work that
has a slot yet”.
Sourcepub fn is_busy(&self) -> bool
pub fn is_busy(&self) -> bool
Whether anything on this connection is still being served.
Both halves count, and the second is the one a tunnel count alone misses: a request accepted before the GOAWAY whose headers are still arriving holds no slot, and closing the connection on it contradicts the “might have been processed” its GOAWAY identifier signalled (RFC 9114 §5.2) without the REQUEST_REJECTED that would let the client retry.
Sourcepub async fn wait_until_idle(&self)
pub async fn wait_until_idle(&self)
Resolves once every request on this connection has finished.
Used by the graceful shutdown path. The caller is responsible for bounding the wait — a tunnel that never ends would otherwise hold shutdown open forever.
Cancel-safe, which it has to be: crate::conn::handle polls this as one
arm of a select! and drops it again on every pass. notify_one leaves
a permit behind when nobody is waiting and hands an unclaimed
notification on when a waiter is dropped, so neither a slot released
between the check and the park nor a lost race can wedge the drain.