(&self, currency: &str)
| 431 | } |
| 432 | |
| 433 | fn background_refresh(&self, currency: &str) { |
| 434 | let inner = self.inner.clone(); |
| 435 | let client = self.client.clone(); |
| 436 | let currency = currency.to_owned(); |
| 437 | tokio::spawn(async move { |
| 438 | tokio::time::sleep(SOURCE_TIMEOUT_SECS * 5).await; |
| 439 | loop { |
| 440 | let mut inner = inner.lock().await; |
| 441 | let now = Instant::now(); |
| 442 | inner.reset_backoff_if_needed(now); |
| 443 | let available_sources: Vec<String> = inner |
| 444 | .sources |
| 445 | .iter() |
| 446 | .filter(|(_, source_health)| source_health.backoff_until < now) |
| 447 | .map(|(name, _)| name) |
| 448 | .cloned() |
| 449 | .collect(); |
| 450 | |
| 451 | let prices = inner |
| 452 | .currencies |
| 453 | .get(¤cy) |
| 454 | .map(|currency_cache| ¤cy_cache.prices); |
| 455 | |
| 456 | let mut sources_by_staleness: Vec<(String, Option<Instant>)> = available_sources |
| 457 | .into_iter() |
| 458 | .map(|name| { |
| 459 | let last_fetch = prices |
| 460 | .and_then(|prices| prices.get(&name)) |
| 461 | .map(|price_cache| price_cache.timestamp); |
| 462 | (name, last_fetch) |
| 463 | }) |
| 464 | .collect(); |
| 465 | |
| 466 | sources_by_staleness.sort_by_key(|(_, s)| *s); |
| 467 | |
| 468 | let stale_cutoff = now - SERVE_TTL + 2 * SOURCE_TIMEOUT_SECS; |
| 469 | if sources_by_staleness |
| 470 | .last() |
| 471 | .and_then(|(_, timestamp)| *timestamp) |
| 472 | .is_some_and(|timestamp| timestamp > stale_cutoff) |
| 473 | { |
| 474 | sources_by_staleness.clear(); |
| 475 | } |
| 476 | |
| 477 | log::trace!( |
| 478 | "sources_by_staleness: {}", |
| 479 | sources_by_staleness |
| 480 | .iter() |
| 481 | .map(|(n, p)| format!( |
| 482 | "{n}:{}", |
| 483 | p.map(|f| f.elapsed().as_secs()).unwrap_or(0) |
| 484 | )) |
| 485 | .collect::<Vec<String>>() |
| 486 | .join(", ") |
| 487 | ); |
| 488 | |
| 489 | for (name, _) in sources_by_staleness { |
| 490 | let source_health = inner.sources.get_mut(&name).unwrap(); |
no test coverage detected