pub enum Refusal {
AtCapacity {
limit: usize,
},
PrincipalAtCapacity {
limit: usize,
active_principals: usize,
},
LaneAtCapacity {
lane: Lane,
ceiling: usize,
limit: usize,
},
AnonymousAtCapacity {
limit: usize,
},
KeyedReserve {
occupied: usize,
limit: usize,
reserve: usize,
},
MemoryBudget {
class: OpClass,
wanted: u64,
reserved: u64,
budget: u64,
},
Paused {
pause: Pause,
},
}Expand description
Why a transfer was not admitted.
Every variant carries the number that was hit, because a refusal an operator cannot tie to a number is a mystery rather than a signal.
Variants§
AtCapacity
The server-wide concurrent-transfer limit is full.
Fields
limit: usizeThe server-wide limit in force at the moment of the refusal.
The governed limit, not the configured one: the configured number
is the ceiling of
LimitBand, and what actually refuses
is wherever the CoDel controller has moved to inside it. Reported
rather than assumed, for the same reason the per-principal share is:
an operator who read one number out of a config file and a different
one out of a log would conclude the gate was broken.
PrincipalAtCapacity
This principal already holds its share of the server-wide limit.
Fields
limit: usizeThe per-principal limit in force at the moment of the refusal.
Under SshConfig::per_principal_limit_is_hard that is the
configured number. Otherwise it is the max-min share —
max(configured, server_wide / active) — which is why it is
reported rather than assumed: an operator who read the configured
number out of a file and a different number out of a log would
conclude the gate was broken.
active_principals: usizeHow many identities held a transfer when this one was refused.
1 means the refusal came from a hard configured cap: a share is
never smaller than the whole server when there is only one claimant.
Without this an operator cannot tell “you are being throttled
because the box is shared” from “you are being throttled because
somebody pinned a number”.
LaneAtCapacity
This operation’s lane is full while the rest of the server is held
by the other lanes’ floors (gate 1b, Lane::floor).
Its own variant because the operator action is different: “the box is full” says raise the budget, and this one says a write storm is being held to five eighths of the box on purpose so that reads still land.
Fields
AnonymousAtCapacity
The anonymous caller is already holding its whole ceiling.
Its own variant, and not Refusal::PrincipalAtCapacity with the
anonymous id in it, because the operator action is different in kind. A
per-principal refusal names a client that can be found, telephoned or
given a bigger share; this one says strangers are at their cap, which
is either normal (a popular public repository) or the first sign of a
scrape, and in neither case is the answer “raise the concurrency limit”.
It also carries no active_principals, because there is exactly one
anonymous principal and dividing anything by it would be theatre.
Fields
limit: usizeThe anonymous ceiling in force, i.e.
Admission::max_anonymous. Unlike every other limit reported
here this one is not governed or shared — it is the operator’s
number, unmodified — which is exactly why it is worth printing:
an operator who sees a refusal against 5 on a box with 200 free
slots must be able to see immediately that a different knob did it.
KeyedReserve
The stranger asked for a slot inside the part of the box that only a keyed identity may hold (D23b).
Its own variant, and specifically not
Refusal::AnonymousAtCapacity, because the two say opposite things
about the machine and are fixed by opposite knobs. AnonymousAtCapacity
means strangers are at their cap — it can fire on an idle box, and the
only number that changes it is --max-anonymous-transfers. This one
means the box is too busy to spend any of itself on a stranger: the
anonymous ceiling is nowhere near reached, and lowering it would do
nothing at all. The knob here is the server-wide limit, or the size of
the box, or a browse replica.
It also carries no principal and no share, because it is not a statement about this caller at all: it is a statement about occupancy, and the only thing about the caller that entered into it was that it has no key.
Fields
occupied: usizeSlots occupied as the gate’s own compare-and-swap observed them —
the reserve rides inside that atomic
(Governor::claim_reserving),
so this is the number that actually made the decision and not a
second reading taken nearby. Reported so an operator can see the
decision rather than reconstruct it: occupied + reserve >= limit
is the whole rule, visible in one line of log.
limit: usizeThe governed server-wide limit those slots are out of, i.e. wherever the CoDel controller has moved to inside its band. The configured number would be actively misleading here: the whole reason this gate exists is that the governed limit moves and the anonymous ceiling does not.
reserve: usizeHow many slots at the top of that limit are keyed-only, i.e.
Admission::keyed_reserve. Printed because it is derived
(CONTENTION_PERCENT) and therefore appears in no config file an
operator could look it up in.
MemoryBudget
The server’s memory budget is already spoken for (gate 2).
A third reason, kept separate from Refusal::AtCapacity on purpose: an
operator who reads “at capacity” raises the concurrency limit, and doing
that here makes the problem worse. The numbers travel so the log says
which knob to turn.
Fields
Paused
The gate is PAUSED — every transfer is refused, not for want of a
slot but because the server asked for a quiet moment: the runtime
data-set growth drains the box before it unmounts /data
(DATA-SET-GROWTH-FLOW.md §3 step 5, T1/T12). Carries no number
because no number was hit; carries the reason, whose words the client
reads. Set and cleared by Admission::pause / Admission::unpause
— one gate, so all three git fronts pause together.
Implementations§
Source§impl Refusal
impl Refusal
Sourcepub fn client_message(&self) -> String
pub fn client_message(&self) -> String
What the client is told, in its own protocol.
Says try again explicitly. “Server busy” with no instruction reads as a permanent failure to a human and to a CI log alike, and this one is transient by definition.
Sourcepub const fn reason(&self) -> &'static str
pub const fn reason(&self) -> &'static str
The machine-readable reason, for a log’s reason field.
The sentence above is for the person holding the git prompt; this is
for the operator filtering a log, and they need different things. Until
the anonymous ceiling existed every refusal here was fairly described by
one string — the box was out of something server-wide — and both
fronts hard-coded admission-gate-full accordingly. That string is now
actively misleading on the one refusal that can fire with the box nearly
empty: an operator who greps admission-gate-full, sees a hit, and
looks at 59 free slots concludes the gate is broken, which is exactly
the “the limit exists but produces no signal an operator can act on”
failure the unbounded listener was about, wearing a different hat.
Only the fronts that can actually see an anonymous caller use this.
The gRPC control plane keeps its literal, because
[gunnar_grpc::auth] refuses the anonymous principal outright: a
reason there that could report anonymity would be describing a state
that crate makes unreachable.
Trait Implementations§
impl Copy for Refusal
impl Eq for Refusal
impl StructuralPartialEq for Refusal
Auto Trait Implementations§
impl Freeze for Refusal
impl RefUnwindSafe for Refusal
impl Send for Refusal
impl Sync for Refusal
impl Unpin for Refusal
impl UnsafeUnpin for Refusal
impl UnwindSafe for Refusal
Blanket Implementations§
impl<T> Allocation for T
Source§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
Source§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.