pub struct ConcurrencyRamp { /* private fields */ }Expand description
Drives concurrency upward on a live transfer while it pays to do so.
Implementations§
Source§impl ConcurrencyRamp
impl ConcurrencyRamp
Sourcepub fn starting_at(min_gain_frac: f64, start: usize, max: usize) -> Self
pub fn starting_at(min_gain_frac: f64, start: usize, max: usize) -> Self
min_gain_frac is the marginal goodput, as a fraction of the
single-connection rate, that a new connection must add to be kept.
Start the search at start connections rather than at one.
§Why the search no longer climbs from one
Climbing costs a measurement window per level, and a window long enough to
outlast slow start (see WINDOW_DELTAS) is long enough that the climb dominates
a short transfer. Climbing from one is only worth it if the levels above one are
likely to be much better — and on the paths measured, they are not.
The asymmetry, not a claimed win, is what justifies starting low. Measured
over 20 paired repetitions on four objects, starting at 1 connection is
statistically indistinguishable from a fixed baseline while fixed -x 8
cost 1.37–3.04x, and on a path a single stream already saturates -x 8
incurred a 3.6x slowdown where -x 1 was 1.17x. So the downside of starting
high is large and measured; the upside is not.
Starting at one is therefore a conservative policy choice, ensuring minimal overhead while admitting more connections only when headroom is proven.
pub fn new(min_gain_frac: f64, max: usize) -> Self
Sourcepub fn arm_warmup(&mut self, now: f64, delta: f64)
pub fn arm_warmup(&mut self, now: f64, delta: f64)
Opt in to the warm-up gate, and arm it for the level the search starts at.
§The measurement this exists to prevent
The windows are scaled by delta, the per-REQUEST setup cost, which on a
pooled connection is one round trip and is measured at 50-100 ms on the paths
this was tuned against. Admitting a CONNECTION costs something else entirely:
a TCP handshake, a TLS handshake and a first byte, and on a 250 ms-RTT path
that is 1.2-1.6 s before a single byte arrives — longer than SETTLE_DELTAS
and WINDOW_DELTAS together, both of which are clamped at MAX_WINDOW_S.
The window therefore opened and closed while the new connection was still handshaking, the level measured as no better than the one below it, and the search settled at ONE on a path with real headroom. Reported from the field as “only two of eight connections start”: the second connection had delivered 240 KB — the tail of its slow start — when the ramp judged it and stopped.
A low-RTT path escapes it by luck: there the handshake fits inside the settle delay, so the same code measures a warm connection and climbs to the ceiling. That is what made this look path-specific rather than systematic.
So the settle delay cannot be a duration alone. The transport reports how many
connections are actually delivering (note_delivering), and the window does
not open until the level’s connections are among them, or until the deadline
this arms expires — a connection that never delivers must not stall the search
forever; the scheduler’s own stall detectors own that case.
Sourcepub fn note_delivering(&mut self, n: usize)
pub fn note_delivering(&mut self, n: usize)
Report how many connections are currently delivering bytes.
Aggregate count, not a set: the gate only asks whether the level it is about to measure is fully on the wire.
Sourcepub fn start(&mut self, now: f64, delta: f64)
pub fn start(&mut self, now: f64, delta: f64)
Begin the first window. now is the transfer’s clock, delta the measured
per-request setup cost.
Sourcepub fn observe(&mut self, bytes: u64, now: f64)
pub fn observe(&mut self, bytes: u64, now: f64)
Record bytes delivered by the whole transfer.
Aggregate, not per-connection: the question is whether the path is carrying more, and a per-connection view cannot answer it — on a saturated link each connection’s own rate falls as connections are added while the total stays flat, which is exactly the case the ramp must detect.
Trait Implementations§
Source§impl Clone for ConcurrencyRamp
impl Clone for ConcurrencyRamp
Source§fn clone(&self) -> ConcurrencyRamp
fn clone(&self) -> ConcurrencyRamp
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more