pub struct Scheduler {
pub stats: Stats,
/* private fields */
}Fields§
§stats: StatsImplementations§
Source§impl Scheduler
impl Scheduler
pub fn new(size: u64, sources: Vec<Source>, conns_per_source: &[usize]) -> Self
Sourcepub fn set_active_limit(&mut self, n: usize)
pub fn set_active_limit(&mut self, n: usize)
Cap how many connections may hold work at once, adjustable mid-transfer.
§Why the concurrency search belongs here and not in a probe
Finding the useful connection count by probing — fetch a slab with one connection, then with two, then three, comparing goodput — is the standard approach and it is what this client did. HARP (Kim, Yildirim, Kosar, SC’16) names the cost directly: probing “may bring too much probing overhead”, because the samples are extra transfers whose price is paid before the real one starts. Measured here on a 3.15 MB object over a live path, the climbing probe made the transfer 1.96x slower than not probing at all (paired over 9 interleaved reps, p = 0.004) — the search cost more than the concurrency it found could save.
The probe is only necessary because concurrency is fixed when the transfer starts. Make it adjustable and the same search runs on the real transfer: start at one connection, measure aggregate goodput over a short window, admit another connection while the marginal gain justifies it, and stop. Every byte moved during the search is a byte of the object, so the search is free — the object had to be fetched anyway. What HARP buys with a historical corpus, this buys by putting the measurement in-band.
Connections above the limit stay dormant: they are not given work and open no socket. Raising the limit lets the next tick hand them work through the ordinary work-conserving path, so no new admission machinery is needed.
Sourcepub fn active_limit(&self) -> usize
pub fn active_limit(&self) -> usize
The current concurrency cap.
Sourcepub fn all_sources_suspended_until(&self, now: f64) -> Option<f64>
pub fn all_sources_suspended_until(&self, now: f64) -> Option<f64>
When every source is deliberately suspended, the earliest time one returns.
None means at least one source is usable now, so a lack of progress is a
genuine stall. Some(t) means the scheduler has chosen to pause every
source until t — nothing can move before then, and that silence is planned
rather than pathological.
§Why a caller must consult this
The transport’s no-progress watchdog exists to fail a transfer where nothing
will ever happen again. A scheduled retry is the opposite of that, and
conflating the two is not hypothetical: with one source (the common case —
one URL, one CDN), stall_timeout 4.0s gives a watchdog of
4 * (4.0 + delta) = 16.2s, while five consecutive stalls suspend that sole
source for min(4.0 * 2^3, 30) = 30s. The transfer is then killed at 16.2s
for failing to make progress it had itself forbidden.
Measured consequence on a 121.7 MiB GitHub release asset: 4 of 8 runs at
-x 8/-x 16 aborted with a digest mismatch, three of them having already
received 126.9-127.0 MB of 127.6 MB — 99.6% complete, killed during a
deliberate backoff over the last half-megabyte.
Sourcepub fn unassigned_is_empty(&self) -> bool
pub fn unassigned_is_empty(&self) -> bool
Whether any work is still unclaimed by any connection.
Exposed so the ramp’s contract is testable: while concurrency is below the budget, work must remain here for connections admitted later to pick up.
Sourcepub fn busy_conns(&self) -> usize
pub fn busy_conns(&self) -> usize
How many connections currently hold a range.
Sourcepub fn with_active_limit(self, n: usize) -> Self
pub fn with_active_limit(self, n: usize) -> Self
Start with only n connections active, ramping up from there.
pub fn with_theta_scale(self, s: f64) -> Self
Sourcepub fn with_health_ranking(self, on: bool) -> Self
pub fn with_health_ranking(self, on: bool) -> Self
Disable health-ranked victim selection (for A/B measurement only).
pub fn with_stall_timeout(self, t: f64) -> Self
Sourcepub fn mark_done(&mut self, lo: u64, hi: u64)
pub fn mark_done(&mut self, lo: u64, hi: u64)
Mark [lo, hi) as already held, for resuming a partial transfer.
Must be called before the first tick: the initial split assigns all
unassigned work, and bytes already on disk must not be part of it.
Sourcepub fn conn_health(&self, j: usize) -> Health
pub fn conn_health(&self, j: usize) -> Health
Health grade of a connection, for the progress UI and for tests.
Sourcepub fn conn_source(&self, j: usize) -> usize
pub fn conn_source(&self, j: usize) -> usize
Source index a connection belongs to, for the progress UI.
Sourcepub fn conn_rate(&self, j: usize) -> f64
pub fn conn_rate(&self, j: usize) -> f64
Smoothed rate estimate of a connection (bytes/s), for the progress UI.
Sourcepub fn conn_range(&self, j: usize) -> Option<(u64, u64, u64)>
pub fn conn_range(&self, j: usize) -> Option<(u64, u64, u64)>
Active range of a connection, for the progress UI.
pub fn n_conns(&self) -> usize
pub fn is_complete(&self) -> bool
pub fn bytes_held(&self) -> u64
Sourcepub fn held_ranges(&self) -> Vec<(u64, u64)>
pub fn held_ranges(&self) -> Vec<(u64, u64)>
The ranges that are complete on disk, as (lo, hi) pairs.
This is the complement of the unassigned set minus what is still in flight, and it is what a resume record must contain. Reporting only a byte COUNT is not enough: positioned writes land ranges out of order, so “2 MB held” says nothing about which 2 MB, and a resume that assumed a contiguous prefix would skip holes and silently corrupt the file.
Sourcepub fn worst_delta(&self) -> f64
pub fn worst_delta(&self) -> f64
Coverage audit: held + outstanding + unassigned == size.
This is a SAFETY invariant and it does NOT imply liveness – the
livelock this code is written to avoid (a fully-stolen range leaving a
connection idle with a non-empty queue) satisfies it at every instant.
liveness_holds is the property that matters.
The largest measured request setup cost across sources, in seconds.
Exposed because a transport-layer watchdog must express its patience in
units of what a request actually costs on this path rather than as a
hardcoded constant: delta differs by an order of magnitude between a
LAN mirror and a TLS connection through a proxy, and a fixed timeout is
either trigger-happy on the slow path or useless on the fast one.
This is the same quantity the repair deadband is built from
(theta = scale * sqrt(delta * T_rem / n)), so a client that widens
delta widens both together, which is the intended coupling.
Sourcepub fn stall_timeout(&self) -> f64
pub fn stall_timeout(&self) -> f64
The configured stall timeout, in seconds.
pub fn coverage_holds(&self) -> bool
Sourcepub fn liveness_holds(&self) -> bool
pub fn liveness_holds(&self) -> bool
True when some enabled transition strictly decreases the unheld-byte count. False means the scheduler is stuck.
Sourcepub fn on_bytes(&mut self, conn: usize, n: u64, now: f64, dt: f64)
pub fn on_bytes(&mut self, conn: usize, n: u64, now: f64, dt: f64)
Record n bytes arriving on conn at time now over dt seconds.
Convenience wrapper that assumes the arrival is contiguous at the
connection’s cursor. Real transports must use Scheduler::on_bytes_at:
a response still draining from a range that was completed or stolen would
otherwise be credited against whatever range the connection holds NOW,
silently advancing a cursor over bytes that never arrived and leaving a
hole of zeros in the output file.
Sourcepub fn on_bytes_at(&mut self, conn: usize, off: u64, n: u64, now: f64, dt: f64)
pub fn on_bytes_at(&mut self, conn: usize, off: u64, n: u64, now: f64, dt: f64)
Record n bytes that landed at absolute offset off.
Arrivals that do not begin exactly at the connection’s cursor are stale (they belong to a superseded request) and are discarded: the bytes are still written to the file by the transport, but they are not credited, so the scheduler’s coverage accounting stays exact.
Sourcepub fn suspend_source(&mut self, src: usize, until: f64)
pub fn suspend_source(&mut self, src: usize, until: f64)
Suspend a source (429/503 with Retry-After) and reclaim its ranges.