use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, PoisonError};
use std::time::Duration;
use alloy::primitives::{Address, U256};
use bao_tree::ChunkRanges;
use decdn_bao_range::{AlignedRange, align_range};
use decdn_protocol::{Coverage, num_blocks};
use futures_util::FutureExt as _;
use futures_util::StreamExt as _;
use futures_util::stream::FuturesUnordered;
use tokio::sync::{Mutex as AsyncMutex, Notify};
use tokio::time::Instant;
use crate::coverage_plan::{
SourceCoverage, covered_part, covered_suffix_start, covers_byte_range, spread_segments,
};
use crate::credential::{
CredentialSlot, CredentialView, FundingNeeded, QuoteMax, RunDemand, unix_now,
};
use crate::driver::{
DriveCounters, PRESENT_RECORD_FLUSH_INTERVAL, PacingWait, SharedPool, UnitProgress, WaitReason,
contiguous_byte_ranges, fill_gap, ranges_content_len,
};
use crate::fault::Fault;
use crate::health::PeerHealth;
use crate::ledgers::LaneLedgers;
use crate::pacer::DownstreamFrontier;
use crate::recovery::{RecoveryGate, SwapStep, after_step};
use crate::segment::{split_evenly, steal_split, without_runs};
use crate::source::{BlobSource, Funder, IngestStore, SourceFuture, SourceStream};
use crate::source_set::{Holder, NoAffordableSource, SourceProvider, SourceSet};
use crate::stop::StopPolicy;
use crate::streamer::StreamCandidate;
use crate::{Pacer, PoolContext, PoolLedger};
pub const LANE_WATCHDOG: Duration = Duration::from_secs(10);
const FIRST_BYTE_GRACE: Duration = Duration::from_secs(30);
pub const GROWTH_RETRY: Duration = Duration::from_secs(1);
const EXTRA_RETRY_CAP: Duration = Duration::from_secs(30);
const SLOW_VICTIM_FACTOR: u64 = 4;
const SLOW_VICTIM_EVIDENCE: Duration = FIRST_BYTE_GRACE;
const STEAL_RECHECK: Duration = Duration::from_secs(5);
const EXTRA_REFUSALS_LOGGED: u32 = 3;
const WAITING_RELOG: Duration = Duration::from_secs(30);
pub(crate) const SEED: u64 = decdn_protocol::DISCOVERY_BLOCK_BYTES;
#[must_use]
pub(crate) fn grown_bound(bound: u64, c0: u64, extra: u64) -> u64 {
let want = bound
.saturating_add(SEED)
.max(c0.saturating_add(extra.saturating_mul(2)));
want.div_ceil(SEED).saturating_mul(SEED)
}
fn known_end<St: IngestStore>(store: &St, c0: u64) -> u64 {
store
.proven()
.unwrap_or_else(|| c0.min(store.total_bytes()))
}
fn bytes_past(present: &ChunkRanges, from: u64, bound: u64) -> u64 {
contiguous_byte_ranges(present, bound)
.into_iter()
.map(|(start, len)| start.saturating_add(len).saturating_sub(start.max(from)))
.fold(0, u64::saturating_add)
}
async fn store_missing<St>(store: &St, start: u64, len: u64) -> anyhow::Result<ChunkRanges>
where
St: IngestStore,
{
crate::driver::missing_below_bound(store, start, len)
.await
.map(|(missing, _bound)| missing)
}
async fn steal_recheck<St>(
store: &St,
work: &AsyncMutex<Work>,
i: usize,
coverage: &Coverage,
) -> anyhow::Result<()>
where
St: IngestStore,
{
loop {
tokio::time::sleep(STEAL_RECHECK).await;
let total_bytes = store.total_bytes();
let missing =
contiguous_byte_ranges(&store_missing(store, 0, total_bytes).await?, total_bytes);
if work
.lock()
.await
.steal_due(i, total_bytes, coverage, &missing)?
{
return Ok(());
}
}
}
pub struct AcquireTarget<'a, St> {
pub store: &'a St,
pub hash: [u8; 32],
pub total_bytes: u64,
pub ranges: &'a [(u64, u64)],
}
impl<St> std::fmt::Debug for AcquireTarget<'_, St> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AcquireTarget")
.field("total_bytes", &self.total_bytes)
.field("ranges", &self.ranges)
.finish_non_exhaustive()
}
}
pub struct AcquireEnv<'a, Pc, F> {
pub pacer: &'a Pc,
pub funder: &'a F,
pub recovery: &'a RecoveryGate,
pub credentials: Option<&'a CredentialSlot>,
pub max_lanes: usize,
pub stop: &'a StopPolicy,
pub on_progress: Option<&'a (dyn Fn(u64, u64) + Send + Sync + 'a)>,
pub ledgers: Option<&'a LaneLedgers>,
pub pacing: Option<&'a ConsumptionPacing<'a>>,
pub max_blob_bytes: u64,
pub run: Option<&'a RunDemand>,
}
impl<Pc, F> std::fmt::Debug for AcquireEnv<'_, Pc, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AcquireEnv")
.field("max_lanes", &self.max_lanes)
.field("stop", &self.stop)
.field("paced", &self.pacing.is_some())
.finish_non_exhaustive()
}
}
#[derive(Default)]
pub struct LaneLease(Mutex<Option<Box<dyn Send + Sync>>>);
impl LaneLease {
pub fn new(held: impl Send + Sync + 'static) -> Self {
Self(Mutex::new(Some(Box::new(held))))
}
pub(crate) fn is_held(&self) -> bool {
self.0
.lock()
.unwrap_or_else(PoisonError::into_inner)
.is_some()
}
pub(crate) fn release(&self) {
let held = self.0.lock().unwrap_or_else(PoisonError::into_inner).take();
drop(held);
}
}
impl std::fmt::Debug for LaneLease {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let held = self.0.lock().is_ok_and(|held| held.is_some());
f.debug_struct("LaneLease").field("held", &held).finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GrowFor {
Restart,
Extra,
}
pub struct LaneWiden {
grow: Box<dyn Fn(GrowFor) -> bool + Send + Sync>,
release: Box<dyn Fn() + Send + Sync>,
}
impl LaneWiden {
pub fn new(
grow: impl Fn(GrowFor) -> bool + Send + Sync + 'static,
release: impl Fn() + Send + Sync + 'static,
) -> Self {
Self {
grow: Box::new(grow),
release: Box::new(release),
}
}
pub(crate) fn grow(&self, kind: GrowFor) -> bool {
(self.grow)(kind)
}
pub(crate) fn release(&self) {
(self.release)();
}
}
impl std::fmt::Debug for LaneWiden {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("LaneWiden")
}
}
struct ReleaseGrant<S>(Arc<StreamCandidate<S>>);
impl<S> Drop for ReleaseGrant<S> {
fn drop(&mut self) {
if let Some(widen) = &self.0.widen {
widen.release();
}
}
}
struct ReleaseLease<S>(Arc<StreamCandidate<S>>);
impl<S> Drop for ReleaseLease<S> {
fn drop(&mut self) {
if self.0.widen.is_some() {
self.0.lease.release();
}
}
}
#[expect(
dead_code,
reason = "an extra worker's grant is never read: it acts only when the worker's future \
drops it"
)]
enum Hold<S> {
Own {
lease: ReleaseLease<S>,
grant: Option<ReleaseGrant<S>>,
},
Extra(ReleaseGrant<S>),
}
impl<S> Hold<S> {
fn park(&mut self) {
if let Self::Own { lease, grant } = self
&& lease.0.widen.is_some()
{
lease.0.lease.release();
*grant = None;
}
}
fn stream(&mut self) -> bool {
match self {
Self::Extra(_) => true,
Self::Own { lease, grant } => {
let lane = &lease.0;
if lane.widen.is_none() || lane.lease.is_held() || grant.is_some() {
return true;
}
let granted = lane
.widen
.as_ref()
.is_some_and(|widen| widen.grow(GrowFor::Restart));
if granted {
*grant = Some(ReleaseGrant(Arc::clone(lane)));
}
granted
}
}
}
}
struct Picked {
range: AlignedRange,
uncovered: bool,
unit: Arc<Unit>,
}
#[derive(Debug)]
struct Unit {
progress: UnitProgress,
stop_at: AtomicU64,
parked: AtomicU64,
park_open_at: AtomicU64,
}
impl Unit {
fn new() -> Self {
Self {
progress: UnitProgress::default(),
stop_at: AtomicU64::new(u64::MAX),
parked: AtomicU64::new(0),
park_open_at: AtomicU64::new(u64::MAX),
}
}
fn rate(&self) -> Option<u64> {
self.rate_sample().map(|(rate, _)| rate)
}
fn rate_sample(&self) -> Option<(u64, Duration)> {
let first = self.progress.first_byte.get()?;
let verified = self.progress.verified.load(Ordering::Relaxed);
let since_first = first.elapsed();
let open = match self.park_open_at.load(Ordering::Acquire) {
u64::MAX => Duration::ZERO,
at => since_first.saturating_sub(Duration::from_nanos(at)),
};
let parked = Duration::from_nanos(self.parked.load(Ordering::Relaxed));
let active = since_first.saturating_sub(parked).saturating_sub(open);
let millis = active.as_millis();
if verified == 0 || millis == 0 {
return None;
}
let rate = u64::try_from(u128::from(verified).saturating_mul(1000) / millis).ok()?;
Some((rate, active))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum StealKind {
CoveredSuffix,
SlowVictim,
}
impl StealKind {
const fn as_str(self) -> &'static str {
match self {
Self::CoveredSuffix => "covered_suffix",
Self::SlowVictim => "slow_victim",
}
}
}
#[derive(Debug)]
struct PlannedSteal {
victim: usize,
tail: AlignedRange,
kind: StealKind,
}
struct CancelHandle {
flag: AtomicBool,
notify: Notify,
}
impl CancelHandle {
fn new() -> Self {
Self {
flag: AtomicBool::new(false),
notify: Notify::new(),
}
}
}
async fn cancelled(handle: &CancelHandle) {
loop {
handle.notify.notified().await;
if handle.flag.load(Ordering::Acquire) {
return;
}
}
}
struct RunSeed {
offset: u64,
len: u64,
max_pieces: usize,
pieces: usize,
}
async fn missing_bytes<St>(store: &St, start: u64, len: u64) -> anyhow::Result<u64>
where
St: IngestStore,
{
let ranges = store_missing(store, start, len).await?;
Ok(contiguous_byte_ranges(&ranges, store.total_bytes())
.iter()
.map(|(_, l)| *l)
.fold(0, u64::saturating_add))
}
async fn missing_chunks<St>(store: &St, ranges: &[(u64, u64)]) -> anyhow::Result<ChunkRanges>
where
St: IngestStore,
{
let mut missing = ChunkRanges::empty();
for &(start, len) in ranges {
missing |= store_missing(store, start, len).await?;
}
Ok(missing)
}
async fn watchdog<St>(
store: &St,
start: u64,
len: u64,
deadline: Duration,
verified: &AtomicU64,
) -> Option<anyhow::Error>
where
St: IngestStore,
{
if deadline.is_zero() {
return std::future::pending().await;
}
let mut prev = verified.load(Ordering::Relaxed);
let mut window = deadline.max(FIRST_BYTE_GRACE);
loop {
tokio::time::sleep(window).await;
window = deadline;
let now = verified.load(Ordering::Relaxed);
if now != prev {
prev = now;
continue;
}
match missing_bytes(store, start, len).await {
Ok(0) => {}
Ok(_) => return None,
Err(err) => return Some(err),
}
}
}
enum UnitOutcome {
Completed,
Cancelled,
Faulted(Option<anyhow::Error>),
}
enum LaneEnd {
Idle,
Faulted {
err: Option<anyhow::Error>,
range: crate::source_set::LaneRange,
piece_at: u64,
at: Instant,
},
Retired,
}
struct WorkerEnd {
provider: Address,
end: LaneEnd,
delivered: bool,
pulled_through: Option<Instant>,
covered_served: Vec<(u64, u64)>,
extra: bool,
}
struct Work {
pending: VecDeque<AlignedRange>,
in_flight: Vec<Option<(u64, u64)>>,
cancel: Vec<Arc<CancelHandle>>,
live: Vec<Arc<Unit>>,
rates: Vec<Option<u64>>,
units: Vec<u64>,
alive: Vec<bool>,
coverage: Vec<Coverage>,
measured: Vec<bool>,
lane_of: Vec<usize>,
extra: Vec<bool>,
providers: Vec<Option<Address>>,
no_uncovered: Vec<bool>,
front_first: bool,
}
impl Work {
const fn new(pending: VecDeque<AlignedRange>, front_first: bool) -> Self {
Self {
pending,
in_flight: Vec::new(),
cancel: Vec::new(),
live: Vec::new(),
rates: Vec::new(),
units: Vec::new(),
alive: Vec::new(),
coverage: Vec::new(),
measured: Vec::new(),
lane_of: Vec::new(),
extra: Vec::new(),
providers: Vec::new(),
no_uncovered: Vec::new(),
front_first,
}
}
fn set_provider(&mut self, slot: usize, provider: Address) {
if let Some(named) = self.providers.get_mut(slot) {
*named = Some(provider);
}
}
fn set_no_uncovered(&mut self, lane: usize, barred: bool) {
if let Some(flag) = self.no_uncovered.get_mut(lane) {
*flag = barred;
}
}
fn set_coverage(&mut self, lane: usize, coverage: &Coverage) {
for i in 0..self.coverage.len() {
if self.lane_of.get(i) == Some(&lane)
&& self.measured.get(i) == Some(&true)
&& let Some(c) = self.coverage.get_mut(i)
&& c != coverage
{
c.clone_from(coverage);
}
}
}
fn barred(&self, i: usize) -> bool {
self.lane_of
.get(i)
.and_then(|&lane| self.no_uncovered.get(lane))
.copied()
.unwrap_or(false)
}
fn add_lane(&mut self, coverage: Option<Coverage>, total_bytes: u64) -> usize {
let slot = self.in_flight.len();
self.in_flight.push(None);
self.cancel.push(Arc::new(CancelHandle::new()));
self.push_unit_state();
self.units.push(0);
self.alive.push(true);
self.measured.push(coverage.is_some());
self.coverage
.push(coverage.unwrap_or_else(|| Coverage::full(num_blocks(total_bytes))));
self.lane_of.push(slot);
self.extra.push(false);
self.providers.push(None);
self.no_uncovered.push(false);
slot
}
fn add_extra(&mut self, lane: usize) -> usize {
let ended = (0..self.in_flight.len()).find(|&i| {
self.extra.get(i) == Some(&true)
&& self.lane_of.get(i) == Some(&lane)
&& self.alive.get(i) == Some(&false)
});
if let Some(slot) = ended {
let coverage = self
.coverage
.get(lane)
.cloned()
.unwrap_or_else(Coverage::empty);
let measured = self.measured.get(lane).copied().unwrap_or(true);
let provider = self.providers.get(lane).copied().flatten();
if let Some(held) = self.in_flight.get_mut(slot) {
*held = None;
}
if let Some(handle) = self.cancel.get_mut(slot) {
*handle = Arc::new(CancelHandle::new());
}
if let Some(rate) = self.rates.get_mut(slot) {
*rate = None;
}
if let Some(alive) = self.alive.get_mut(slot) {
*alive = true;
}
if let Some(m) = self.measured.get_mut(slot) {
*m = measured;
}
if let Some(c) = self.coverage.get_mut(slot) {
*c = coverage;
}
if let Some(p) = self.providers.get_mut(slot) {
*p = provider;
}
return slot;
}
let slot = self.in_flight.len();
self.in_flight.push(None);
self.cancel.push(Arc::new(CancelHandle::new()));
self.push_unit_state();
self.units.push(0);
self.alive.push(true);
self.measured
.push(self.measured.get(lane).copied().unwrap_or(true));
self.coverage.push(
self.coverage
.get(lane)
.cloned()
.unwrap_or_else(Coverage::empty),
);
self.lane_of.push(lane);
self.extra.push(true);
self.providers
.push(self.providers.get(lane).copied().flatten());
self.no_uncovered.push(false);
slot
}
fn push_unit_state(&mut self) {
self.live.push(Arc::new(Unit::new()));
self.rates.push(None);
}
fn record_rate(&mut self, i: usize, rate: Option<u64>) {
if let (Some(slot), Some(rate)) = (self.rates.get_mut(i), rate) {
*slot = Some(rate);
}
}
fn end_extra(&mut self, i: usize) {
self.park(i);
}
fn growth_wanted(
&self,
total_bytes: u64,
can_grow: impl Fn(usize) -> bool,
) -> Vec<(AlignedRange, Vec<usize>)> {
let covers = |slot: usize, seg: &AlignedRange| {
self.coverage
.get(slot)
.is_some_and(|c| !covered_part(c, seg.chunk_ranges(), total_bytes).is_empty())
};
let mut idle: Vec<usize> = (0..self.in_flight.len())
.filter(|&i| {
self.alive.get(i) == Some(&true)
&& self.in_flight.get(i).is_some_and(Option::is_none)
})
.collect();
let mut wanted = Vec::new();
for seg in &self.pending {
let orphan = !self.uncovered_part(seg, total_bytes).is_empty();
if let Some(pos) = idle
.iter()
.position(|&i| (orphan && !self.barred(i)) || covers(i, seg))
{
idle.swap_remove(pos);
continue;
}
let candidates = (0..self.in_flight.len())
.filter(|&lane| {
self.extra.get(lane) == Some(&false)
&& self.alive.get(lane) == Some(&true)
&& self.in_flight.get(lane).is_some_and(Option::is_some)
&& can_grow(lane)
&& ((orphan && !self.barred(lane)) || covers(lane, seg))
})
.collect();
wanted.push((seg.clone(), candidates));
}
wanted
}
fn regrow(&mut self, missing: &ChunkRanges, total_bytes: u64) -> anyhow::Result<()> {
self.grow_coverage(total_bytes);
self.pending = plan_pending(missing, total_bytes, &self.coverage)?;
Ok(())
}
fn grow_coverage(&mut self, total_bytes: u64) {
for (coverage, &measured) in self.coverage.iter_mut().zip(&self.measured) {
if !measured {
*coverage = Coverage::full(num_blocks(total_bytes));
}
}
}
fn clip(&mut self, total_bytes: u64) -> anyhow::Result<()> {
let mut kept = VecDeque::with_capacity(self.pending.len());
for seg in self.pending.drain(..) {
let start = seg.fetch_start();
if start >= total_bytes {
continue;
}
if seg.fetch_end() > total_bytes {
kept.push_back(align_range(start, total_bytes - start, total_bytes)?);
} else {
kept.push_back(seg);
}
}
self.pending = kept;
for i in 0..self.in_flight.len() {
let Some(Some((start, len))) = self.in_flight.get(i).copied() else {
continue;
};
if start >= total_bytes {
if let Some(unit) = self.units.get(i).copied() {
self.cancel_victim(i, unit);
}
} else if start.saturating_add(len) > total_bytes
&& let Some(Some(slot)) = self.in_flight.get_mut(i)
{
slot.1 = total_bytes - start;
}
}
Ok(())
}
fn park(&mut self, i: usize) {
if let Some(alive) = self.alive.get_mut(i) {
*alive = false;
}
}
fn revive(&mut self, i: usize) {
if let Some(alive) = self.alive.get_mut(i) {
*alive = true;
}
}
fn busy(&self) -> bool {
self.in_flight.iter().any(Option::is_some)
}
fn has_work_for(&self, coverage: &Coverage, total_bytes: u64, take_uncovered: bool) -> bool {
if self
.pending
.iter()
.any(|seg| !covered_part(coverage, seg.chunk_ranges(), total_bytes).is_empty())
|| (take_uncovered && self.uncovered(total_bytes))
{
return true;
}
let mut in_flight: Vec<(u64, u64)> = self.in_flight.iter().flatten().copied().collect();
in_flight.sort_unstable();
matches!(
self.plan_steal(total_bytes, coverage, &in_flight, None, None),
Ok(Some(_))
)
}
fn steal_due(
&self,
i: usize,
total_bytes: u64,
coverage: &Coverage,
missing: &[(u64, u64)],
) -> anyhow::Result<bool> {
let (stealer_rate, slow_from) = self.stealer(i);
Ok(self
.plan_steal(total_bytes, coverage, missing, stealer_rate, slow_from)?
.is_some())
}
fn stealer(&self, i: usize) -> (Option<u64>, Option<usize>) {
let stealer_rate = self.rates.get(i).copied().flatten();
(stealer_rate, (!self.barred(i)).then_some(i))
}
fn slow_victim(&self, owner: usize, i: usize, stealer_rate: u64) -> bool {
let (Some(lane), Some(own_lane)) = (self.lane_of.get(owner), self.lane_of.get(i)) else {
return false;
};
if lane == own_lane {
return false;
}
let Some((rate, over)) = self.live.get(owner).and_then(|unit| unit.rate_sample()) else {
return false;
};
over >= SLOW_VICTIM_EVIDENCE
&& rate.max(1).saturating_mul(SLOW_VICTIM_FACTOR) <= stealer_rate
}
fn uncovered_part(&self, seg: &AlignedRange, total_bytes: u64) -> ChunkRanges {
let mut held = ChunkRanges::empty();
for (_, coverage) in self
.alive
.iter()
.zip(&self.coverage)
.filter(|&(&alive, _)| alive)
{
held |= covered_part(coverage, seg.chunk_ranges(), total_bytes);
}
seg.chunk_ranges().clone() - held
}
fn uncovered(&self, total_bytes: u64) -> bool {
self.pending
.iter()
.any(|seg| !self.uncovered_part(seg, total_bytes).is_empty())
}
fn seed(
&mut self,
missing: &ChunkRanges,
total_bytes: u64,
coverages: &[Option<Coverage>],
) -> anyhow::Result<Vec<usize>> {
let slots: Vec<usize> = coverages
.iter()
.map(|coverage| self.add_lane(coverage.clone(), total_bytes))
.collect();
let planned: Vec<Coverage> = slots
.iter()
.filter_map(|&slot| self.coverage.get(slot).cloned())
.collect();
self.pending = plan_pending(missing, total_bytes, &planned)?;
Ok(slots)
}
fn slot_mut(&mut self, i: usize) -> anyhow::Result<&mut Option<(u64, u64)>> {
match self.in_flight.get_mut(i) {
Some(slot) => Ok(slot),
None => anyhow::bail!("worker index {i} out of range for in-flight slots"),
}
}
fn pick(
&mut self,
i: usize,
total_bytes: u64,
coverage: &Coverage,
steal: bool,
missing: &[(u64, u64)],
) -> anyhow::Result<Option<Picked>> {
match self.cancel.get(i) {
Some(handle) => handle.flag.store(false, Ordering::Release),
None => anyhow::bail!("worker index {i} out of range for cancel handles"),
}
match self.units.get_mut(i) {
Some(unit) => *unit = unit.wrapping_add(1),
None => anyhow::bail!("worker index {i} out of range for unit counters"),
}
let unit = Arc::new(Unit::new());
match self.live.get_mut(i) {
Some(live) => *live = Arc::clone(&unit),
None => anyhow::bail!("worker index {i} out of range for unit state"),
}
let covered = |seg: &AlignedRange| {
covers_byte_range(coverage, seg.fetch_start(), seg.fetch_len(), total_bytes)
};
let coverable = if self.front_first {
self.pending
.iter()
.enumerate()
.filter(|(_, seg)| covered(seg))
.min_by_key(|(_, seg)| seg.fetch_start())
.map(|(pos, _)| pos)
} else {
self.pending.iter().position(covered)
};
if let Some(pos) = coverable {
if let Some(seg) = self.pending.remove(pos) {
*self.slot_mut(i)? = Some((seg.fetch_start(), seg.fetch_len()));
return Ok(Some(Picked {
range: seg,
uncovered: false,
unit,
}));
}
}
let taken = match self.take_part(total_bytes, |_, seg| {
covered_part(coverage, seg.chunk_ranges(), total_bytes)
})? {
Some(part) => Some((part, false)),
None if self.barred(i) => None,
None => self
.take_part(total_bytes, |w, seg| w.uncovered_part(seg, total_bytes))?
.map(|part| (part, true)),
};
if let Some((picked, uncovered)) = taken {
*self.slot_mut(i)? = Some((picked.fetch_start(), picked.fetch_len()));
return Ok(Some(Picked {
range: picked,
uncovered,
unit,
}));
}
if !steal {
*self.slot_mut(i)? = None;
return Ok(None);
}
self.steal(i, total_bytes, coverage, missing, unit)
}
fn plan_steal(
&self,
total_bytes: u64,
coverage: &Coverage,
missing: &[(u64, u64)],
stealer_rate: Option<u64>,
slow_from: Option<usize>,
) -> anyhow::Result<Option<PlannedSteal>> {
let (owners, remaining): (Vec<usize>, Vec<(u64, u64)>) = self
.in_flight
.iter()
.enumerate()
.filter_map(|(idx, slot)| slot.map(|r| (idx, r)))
.unzip();
let received: Vec<(u64, u64)> = owners
.iter()
.zip(&remaining)
.filter_map(|(&owner, &(start, _))| {
let frontier = self
.live
.get(owner)?
.progress
.frontier
.load(Ordering::SeqCst);
(frontier > start).then(|| (start, frontier - start))
})
.collect();
let missing = without_runs(missing, &received);
let victim_rate = |k: usize| {
owners
.get(k)
.and_then(|&owner| self.live.get(owner))
.and_then(|unit| unit.rate())
};
let covered = steal_split(
&remaining,
&missing,
total_bytes,
|s, l| covered_suffix_start(coverage, s, l, total_bytes),
stealer_rate,
victim_rate,
)?;
if let Some((k, tail)) = covered {
return Ok(owners.get(k).map(|&victim| PlannedSteal {
victim,
tail,
kind: StealKind::CoveredSuffix,
}));
}
let (Some(i), Some(rate)) = (slow_from, stealer_rate) else {
return Ok(None);
};
let slow: Vec<(usize, (u64, u64))> = owners
.iter()
.zip(&remaining)
.enumerate()
.filter(|&(_, (&owner, _))| self.slow_victim(owner, i, rate))
.map(|(k, (_, &range))| (k, range))
.collect();
let slow_remaining: Vec<(u64, u64)> = slow.iter().map(|&(_, range)| range).collect();
let planned = steal_split(
&slow_remaining,
&missing,
total_bytes,
|s, _| Some(s),
stealer_rate,
|j| slow.get(j).and_then(|&(k, _)| victim_rate(k)),
)?;
Ok(planned.and_then(|(j, tail)| {
let victim = *owners.get(slow.get(j)?.0)?;
Some(PlannedSteal {
victim,
tail,
kind: StealKind::SlowVictim,
})
}))
}
fn steal(
&mut self,
i: usize,
total_bytes: u64,
coverage: &Coverage,
missing: &[(u64, u64)],
unit: Arc<Unit>,
) -> anyhow::Result<Option<Picked>> {
let (stealer_rate, slow_from) = self.stealer(i);
let Some(PlannedSteal { victim, tail, kind }) =
self.plan_steal(total_bytes, coverage, missing, stealer_rate, slow_from)?
else {
*self.slot_mut(i)? = None;
return Ok(None);
};
let Some(victim_unit) = self.live.get(victim).map(Arc::clone) else {
*self.slot_mut(i)? = None;
return Ok(None);
};
let trimmed = match self.in_flight.get_mut(victim) {
Some(Some((start, len))) if victim != i && tail.fetch_start() > *start => {
let end = start.saturating_add(*len);
*len = tail.fetch_start() - *start;
Some((*start, end))
}
_ => None,
};
let Some((victim_start, victim_end)) = trimmed else {
*self.slot_mut(i)? = None;
return Ok(None);
};
victim_unit
.stop_at
.fetch_min(tail.fetch_start(), Ordering::SeqCst);
let frontier = victim_unit.progress.frontier.load(Ordering::SeqCst);
let tail = if frontier < tail.fetch_start() {
tail
} else if frontier >= tail.fetch_end() {
if let Some(Some((_, len))) = self.in_flight.get_mut(victim) {
*len = victim_end - victim_start;
}
*self.slot_mut(i)? = None;
return Ok(None);
} else {
if let Some(Some((_, len))) = self.in_flight.get_mut(victim) {
*len = frontier - victim_start;
}
align_range(frontier, tail.fetch_end() - frontier, total_bytes)?
};
let stealer = self.providers.get(i).copied().flatten();
let victim_provider = self.providers.get(victim).copied().flatten();
let victim_range = self.in_flight.get(victim).copied().flatten();
let victim_rate = victim_unit.rate();
match kind {
StealKind::CoveredSuffix => tracing::debug!(
stealer = ?stealer,
victim = ?victim_provider,
kind = kind.as_str(),
split = tail.fetch_start(),
stolen = tail.fetch_len(),
victim_range = ?victim_range,
victim_frontier = frontier,
stealer_rate,
victim_rate,
"stole the tail of a lane's missing remainder"
),
StealKind::SlowVictim => tracing::info!(
stealer = ?stealer,
victim = ?victim_provider,
kind = kind.as_str(),
split = tail.fetch_start(),
stolen = tail.fetch_len(),
victim_range = ?victim_range,
victim_frontier = frontier,
stealer_rate,
victim_rate,
"stole the tail of a lane's missing remainder"
),
}
let uncovered =
!covers_byte_range(coverage, tail.fetch_start(), tail.fetch_len(), total_bytes);
*self.slot_mut(i)? = Some((tail.fetch_start(), tail.fetch_len()));
Ok(Some(Picked {
range: tail,
uncovered,
unit,
}))
}
fn take_part(
&mut self,
total_bytes: u64,
part_of: impl Fn(&Self, &AlignedRange) -> ChunkRanges,
) -> anyhow::Result<Option<AlignedRange>> {
let mut parts = self.pending.iter().enumerate().filter_map(|(pos, seg)| {
let part = part_of(self, seg);
let &(start, len) = contiguous_byte_ranges(&part, total_bytes).first()?;
Some((pos, start, len))
});
let found = if self.front_first {
parts.min_by_key(|&(_, start, _)| start)
} else {
parts.next()
};
let Some((pos, start, len)) = found else {
return Ok(None);
};
let take = align_range(start, len, total_bytes)?;
let Some(seg) = self.pending.remove(pos) else {
return Ok(None);
};
let rest = seg.chunk_ranges().clone() - take.chunk_ranges();
for (k, (s, l)) in contiguous_byte_ranges(&rest, total_bytes)
.into_iter()
.enumerate()
{
self.pending
.insert(pos.saturating_add(k), align_range(s, l, total_bytes)?);
}
Ok(Some(take))
}
fn cancel_victim(&self, victim: usize, unit: u64) {
let same_unit = self.units.get(victim) == Some(&unit)
&& self.in_flight.get(victim).is_some_and(Option::is_some);
if !same_unit {
tracing::debug!(victim, unit, "the unit to cancel has ended; cancel skipped");
return;
}
if let Some(handle) = self.cancel.get(victim) {
handle.flag.store(true, Ordering::Release);
handle.notify.notify_one();
}
}
fn earlier_pending(&self, i: usize, coverage: &Coverage, total_bytes: u64) -> bool {
let Some(Some((start, _))) = self.in_flight.get(i) else {
return false;
};
self.pending.iter().any(|seg| {
seg.fetch_start() < *start
&& covers_byte_range(coverage, seg.fetch_start(), seg.fetch_len(), total_bytes)
})
}
fn clear(&mut self, i: usize) -> anyhow::Result<()> {
*self.slot_mut(i)? = None;
Ok(())
}
}
pub(crate) fn block_runs(coverage: &Coverage) -> String {
let mut runs: Vec<(u32, u32)> = Vec::new();
for block in coverage.covered_blocks() {
match runs.last_mut() {
Some((_, end)) if end.checked_add(1) == Some(block) => *end = block,
_ => runs.push((block, block)),
}
}
if runs.is_empty() {
return "none".to_owned();
}
runs.iter()
.map(|&(start, end)| {
if start == end {
start.to_string()
} else {
format!("{start}-{end}")
}
})
.collect::<Vec<_>>()
.join(",")
}
fn byte_runs(ranges: impl Iterator<Item = (u64, u64)>) -> String {
ranges
.map(|(start, len)| format!("{start}+{len}"))
.collect::<Vec<_>>()
.join(",")
}
fn plan_pending(
missing: &ChunkRanges,
total_bytes: u64,
coverage: &[Coverage],
) -> anyhow::Result<VecDeque<AlignedRange>> {
let sources: Vec<SourceCoverage> = coverage
.iter()
.enumerate()
.map(|(source_ix, coverage)| SourceCoverage {
source_ix,
coverage: coverage.clone(),
})
.collect();
let rank: Vec<usize> = (0..coverage.len()).collect();
let (runs, uncovered) = spread_segments(missing, total_bytes, &sources, &rank);
let mut pieces = Vec::with_capacity(runs.len());
for run in &runs {
let span = align_range(run.offset, run.len, total_bytes)?;
let run_missing = span.chunk_ranges() & missing;
pieces.extend(contiguous_byte_ranges(&run_missing, total_bytes));
}
let mut seeds: Vec<RunSeed> = pieces
.iter()
.map(|&(offset, len)| {
let holders = coverage
.iter()
.filter(|c| covers_byte_range(c, offset, len, total_bytes))
.count()
.max(1);
RunSeed {
offset,
len,
max_pieces: holders,
pieces: 1,
}
})
.collect();
let mut spare = coverage.len().saturating_sub(seeds.len());
while spare > 0 {
let Some(seed) = seeds
.iter_mut()
.filter(|s| s.pieces < s.max_pieces)
.max_by_key(|s| s.len / u64::try_from(s.pieces + 1).unwrap_or(u64::MAX))
else {
break;
};
seed.pieces += 1;
spare -= 1;
}
let mut pending: VecDeque<AlignedRange> = VecDeque::with_capacity(seeds.len());
for seed in &seeds {
for seg in split_evenly(seed.offset, seed.len, seed.pieces, total_bytes)? {
pending.push_back(seg);
}
}
for (start, len) in contiguous_byte_ranges(&uncovered, total_bytes) {
pending.push_back(align_range(start, len, total_bytes)?);
}
Ok(pending)
}
async fn requeue_missing<St>(
store: &St,
work: &AsyncMutex<Work>,
i: usize,
) -> anyhow::Result<Option<Requeued>>
where
St: IngestStore,
{
let assigned = {
let mut w = work.lock().await;
w.in_flight.get_mut(i).and_then(Option::take)
};
let Some(held) = assigned else {
return Ok(None);
};
let total_bytes = store.total_bytes();
let missing = store_missing(store, held.0, held.1).await?;
let remainder = contiguous_byte_ranges(&missing, total_bytes);
let bytes = remainder
.iter()
.map(|&(_, l)| l)
.fold(0, u64::saturating_add);
if !remainder.is_empty() {
let mut w = work.lock().await;
for (s, l) in remainder {
w.pending.push_back(align_range(s, l, total_bytes)?);
}
}
Ok(Some(Requeued { held, bytes }))
}
struct Requeued {
held: (u64, u64),
bytes: u64,
}
struct ParkedWait<'a> {
inner: &'a dyn PacingWait,
parked: &'a AtomicBool,
parked_wake: &'a Notify,
unit: &'a Unit,
}
struct ClosePark<'a>(&'a Unit, Instant, bool);
impl Drop for ClosePark<'_> {
fn drop(&mut self) {
let Self(unit, parked_at, counted) = *self;
if counted {
let nanos = u64::try_from(parked_at.elapsed().as_nanos()).unwrap_or(u64::MAX);
unit.parked.fetch_add(nanos, Ordering::Relaxed);
}
unit.park_open_at.store(u64::MAX, Ordering::Release);
}
}
struct Unpark<'a>(&'a AtomicBool);
impl Drop for Unpark<'_> {
fn drop(&mut self) {
self.0.store(false, Ordering::Release);
}
}
impl PacingWait for ParkedWait<'_> {
fn wait(
&self,
observed: DownstreamFrontier,
reason: WaitReason,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
Box::pin(async move {
self.parked.store(true, Ordering::Release);
let _unpark = Unpark(self.parked);
self.parked_wake.notify_waiters();
let parked_at = Instant::now();
let first_byte = self.unit.progress.first_byte.get().copied();
if let Some(first) = first_byte {
let open_at = u64::try_from(parked_at.saturating_duration_since(first).as_nanos())
.unwrap_or(u64::MAX - 1);
self.unit.park_open_at.store(open_at, Ordering::Relaxed);
}
let _closed = ClosePark(self.unit, parked_at, first_byte.is_some());
self.inner.wait(observed, reason).await;
})
}
}
async fn yield_to_front(
work: &AsyncMutex<Work>,
i: usize,
coverage: &Coverage,
total_bytes: u64,
parked: &AtomicBool,
parked_wake: &Notify,
progress_wake: &Notify,
) {
loop {
let on_park = parked_wake.notified();
let on_work = progress_wake.notified();
tokio::pin!(on_park);
tokio::pin!(on_work);
on_park.as_mut().enable();
on_work.as_mut().enable();
if parked.load(Ordering::Acquire)
&& work.lock().await.earlier_pending(i, coverage, total_bytes)
{
return;
}
tokio::select! {
() = on_park => {}
() = on_work => {}
}
}
}
struct Engine<'a, St, Pc> {
store: &'a St,
hash: [u8; 32],
pacer: &'a Pc,
work: &'a AsyncMutex<Work>,
progress_wake: &'a Notify,
progress_agg: &'a AtomicU64,
on_progress: &'a (dyn Fn(u64, u64) + Send + Sync + 'a),
pool: &'a SharedPool<'a>,
watchdog: Duration,
pacing: Option<&'a ConsumptionPacing<'a>>,
credentials: Option<&'a CredentialSlot>,
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
async fn run_worker<St, S, Pc>(
engine: &Engine<'_, St, Pc>,
i: usize,
lane: Arc<StreamCandidate<S>>,
provider: Address,
health: &PeerHealth,
hold: &mut Hold<S>,
) -> anyhow::Result<WorkerEnd>
where
St: IngestStore,
S: BlobSource,
Pc: Pacer,
{
let extra = matches!(hold, Hold::Extra(_));
let Engine {
store,
hash,
work,
progress_wake,
..
} = *engine;
let (my_coverage, handle) = {
let w = work.lock().await;
match (w.coverage.get(i).cloned(), w.cancel.get(i).map(Arc::clone)) {
(Some(c), Some(h)) => (c, h),
_ => anyhow::bail!("worker index {i} out of range for lane slots"),
}
};
let mut delivered = false;
let mut pulled_through = None;
let covered_served: Mutex<Vec<(u64, u64)>> = Mutex::new(Vec::new());
let take_served = || {
std::mem::take(
&mut *covered_served
.lock()
.unwrap_or_else(PoisonError::into_inner),
)
};
let mut counters = DriveCounters::new();
let wake = || progress_wake.notify_waiters();
let lane_parked = AtomicBool::new(false);
let lane_parked_wake = Notify::new();
if !extra && !retired(engine.credentials, &lane) {
let owed = lane.ledger.take_unpaid(hash);
for (index, &(start, len)) in owed.iter().enumerate() {
let settled = tokio::select! {
biased;
res = fill_gap(
store,
&lane.source,
engine.pacer,
&lane.ctx,
&lane.ledger,
hash,
start,
len,
&mut counters,
None,
None,
None,
None,
None,
Some(engine.pool),
None,
) => res.map(Some),
() = cancelled(&handle) => Ok(None),
};
let (unreached, err) = match settled {
Ok(Some(())) => continue,
Ok(None) => (index, None),
Err(err) => {
let end = start.saturating_add(len);
let paid = counters.gap_paid_frontier.unwrap_or(start).max(start);
lane.ledger.note_unpaid(hash, paid, end);
(index.saturating_add(1), Some(err))
}
};
for &(rest, rest_len) in owed.iter().skip(unreached) {
lane.ledger
.note_unpaid(hash, rest, rest.saturating_add(rest_len));
}
let Some(err) = err else { break };
work.lock().await.park(i);
wake();
return Ok(WorkerEnd {
provider,
end: LaneEnd::Faulted {
err: Some(err),
range: crate::source_set::LaneRange {
offset: start,
len,
landed: 0,
past_end: false,
uncovered: false,
},
piece_at: start,
at: Instant::now(),
},
delivered,
pulled_through,
covered_served: take_served(),
extra,
});
}
}
loop {
if retired(engine.credentials, &lane) {
requeue_missing(store, work, i).await?;
{
let mut w = work.lock().await;
if extra {
w.end_extra(i);
} else {
w.park(i);
}
}
wake();
return Ok(WorkerEnd {
provider,
end: LaneEnd::Retired,
delivered,
pulled_through,
covered_served: take_served(),
extra,
});
}
let parked = progress_wake.notified();
tokio::pin!(parked);
parked.as_mut().enable();
let total_bytes = store.total_bytes();
let idle = || WorkerEnd {
provider,
end: LaneEnd::Idle,
delivered,
pulled_through,
covered_served: take_served(),
extra,
};
let missing = if extra {
Vec::new()
} else {
contiguous_byte_ranges(&store_missing(store, 0, total_bytes).await?, total_bytes)
};
let picked = {
let mut w = work.lock().await;
let lane_stopped = extra
&& w.lane_of
.get(i)
.and_then(|&l| w.alive.get(l))
.is_none_or(|alive| !alive);
if lane_stopped {
None
} else if !hold.stream() {
if !w.busy() {
w.park(i);
drop(w);
wake();
return Ok(idle());
}
drop(w);
tokio::select! {
() = parked.as_mut() => {}
() = tokio::time::sleep(GROWTH_RETRY) => {}
}
continue;
} else {
let picked = w.pick(i, total_bytes, &my_coverage, !extra, &missing)?;
if picked.is_none() {
hold.park();
}
picked
}
};
let Some(Picked {
range,
uncovered,
unit,
}) = picked
else {
{
let mut w = work.lock().await;
if extra {
w.end_extra(i);
drop(w);
wake();
return Ok(idle());
}
if !w.busy() {
w.park(i);
drop(w);
wake();
return Ok(idle());
}
}
tokio::select! {
() = parked.as_mut() => {}
res = steal_recheck(store, work, i, &my_coverage) => res?,
}
continue;
};
let (r_start, r_len) = (range.fetch_start(), range.fetch_len());
let gaps = contiguous_byte_ranges(
&store_missing(store, r_start, r_len).await?,
store.total_bytes(),
);
if gaps.is_empty() {
let mut w = work.lock().await;
w.clear(i)?;
if extra {
w.end_extra(i);
drop(w);
wake();
return Ok(idle());
}
drop(w);
wake();
continue;
}
let mut terminal: Option<UnitOutcome> = None;
let mut piece_at = r_start;
let lane_wait = engine.pacing.map(|p| ParkedWait {
inner: p.pacing_wait,
parked: &lane_parked,
parked_wake: &lane_parked_wake,
unit: &unit,
});
for (g_start, g_len) in gaps {
if retired(engine.credentials, &lane) {
break;
}
piece_at = g_start;
let verified = &unit.progress.verified;
let before = verified.load(Ordering::Relaxed);
let outcome = {
let fill = fill_gap(
store,
&lane.source,
engine.pacer,
&lane.ctx,
&lane.ledger,
hash,
g_start,
g_len,
&mut counters,
Some(engine.on_progress),
Some(engine.progress_agg),
Some(&unit.progress),
lane_wait.as_ref().map(|w| w as &dyn PacingWait),
engine.pacing.map(|p| p.downstream),
Some(engine.pool),
Some(&unit.stop_at),
);
tokio::select! {
biased;
res = fill => match res {
Ok(()) => UnitOutcome::Completed,
Err(e) => UnitOutcome::Faulted(Some(e)),
},
() = cancelled(&handle) => UnitOutcome::Cancelled,
() = yield_to_front(
work,
i,
&my_coverage,
total_bytes,
&lane_parked,
&lane_parked_wake,
progress_wake,
), if engine.pacing.is_some() => UnitOutcome::Cancelled,
err = watchdog(store, g_start, g_len, engine.watchdog, verified) => {
UnitOutcome::Faulted(err)
}
() = retire_on_swap(engine.credentials, &lane, &unit, g_start) => {
UnitOutcome::Cancelled
}
}
};
let gap_landed = verified.load(Ordering::Relaxed).saturating_sub(before);
if gap_landed > 0 {
delivered = true;
if uncovered {
pulled_through = Some(Instant::now());
} else {
covered_served
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push((g_start, gap_landed));
}
health.record_progress(provider);
}
match outcome {
UnitOutcome::Completed => {}
other => {
terminal = Some(other);
break;
}
}
}
let landed = unit.progress.verified.load(Ordering::Relaxed);
if landed > 0 {
work.lock().await.record_rate(i, unit.rate());
}
match terminal {
None | Some(UnitOutcome::Completed) => {
let split = unit.stop_at.load(Ordering::Acquire);
if split < r_start.saturating_add(r_len) {
tracing::debug!(
%provider,
start = r_start,
split,
"a lane stopped its stream at a steal split"
);
}
if !retired(engine.credentials, &lane) {
work.lock().await.clear(i)?;
}
}
Some(UnitOutcome::Cancelled) => {
if let Some(Requeued { held, bytes }) = requeue_missing(store, work, i).await? {
tracing::debug!(
%provider,
held_start = held.0,
held_len = held.1,
requeued_bytes = bytes,
"cancelled leg re-queued the missing bytes of its trimmed range"
);
}
}
Some(UnitOutcome::Faulted(err)) => {
let faulted_at = Instant::now();
requeue_missing(store, work, i).await?;
{
let mut w = work.lock().await;
if extra {
w.end_extra(i);
} else {
w.park(i);
}
}
wake();
let range = crate::source_set::LaneRange {
offset: r_start,
len: r_len,
landed,
past_end: false,
uncovered,
};
return Ok(WorkerEnd {
provider,
end: LaneEnd::Faulted {
err,
range,
piece_at,
at: faulted_at,
},
delivered,
pulled_through,
covered_served: take_served(),
extra,
});
}
}
if extra && !retired(engine.credentials, &lane) {
work.lock().await.end_extra(i);
wake();
return Ok(WorkerEnd {
provider,
end: LaneEnd::Idle,
delivered,
pulled_through,
covered_served: take_served(),
extra,
});
}
wake();
}
}
fn retired<S>(slot: Option<&CredentialSlot>, lane: &StreamCandidate<S>) -> bool {
slot.is_some_and(|slot| slot.is_stale(&lane.ctx))
}
async fn retire_on_swap<S>(
slot: Option<&CredentialSlot>,
lane: &StreamCandidate<S>,
unit: &Unit,
start: u64,
) {
if let Some(slot) = slot {
loop {
let generation = slot.generation();
if slot.is_stale(&lane.ctx) {
let frontier = unit.progress.frontier.load(Ordering::SeqCst).max(start);
let group = decdn_bao_range::CHUNK_GROUP_BYTES;
let end = frontier.div_ceil(group).saturating_mul(group);
unit.stop_at.fetch_min(end, Ordering::SeqCst);
break;
}
slot.swapped_since(generation).await;
}
}
std::future::pending::<()>().await;
}
async fn worker<St, S, Pc>(
engine: &Engine<'_, St, Pc>,
i: usize,
lane: Arc<StreamCandidate<S>>,
provider: Address,
health: &PeerHealth,
hold: Hold<S>,
) -> anyhow::Result<WorkerEnd>
where
St: IngestStore,
S: BlobSource,
Pc: Pacer,
{
let mut hold = hold;
run_worker(engine, i, lane, provider, health, &mut hold).await
}
fn log_extra_fault(
provider: Address,
hash: [u8; 32],
range: crate::source_set::LaneRange,
err: &anyhow::Error,
) {
let hash = blake3::Hash::from_bytes(hash).to_hex();
let crate::source_set::LaneRange {
offset,
len,
landed,
past_end: _,
uncovered,
} = range;
let error = decdn_common::redact::sanitize_err_chain(err);
if landed > 0 {
let fault = crate::fault::classify(err);
crate::fault::warn_or_info!(
fault.warns(),
%provider,
%hash,
offset,
len,
landed,
uncovered,
?fault,
%error,
"an extra stream of a lane faulted; its remainder goes back to the queue"
);
} else {
tracing::debug!(
%provider,
%hash,
offset,
len,
uncovered,
%error,
"an extra stream of a lane faulted before any verified byte; its range goes back \
to the queue"
);
}
}
pub struct ConsumptionPacing<'a> {
pub downstream: &'a (dyn Fn() -> DownstreamFrontier + Send + Sync),
pub pacing_wait: &'a dyn PacingWait,
}
impl std::fmt::Debug for ConsumptionPacing<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConsumptionPacing").finish_non_exhaustive()
}
}
struct Want {
ranges: Vec<(u64, u64)>,
tail: bool,
bound: u64,
}
impl Want {
fn new(ranges: &[(u64, u64)], bound: u64, c0: u64) -> Self {
let end = c0.min(bound);
let reaches_end = |start: u64, len: u64| len == 0 || start.saturating_add(len) >= end;
let tail = ranges.iter().any(|&(start, len)| reaches_end(start, len));
let ranges: Vec<(u64, u64)> = ranges
.iter()
.filter(|&&(start, _)| start < bound)
.map(|&(start, len)| {
let room = bound - start;
let len = if reaches_end(start, len) {
room
} else {
len.min(room)
};
(start, len)
})
.collect();
Self {
ranges,
tail,
bound,
}
}
fn fit(&mut self, bound: u64) -> bool {
if bound == self.bound {
return false;
}
if bound > self.bound && self.tail {
self.ranges.push((self.bound, bound - self.bound));
}
if bound < self.bound {
self.ranges = self
.ranges
.iter()
.filter(|&&(start, _)| start < bound)
.map(|&(start, len)| (start, len.min(bound - start)))
.collect();
}
self.bound = bound;
true
}
}
struct ReleaseLeases<S>(Vec<Arc<StreamCandidate<S>>>);
impl<S> Drop for ReleaseLeases<S> {
fn drop(&mut self) {
for lane in &self.0 {
lane.lease.release();
}
}
}
type PoolLanes = Vec<(Arc<Mutex<PoolContext>>, Arc<PoolLedger>)>;
type LaneAt<S> = HashMap<usize, (Address, Arc<StreamCandidate<S>>)>;
fn join_pool<S>(lane: &StreamCandidate<S>, deposit: U256, pool_lanes: &Mutex<PoolLanes>) {
if let Ok(mut ctx) = lane.ctx.lock()
&& ctx.deposit < deposit
{
ctx.deposit = deposit;
}
pool_lanes
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push((Arc::clone(&lane.ctx), Arc::clone(&lane.ledger)));
}
fn lanes_to_start<P: SourceProvider>(
sources: &SourceSet<'_, P>,
work: &Work,
total_bytes: u64,
now: Instant,
deposit: U256,
mut busy: HashSet<Address>,
room: usize,
) -> Vec<Holder> {
let mut out = Vec::new();
while out.len() < room {
let Some(holder) = sources.next_to_start(now, deposit, &busy) else {
break;
};
busy.insert(holder.provider);
let take_uncovered = !sources.no_pull_through(holder.provider);
if work.has_work_for(
&holder_coverage(&holder, total_bytes),
total_bytes,
take_uncovered,
) {
out.push(holder);
}
}
out
}
fn holder_coverage(holder: &Holder, total_bytes: u64) -> Coverage {
holder
.coverage
.clone()
.unwrap_or_else(|| Coverage::full(num_blocks(total_bytes)))
}
fn only_uncovered_left<P: SourceProvider>(
sources: &SourceSet<'_, P>,
work: &Work,
total_bytes: u64,
) -> bool {
sources
.holders()
.iter()
.filter(|h| sources.no_pull_through(h.provider))
.all(|h| !work.has_work_for(&holder_coverage(h, total_bytes), total_bytes, false))
}
async fn all_present<St>(
store: &St,
ranges: &[(u64, u64)],
work: &AsyncMutex<Work>,
) -> anyhow::Result<bool>
where
St: IngestStore,
{
let total_bytes = store.total_bytes();
let gaps = contiguous_byte_ranges(&missing_chunks(store, ranges).await?, total_bytes);
if gaps.is_empty() {
return Ok(true);
}
let mut w = work.lock().await;
if w.pending.is_empty() && !w.busy() {
for (start, len) in gaps {
w.pending.push_back(align_range(start, len, total_bytes)?);
}
}
Ok(false)
}
pub(crate) type Connecting<'p, S> = std::pin::Pin<
Box<dyn Future<Output = (Address, anyhow::Result<StreamCandidate<S>>)> + Send + 'p>,
>;
pub(crate) fn connect_future<P: SourceProvider>(
provider: &P,
holder: Holder,
) -> Connecting<'_, P::Source> {
Box::pin(async move {
let built = provider.connect(&holder).await;
(holder.provider, built)
})
}
async fn poll_opt<T>(slot: &mut Option<SourceFuture<'_, T>>) -> anyhow::Result<T> {
match slot.as_mut() {
Some(fut) => fut.await,
None => std::future::pending().await,
}
}
async fn next_opt<T>(slot: &mut Option<SourceStream<'_, T>>) -> Option<T> {
use futures_util::StreamExt as _;
match slot.as_mut() {
Some(stream) => stream.next().await,
None => std::future::pending().await,
}
}
pub(crate) async fn sleep_until_opt(wake: Option<Instant>) {
match wake {
Some(at) => tokio::time::sleep_until(at).await,
None => std::future::pending().await,
}
}
struct Growth {
wanted: bool,
retry_at: Option<Instant>,
extra_backoff: HashMap<usize, crate::source_set::Backoff>,
waiting: Vec<(u64, u64)>,
waiting_since: Instant,
logged_at: Option<Instant>,
}
struct GrowthPass {
waiting: Vec<(u64, u64)>,
refused: usize,
no_candidate: usize,
growable: bool,
}
impl Growth {
fn new(now: Instant) -> Self {
Self {
wanted: false,
retry_at: None,
extra_backoff: HashMap::new(),
waiting: Vec::new(),
waiting_since: now,
logged_at: None,
}
}
fn due(&mut self, now: Instant) -> bool {
std::mem::take(&mut self.wanted) || self.retry_at.is_some_and(|at| now >= at)
}
fn may_ask(&self, lane: usize, now: Instant) -> bool {
self.extra_backoff
.get(&lane)
.is_none_or(|backoff| now >= backoff.next_at)
}
fn retry_by(&mut self, at: Instant) {
self.retry_at = Some(self.retry_at.map_or(at, |retry| retry.min(at)));
}
fn extra_refused(&mut self, lane: usize, now: Instant) -> u32 {
let backoff = self
.extra_backoff
.get(&lane)
.copied()
.unwrap_or(crate::source_set::Backoff::new(now))
.fail(now, GROWTH_RETRY, EXTRA_RETRY_CAP);
self.extra_backoff.insert(lane, backoff);
self.retry_by(backoff.next_at);
backoff.attempts
}
fn extra_served(&mut self, lane: usize) {
self.extra_backoff.remove(&lane);
}
fn passed(&mut self, now: Instant, hash: [u8; 32], pass: GrowthPass) {
self.retry_at = None;
if pass.waiting.is_empty() {
self.waiting.clear();
return;
}
if pass.refused > 0 || (pass.no_candidate > 0 && pass.growable) {
self.retry_by(now + GROWTH_RETRY);
}
if let Some(ends) = self
.extra_backoff
.values()
.map(|backoff| backoff.next_at)
.filter(|at| *at > now)
.min()
{
self.retry_by(ends);
}
let changed = pass.waiting != self.waiting;
if changed {
self.waiting = pass.waiting;
self.waiting_since = now;
}
let ranges = byte_runs(self.waiting.iter().copied());
let waited_secs = now.saturating_duration_since(self.waiting_since).as_secs();
if self
.logged_at
.is_none_or(|at| now.saturating_duration_since(at) >= WAITING_RELOG)
{
self.logged_at = Some(now);
tracing::info!(
hash = %blake3::Hash::from_bytes(hash).to_hex(),
%ranges,
refused = pass.refused,
no_candidate = pass.no_candidate,
waited_secs,
"a queued range waits for a stream: no idle lane takes it, and no busy lane \
could take one more stream for it"
);
} else if changed {
tracing::debug!(
hash = %blake3::Hash::from_bytes(hash).to_hex(),
%ranges,
refused = pass.refused,
no_candidate = pass.no_candidate,
waited_secs,
"a queued range waits for a stream: no idle lane takes it, and no busy lane \
could take one more stream for it"
);
}
}
}
async fn flushed<St>(store: &St, err: anyhow::Error) -> anyhow::Error
where
St: IngestStore,
{
if let Err(flush) = store.flush_present_record().await {
tracing::warn!("flushing the present record failed: {flush:#}");
}
err
}
fn pool_deposit<S>(
watch: &tokio::sync::watch::Receiver<U256>,
lanes: &[Arc<StreamCandidate<S>>],
) -> U256 {
lanes
.iter()
.filter_map(|lane| lane.ctx.lock().ok().map(|ctx| ctx.deposit))
.fold(*watch.borrow(), U256::max)
}
fn raise_to_lane<S>(raise: &dyn Fn(U256), lane: &StreamCandidate<S>) {
let seen = lane
.ctx
.lock()
.unwrap_or_else(PoisonError::into_inner)
.deposit;
raise(seen);
}
async fn swap_after(slot: Option<&CredentialSlot>, generation: u64) {
match slot {
Some(slot) => slot.swapped_since(generation).await,
None => std::future::pending().await,
}
}
pub(crate) fn key_spend<S>(
slot: &CredentialSlot,
lanes: &[Arc<StreamCandidate<S>>],
ledgers: Option<&LaneLedgers>,
) -> U256 {
let signer = slot.current().signer.address();
let this_fetch = match ledgers {
Some(reg) => reg.signed_by(signer),
None => lanes
.iter()
.filter(|lane| {
lane.ctx
.lock()
.is_ok_and(|ctx| ctx.client_signer.address() == signer)
})
.map(|lane| crate::credential::signed_past_prior(&lane.ctx, &lane.ledger))
.fold(U256::ZERO, U256::saturating_add),
};
slot.recorded_spend(signer).saturating_add(this_fetch)
}
async fn credential_view<St, S>(
slot: &CredentialSlot,
store: &St,
ranges: &[(u64, u64)],
lanes: &[Arc<StreamCandidate<S>>],
(ledgers, run): (Option<&LaneLedgers>, Option<&RunDemand>),
quotes: &QuoteMax,
) -> anyhow::Result<CredentialView>
where
St: IngestStore,
{
let credential = slot.current();
let spent_by_key = key_spend(slot, lanes, ledgers);
let own = ranges_content_len(&missing_chunks(store, ranges).await?, store.total_bytes());
let remaining = run.map_or(own, |run| own.max(run.outstanding()));
Ok(CredentialView::of(
&credential,
spent_by_key,
remaining,
quotes,
unix_now(),
))
}
pub async fn acquire<St, P, Pc, F>(
target: AcquireTarget<'_, St>,
sources: &mut SourceSet<'_, P>,
env: &AcquireEnv<'_, Pc, F>,
) -> anyhow::Result<()>
where
St: IngestStore,
P: SourceProvider,
Pc: Pacer,
F: Funder,
{
let watchdog = if env.pacing.is_none() {
LANE_WATCHDOG
} else {
Duration::ZERO
};
acquire_with_watchdog(target, sources, env, watchdog).await
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
async fn acquire_with_watchdog<'p, St, P, Pc, F>(
target: AcquireTarget<'_, St>,
sources: &mut SourceSet<'p, P>,
env: &AcquireEnv<'_, Pc, F>,
watchdog: Duration,
) -> anyhow::Result<()>
where
St: IngestStore,
P: SourceProvider,
Pc: Pacer,
F: Funder,
{
let AcquireTarget {
store,
hash,
total_bytes: c0,
ranges,
} = target;
let health = Arc::clone(sources.health());
let cap = env.max_blob_bytes;
let capped = |size: u64| if cap > 0 { size.min(cap) } else { size };
let c0 = capped(c0);
if cap > 0 && store.total_bytes() > cap {
store.set_bound(cap);
}
let first_bound = store.total_bytes();
if cap > 0 && first_bound > cap {
return Err(anyhow::Error::new(crate::BlobTooLarge {
reached: first_bound,
ceiling: cap,
}));
}
let mut want = Want::new(ranges, first_bound, c0);
let mut pending = VecDeque::new();
for (start, len) in
contiguous_byte_ranges(&missing_chunks(store, &want.ranges).await?, first_bound)
{
pending.push_back(align_range(start, len, first_bound)?);
}
let work = AsyncMutex::new(Work::new(pending, env.pacing.is_some()));
let progress_wake = Notify::new();
let (deposit_tx, mut deposit_rx) = tokio::sync::watch::channel(U256::ZERO);
let mut top_ups_seen = env.recovery.top_ups();
let pool_lanes: Mutex<PoolLanes> = Mutex::new(Vec::new());
let funder = env.funder;
let lanes = &pool_lanes;
let own: Box<dyn Fn() -> U256 + Send + Sync + '_> = match env.ledgers {
Some(reg) => Box::new(move || reg.total_committed()),
None => Box::new(move || {
lanes
.lock()
.unwrap_or_else(PoisonError::into_inner)
.iter()
.map(|(_, ledger)| ledger.committed().amount)
.fold(U256::ZERO, U256::saturating_add)
}),
};
let spent = move || own().max(funder.pool_spent().unwrap_or(U256::ZERO));
let credit: Box<dyn Fn(U256) -> anyhow::Result<()> + Send + Sync + '_> = match env.ledgers {
Some(reg) => Box::new(|new_deposit| {
reg.credit_all(new_deposit);
deposit_tx.send_replace(new_deposit);
Ok(())
}),
None => Box::new(|new_deposit| {
for (ctx, _) in pool_lanes
.lock()
.unwrap_or_else(PoisonError::into_inner)
.iter()
{
ctx.lock()
.map_err(|_| anyhow::anyhow!("pool context lock poisoned"))?
.deposit = new_deposit;
}
deposit_tx.send_replace(new_deposit);
Ok(())
}),
};
let raise = |seen: U256| {
match env.ledgers {
Some(reg) => reg.raise_all(seen),
None => {
for (ctx, _) in pool_lanes
.lock()
.unwrap_or_else(PoisonError::into_inner)
.iter()
{
let mut ctx = ctx.lock().unwrap_or_else(PoisonError::into_inner);
ctx.deposit = ctx.deposit.max(seen);
}
}
}
deposit_tx.send_if_modified(|current| {
let raised = seen > *current;
if raised {
*current = seen;
}
raised
});
};
let local_quotes = QuoteMax::default();
let quotes = env.run.map_or(&local_quotes, |run| &run.quotes);
let pool = SharedPool {
spent: &spent,
quotes: Some(quotes),
};
let mut generation_seen = env.credentials.map_or(0, CredentialSlot::generation);
let mut capability_refused = false;
let base_present = ranges_content_len(&store.present_ranges().await?, first_bound);
let progress_agg = AtomicU64::new(base_present);
if let Some(cb) = env.on_progress {
cb(base_present, first_bound);
}
let bound_seen = AtomicU64::new(first_bound);
let bound_moved = Notify::new();
let verified_seen = AtomicU64::new(base_present);
let on_progress = |position: u64, total: u64| {
let before = verified_seen.fetch_max(position, Ordering::AcqRel);
if position > before {
env.recovery.record_verified(position - before);
}
env.stop.clock.tick();
if store.total_bytes() != bound_seen.load(Ordering::Acquire) {
bound_moved.notify_one();
}
if let Some(cb) = env.on_progress {
cb(position, total);
}
};
let engine = Engine {
store,
hash,
pacer: env.pacer,
work: &work,
progress_wake: &progress_wake,
progress_agg: &progress_agg,
on_progress: &on_progress,
pool: &pool,
watchdog,
pacing: env.pacing,
credentials: env.credentials,
};
let max_lanes = env.max_lanes.max(1);
let mut started = ReleaseLeases(Vec::new());
let mut workers = FuturesUnordered::new();
let mut connecting: FuturesUnordered<Connecting<'p, P::Source>> = FuturesUnordered::new();
let mut connecting_set: HashSet<Address> = HashSet::new();
let mut discovering: Option<SourceFuture<'p, Vec<Holder>>> = None;
let mut arrivals: Option<SourceStream<'p, Holder>> = sources.provider().arrivals();
let mut running: HashSet<Address> = HashSet::new();
let mut slots: HashMap<Address, usize> = HashMap::new();
let mut lane_at: LaneAt<P::Source> = HashMap::new();
let mut extras_running = 0usize;
let mut growth = Growth::new(Instant::now());
let mut charged: HashSet<Address> = HashSet::new();
let mut ready: Vec<(Address, Arc<StreamCandidate<P::Source>>)> = Vec::new();
let mut seeded = false;
let mut flush = tokio::time::interval_at(
Instant::now() + PRESENT_RECORD_FLUSH_INTERVAL,
PRESENT_RECORD_FLUSH_INTERVAL,
);
let mut record: Option<SourceFuture<'_, ()>> = None;
let result: anyhow::Result<()> = async {
loop {
let bound = store.total_bytes();
if want.fit(bound) {
bound_seen.store(bound, Ordering::Release);
let mut w = work.lock().await;
w.grow_coverage(bound);
w.clip(bound)?;
}
if running.is_empty()
&& ready.is_empty()
&& extras_running == 0
&& all_present(store, &want.ranges, &work).await?
{
if let Some(proven) = store.proven()
&& cap > 0
&& proven > cap
{
let too_large = crate::BlobTooLarge {
reached: proven,
ceiling: cap,
};
return Err(anyhow::Error::new(too_large));
}
if store.proven().is_some() || !want.tail {
if let Some(pending) = record.take()
&& let Err(err) = pending.await
{
tracing::warn!(
"a periodic present-record write failed before the final one: \
{err:#}"
);
}
store.flush_present_record().await?;
return Ok(());
}
if cap > 0 && bound >= cap {
let too_large = crate::BlobTooLarge {
reached: bound.saturating_add(1),
ceiling: cap,
};
return Err(anyhow::Error::new(too_large));
}
let extra = bytes_past(&store.present_ranges().await?, c0, bound);
let grown = capped(grown_bound(bound, c0, extra));
tracing::debug!(
hash = %blake3::Hash::from_bytes(hash).to_hex(),
bound,
grown,
extra,
"every byte below the size claim is present and none proves the size; \
growing the claim"
);
store.set_bound(grown);
want.fit(grown);
bound_seen.store(grown, Ordering::Release);
let missing = missing_chunks(store, &want.ranges).await?;
work.lock().await.regrow(&missing, grown)?;
if let Some(cb) = env.on_progress {
cb(progress_agg.load(Ordering::Relaxed), grown);
}
continue;
}
let total_bytes = bound;
let now = Instant::now();
let deposit = pool_deposit(&deposit_rx, &started.0);
if let Some(slot) = env.credentials
&& slot.generation() != generation_seen
{
generation_seen = slot.generation();
sources.drop_lanes(|lane| slot.is_stale(&lane.ctx));
ready.retain(|(_, lane)| !slot.is_stale(&lane.ctx));
health.clear_unaffordable();
capability_refused = false;
let view = credential_view(
slot,
store,
&want.ranges,
&started.0,
(env.ledgers, env.run),
quotes,
)
.await?;
slot.report(view.event());
}
let busy: HashSet<Address> = running
.iter()
.chain(&connecting_set)
.copied()
.chain(ready.iter().map(|(p, _)| *p))
.collect();
let room = max_lanes.saturating_sub(busy.len());
let starts = {
let mut w = work.lock().await;
sources.expire_pull_through_bars(now);
for (&provider, &slot) in &slots {
w.set_no_uncovered(slot, sources.no_pull_through(provider));
if let Some(coverage) =
sources.holder(provider).and_then(|h| h.coverage.as_ref())
{
w.set_coverage(slot, coverage);
}
}
lanes_to_start(sources, &w, total_bytes, now, deposit, busy, room)
};
for holder in starts {
if let Some(lane) = sources.cached_lane(holder.provider) {
ready.push((holder.provider, lane));
} else {
connecting_set.insert(holder.provider);
connecting.push(connect_future(sources.provider(), holder));
}
}
if !ready.is_empty() {
let batch = std::mem::take(&mut ready);
let mut w = work.lock().await;
if !seeded {
seeded = true;
let coverages: Vec<Option<Coverage>> = batch
.iter()
.map(|(_, lane)| lane.coverage.clone())
.collect();
let missing = missing_chunks(store, &want.ranges).await?;
for ((provider, _), slot) in
batch.iter().zip(w.seed(&missing, total_bytes, &coverages)?)
{
slots.insert(*provider, slot);
}
tracing::debug!(
hash = %blake3::Hash::from_bytes(hash).to_hex(),
total_bytes,
lanes = batch.len(),
pending = %byte_runs(w.pending.iter().map(|s| (s.fetch_start(), s.fetch_len()))),
"planned the missing ranges across the first lanes"
);
}
for (provider, lane) in batch {
let restart = started.0.iter().any(|l| Arc::ptr_eq(l, &lane));
let grant = match lane.widen.as_ref() {
Some(widen) if restart && !lane.lease.is_held() => {
let at = Instant::now();
if !widen.grow(GrowFor::Restart) {
if sources.start_refused(provider, at + GROWTH_RETRY) {
tracing::info!(
%provider,
hash = %blake3::Hash::from_bytes(hash).to_hex(),
"a lane cannot start again: its provider has no free \
stream; it tries again each second"
);
}
continue;
}
sources.start_taken(provider);
Some(ReleaseGrant(Arc::clone(&lane)))
}
_ => None,
};
let slot = if let Some(&slot) = slots.get(&provider) {
w.revive(slot);
slot
} else {
let slot = w.add_lane(lane.coverage.clone(), total_bytes);
slots.insert(provider, slot);
slot
};
tracing::debug!(
hash = %blake3::Hash::from_bytes(hash).to_hex(),
%provider,
slot,
coverage = %lane.coverage.as_ref().map_or_else(
|| "whole blob".to_owned(),
block_runs,
),
"lane starts"
);
if !started.0.iter().any(|l| Arc::ptr_eq(l, &lane)) {
join_pool(&lane, deposit, &pool_lanes);
started.0.push(Arc::clone(&lane));
}
running.insert(provider);
w.set_provider(slot, provider);
lane_at.insert(slot, (provider, Arc::clone(&lane)));
let hold = Hold::Own {
lease: ReleaseLease(Arc::clone(&lane)),
grant,
};
workers.push(worker(&engine, slot, lane, provider, &health, hold));
}
}
if growth.due(now) {
let mut w = work.lock().await;
let wanted = w.growth_wanted(total_bytes, |lane| {
growth.may_ask(lane, now)
&& lane_at.get(&lane).is_some_and(|(_, l)| l.widen.is_some())
});
let mut refused_lanes: HashSet<usize> = HashSet::new();
let mut pass = GrowthPass {
waiting: Vec::new(),
refused: 0,
no_candidate: 0,
growable: lane_at
.values()
.any(|(provider, l)| running.contains(provider) && l.widen.is_some()),
};
for (range, candidates) in wanted {
let had_candidates = !candidates.is_empty();
let taker = candidates.into_iter().find_map(|lane| {
if refused_lanes.contains(&lane) {
return None;
}
let (provider, l) = lane_at.get(&lane)?;
let granted = l
.widen
.as_ref()
.is_some_and(|widen| widen.grow(GrowFor::Extra));
if !granted {
refused_lanes.insert(lane);
}
granted.then(|| (lane, *provider, Arc::clone(l)))
});
let Some((lane, provider, l)) = taker else {
pass.waiting.push((range.fetch_start(), range.fetch_len()));
if had_candidates {
pass.refused = pass.refused.saturating_add(1);
} else {
pass.no_candidate = pass.no_candidate.saturating_add(1);
}
continue;
};
let hold = Hold::Extra(ReleaseGrant(Arc::clone(&l)));
let slot = w.add_extra(lane);
workers.push(worker(&engine, slot, l, provider, &health, hold));
extras_running = extras_running.saturating_add(1);
tracing::debug!(
%provider,
offset = range.fetch_start(),
len = range.fetch_len(),
"a busy lane takes one extra stream for a queued range"
);
}
growth.passed(now, hash, pass);
}
if running.is_empty() && connecting.is_empty() && discovering.is_none() {
let only_uncovered = only_uncovered_left(sources, &*work.lock().await, total_bytes);
if let Some(err) = sources.exhausted(deposit, only_uncovered) {
if err.downcast_ref::<NoAffordableSource>().is_none() {
return Err(err);
}
if let Some(slot) = env.credentials {
let view = credential_view(
slot,
store,
&want.ranges,
&started.0,
(env.ledgers, env.run),
quotes,
)
.await?;
slot.report(view.event());
let pool = slot.pool_id();
let Some(cause) = view.funding_cause(capability_refused) else {
return Err(err.context(FundingNeeded::PublisherPool { pool }));
};
match env.recovery.swap_step(slot, generation_seen).await {
SwapStep::Swapped => continue,
SwapStep::NoProgress | SwapStep::TimedOut => {
return Err(
err.context(FundingNeeded::NewCapability { pool, cause })
);
}
}
}
let seen = sources.priced_out_at(deposit);
let stepped = env
.recovery
.step(
env.funder,
seen,
&mut top_ups_seen,
|| pool_deposit(&deposit_rx, &started.0),
spent(),
)
.await;
let raised = after_step(stepped, err)?;
if raised > deposit {
credit(raised)?;
}
if raised <= seen {
health.clear_unaffordable();
}
continue;
}
}
let uncovered = work.lock().await.uncovered(total_bytes);
if discovering.is_none()
&& arrivals.is_none()
&& sources.wants_discovery(now, deposit, running.len(), uncovered)
{
discovering = Some(sources.provider().discover(sources.hash()));
}
let wake = match (sources.next_wake(now), growth.retry_at) {
(Some(at), Some(retry)) => Some(at.min(retry)),
(at, retry) => at.or(retry),
};
tokio::select! {
biased;
Some(end) = workers.next(), if !workers.is_empty() => {
let WorkerEnd { provider, end, delivered, pulled_through, covered_served, extra } = match end {
Ok(end) => end,
Err(err) => return Err(err),
};
let lane_slot = slots.get(&provider).copied();
debug_assert!(lane_slot.is_some(), "a worker ended for an unstarted lane");
if extra {
extras_running = extras_running.saturating_sub(1);
if delivered && let Some(lane) = lane_slot {
growth.extra_served(lane);
}
} else {
running.remove(&provider);
}
growth.wanted = true;
if delivered {
sources.record_progress(provider);
sources.record_covered_served(provider, &covered_served);
charged.remove(&provider);
}
if let Some(at) = pulled_through {
sources.record_pull_through(provider, at);
}
if let (LaneEnd::Retired, Some(slot)) = (&end, env.credentials) {
sources.drop_lanes(|lane| slot.is_stale(&lane.ctx));
}
if let LaneEnd::Faulted { err: Some(err), .. } = &end
&& crate::fault::refuses_capability(err)
{
capability_refused = true;
}
if let LaneEnd::Faulted {
err,
range,
piece_at,
at,
} = end
{
let err = err.unwrap_or_else(|| {
anyhow::anyhow!("no verified progress for {watchdog:?}")
});
let range = crate::source_set::LaneRange {
past_end: piece_at >= known_end(store, c0),
..range
};
if extra
&& (running.contains(&provider) || charged.contains(&provider))
{
if let Fault::Fatal(_) = crate::fault::classify(&err) {
return Err(err);
}
log_extra_fault(provider, hash, range, &err);
if range.uncovered {
sources.record_extra_refusal(provider, &err, range, at);
}
growth.wanted = false;
if let Some(lane) = lane_slot {
let refusals = growth.extra_refused(lane, Instant::now());
if refusals == EXTRA_REFUSALS_LOGGED {
tracing::info!(
%provider,
hash = %blake3::Hash::from_bytes(hash).to_hex(),
offset = range.offset,
len = range.len,
uncovered = range.uncovered,
refusals,
error = %decdn_common::redact::sanitize_err_chain(&err),
"a node refuses a lane's extra streams; the lane is \
asked less often until one serves"
);
}
}
} else if !(env.recovery.settling(at)
&& sources.hold_while_settling(provider, &err, at))
{
let deposit = pool_deposit(&deposit_rx, &started.0);
charged.insert(provider);
if let Fault::Fatal(_) =
sources.record_fault(provider, &err, Some(range), at, deposit)
{
return Err(err);
}
}
}
progress_wake.notify_waiters();
}
Some((provider, built)) = connecting.next(), if !connecting.is_empty() => {
let mut builds = vec![(provider, built)];
while let Some(Some(more)) = connecting.next().now_or_never() {
builds.push(more);
}
for (provider, built) in builds {
connecting_set.remove(&provider);
match sources.lane_built(provider, built, Instant::now()) {
Ok(lane) => {
raise_to_lane(&raise, &lane);
ready.push((provider, lane));
growth.wanted = true;
}
Err(err) => {
if let Fault::Fatal(_) = crate::fault::classify(&err) {
return Err(err);
}
tracing::debug!(%provider, error = %decdn_common::redact::sanitize_err_chain(&err), "lane build failed");
}
}
}
}
found = poll_opt(&mut discovering), if discovering.is_some() => {
discovering = None;
let deposit = pool_deposit(&deposit_rx, &started.0);
sources.discovery_done(found, Instant::now(), deposit);
}
arrived = next_opt(&mut arrivals), if arrivals.is_some() => {
match arrived {
Some(holder) => {
tracing::debug!(
provider = %holder.provider,
rtt_ms = holder.rtt_ms,
probed_holder = holder.probed_holder,
"a late holder joined"
);
sources.holders_arrived(vec![holder]);
growth.wanted = true;
}
None => arrivals = None,
}
}
() = sleep_until_opt(wake) => {}
Ok(()) = deposit_rx.changed() => {}
() = swap_after(env.credentials, generation_seen) => {}
() = bound_moved.notified() => {}
flushed = poll_opt(&mut record), if record.is_some() => {
record = None;
flushed?;
}
_ = flush.tick() => {
if let Some(slot) = env.credentials {
let view = credential_view(
slot,
store,
&want.ranges,
&started.0,
(env.ledgers, env.run),
quotes,
)
.await?;
slot.report(view.event());
}
if record.is_none() {
record = Some(store.flush_present_record());
} else {
tracing::debug!(
hash = %blake3::Hash::from_bytes(hash).to_hex(),
"a present-record write is still in flight; this tick skips its \
write"
);
}
}
gave_up = env.stop.expired() => {
return Err(anyhow::Error::new(gave_up));
}
}
}
}
.await;
drop(workers);
drop(discovering);
drop(arrivals);
if let Some(pending) = record.take()
&& let Err(err) = pending.await
{
tracing::warn!("a periodic present-record write failed as the fetch ended: {err:#}");
}
let result = match result {
Err(err) => Err(flushed(store, err).await),
ok => ok,
};
drain_builds(&mut connecting).await;
result
}
const BUILD_DRAIN: Duration = Duration::from_secs(30);
async fn drain_builds<S>(connecting: &mut FuturesUnordered<Connecting<'_, S>>) {
if connecting.is_empty() {
return;
}
let drained = tokio::time::timeout(BUILD_DRAIN, async {
while let Some((provider, built)) = connecting.next().await {
if let Err(err) = built {
tracing::debug!(
%provider,
error = %decdn_common::redact::sanitize_err_chain(&err),
"a lane build that ended after the fetch failed"
);
}
}
})
.await;
if drained.is_err() {
tracing::warn!(
builds = connecting.len(),
"lane builds still running {BUILD_DRAIN:?} after the fetch ended are dropped"
);
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
clippy::cast_possible_truncation
)] mod tests;