pub(crate) struct EgressGauge {
queued: AtomicUsize,
idle: Notify,
discarding: AtomicBool,
}Expand description
How many bytes one session’s egress queues are holding, right now.
One gauge per session, shared by every PendingQueue the session
builds. It exists for a single caller — a requested close, which has to
know when there is nothing left to flush so it can stop waiting early
— and it answers that question in the one unit that is worth waiting on.
§Why a byte gauge rather than a stream count
A session’s live streams are already enumerated by
StreamRegistry, and waiting for that
to empty was the obvious alternative. It is the wrong signal twice over:
a stream with an empty queue keeps its registration until its source
FINs, so the wait would run to the deadline on a session that had
nothing to flush at all; and a stream that ends while its queue is full
drops its registration with the bytes still unwritten, so the wait would
finish claiming a drain that lost data.
§Two-valued, and the second value is the whole bound
Self::wait_idle is the wait. Self::begin_discarding is what
happens when the wait runs out: it puts every queue in the session into
a mode where PendingQueue::drain_ignoring_release_times writes
nothing at all.
That looks backwards next to the rest of this module, whose teardown rule is
delivered late beats lost silently. The difference is that an ordinary
teardown is not a deadline anybody set — the flush is the best remaining
chance those bytes have. A close that has already been given its drain
window and spent it is a deadline somebody set, and flushing past it makes
the outcome unreportable: a write_all into quinn returns Ok as soon as
the bytes are buffered and Connection::close discards that buffer, so the
bytes are neither confirmably delivered nor confirmably lost, and no count
of them adds up. Declining to write keeps the arithmetic exact — what the
peer received plus what
ImpairmentKind::QueuedBytesAtTeardown
names equals what was queued when the close was requested — which is the
property a gate can actually be written against.
Fields§
§queued: AtomicUsizeBytes queued across every live PendingQueue of this session.
idle: NotifyPulsed whenever queued reaches zero.
discarding: AtomicBoolWhether teardown flushes are declined — see the type’s own doc.
Implementations§
Source§impl EgressGauge
impl EgressGauge
Sourcepub(crate) fn is_discarding(&self) -> bool
pub(crate) fn is_discarding(&self) -> bool
Whether teardown flushes are being declined.
Sourcepub(crate) fn begin_discarding(&self)
pub(crate) fn begin_discarding(&self)
Decline every later teardown flush on this session.
One-way: nothing turns it off again, because the only caller is a close whose drain window has expired and a session in that state is on its way down.
Sourcepub(crate) async fn wait_idle(&self, timeout: Duration) -> usize
pub(crate) async fn wait_idle(&self, timeout: Duration) -> usize
Wait for the session’s queues to empty, for at most timeout.
Answers the bytes still queued when it returns: 0 when everything
drained, and the residue when the deadline won. That residue is the
figure a caller acts on — it is what will be abandoned — and it is
deliberately a byte count and not “did it time out”, because the
two differ: a queue that empties on the last poll before the
deadline drained, and one that reports zero after the deadline
drained too.
The registration is armed before the count is re-read, so a queue that empties between the two is not a lost wakeup that costs the caller its whole timeout.