cloudscraper_rs/challenges/core/
timing.rs

1//! Adaptive timing utilities.
2//!
3//! These abstractions provide a simplified feedback-driven delay system that
4//! solvers and the pipeline can reuse as a foundation for dynamic delays.
5
6use std::cmp::Ordering;
7use std::time::Duration;
8
9/// Feedback emitted after solving attempts.
10#[derive(Debug, Clone, Copy)]
11pub enum TimingFeedback {
12    Success,
13    Failure,
14    RateLimited,
15}
16
17/// Strategies used to compute the next delay before replaying a challenge.
18#[derive(Debug, Clone)]
19pub struct DelayStrategy {
20    base_delay_ms: u64,
21    min_delay_ms: u64,
22    max_delay_ms: u64,
23    variance_pct: f64,
24    recent_failures: u32,
25}
26
27impl DelayStrategy {
28    pub fn new(base_delay_ms: u64) -> Self {
29        Self {
30            base_delay_ms,
31            min_delay_ms: base_delay_ms / 2,
32            max_delay_ms: base_delay_ms * 2,
33            variance_pct: 0.25,
34            recent_failures: 0,
35        }
36    }
37
38    pub fn with_bounds(mut self, min_delay_ms: u64, max_delay_ms: u64) -> Self {
39        self.min_delay_ms = min_delay_ms;
40        self.max_delay_ms = max_delay_ms;
41        self
42    }
43
44    pub fn with_variance(mut self, variance_pct: f64) -> Self {
45        self.variance_pct = variance_pct;
46        self
47    }
48
49    pub fn register_feedback(&mut self, feedback: TimingFeedback) {
50        match feedback {
51            TimingFeedback::Success => {
52                self.recent_failures = self.recent_failures.saturating_sub(1);
53            }
54            TimingFeedback::Failure => {
55                self.recent_failures = self.recent_failures.saturating_add(1);
56            }
57            TimingFeedback::RateLimited => {
58                self.recent_failures = self.recent_failures.saturating_add(2);
59            }
60        }
61    }
62
63    pub fn next_delay(&self) -> Duration {
64        let mut delay = self.base_delay_ms as f64;
65
66        match self.recent_failures.cmp(&2) {
67            Ordering::Less => {}
68            Ordering::Equal => delay *= 1.5,
69            Ordering::Greater => delay *= 2.0,
70        }
71
72        let variance = delay * self.variance_pct;
73        let jitter = rand::random::<f64>() * variance - (variance / 2.0);
74        delay = (delay + jitter).clamp(self.min_delay_ms as f64, self.max_delay_ms as f64);
75        Duration::from_millis(delay.max(0.0) as u64)
76    }
77}