1use axum::{extract::Request, http::StatusCode, middleware::Next, response::Response};
2use dashmap::DashMap;
3use std::sync::atomic::{AtomicU8, AtomicU64, AtomicUsize, Ordering};
4use std::sync::{Arc, Mutex};
5use std::time::{Duration, Instant};
6
7const MONITORS_IDLE: u8 = 0;
8const MONITORS_RUNNING: u8 = 1;
9const MONITORS_SHUT_DOWN: u8 = 2;
10
11#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum TrafficShieldError {
15 #[error("Traffic Shield monitors require an active Tokio runtime")]
17 RuntimeUnavailable,
18 #[error("Traffic Shield monitors have already been shut down")]
20 AlreadyShutDown,
21}
22
23#[non_exhaustive]
25#[derive(Clone, Debug)]
26pub struct TrafficShieldConfig {
27 pub max_event_loop_lag: Duration,
29 pub max_db_latency: Duration,
31 pub max_active_requests: usize,
33 pub enable_db_probe: bool,
35}
36
37impl Default for TrafficShieldConfig {
38 fn default() -> Self {
39 Self {
40 max_event_loop_lag: Duration::from_millis(100),
41 max_db_latency: Duration::from_millis(500),
42 max_active_requests: 1000,
43 enable_db_probe: true,
44 }
45 }
46}
47
48impl TrafficShieldConfig {
49 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn with_max_event_loop_lag(mut self, lag: Duration) -> Self {
57 self.max_event_loop_lag = lag;
58 self
59 }
60
61 pub fn with_max_db_latency(mut self, latency: Duration) -> Self {
63 self.max_db_latency = latency;
64 self
65 }
66
67 pub fn with_max_active_requests(mut self, limit: usize) -> Self {
69 self.max_active_requests = limit;
70 self
71 }
72
73 pub fn with_db_probe(mut self, enable: bool) -> Self {
75 self.enable_db_probe = enable;
76 self
77 }
78}
79
80#[derive(Clone)]
82pub struct TrafficShield {
83 pub(crate) config: TrafficShieldConfig,
84 event_loop_lag_ms: Arc<AtomicU64>,
85 db_latency_ms: Arc<AtomicU64>,
86 active_requests: Arc<AtomicUsize>,
87 monitors: Arc<TrafficShieldMonitors>,
88}
89
90struct TrafficShieldMonitors {
91 state: AtomicU8,
92 shutdown: Arc<tokio::sync::Notify>,
93 tasks: Mutex<Vec<tokio::task::JoinHandle<()>>>,
94}
95
96impl TrafficShieldMonitors {
97 fn abort_tasks(&self) {
98 self.state.store(MONITORS_SHUT_DOWN, Ordering::Release);
99 self.shutdown.notify_waiters();
100 let mut tasks = match self.tasks.lock() {
101 Ok(tasks) => tasks,
102 Err(poisoned) => poisoned.into_inner(),
103 };
104 for task in tasks.drain(..) {
105 task.abort();
106 }
107 }
108}
109
110impl Drop for TrafficShieldMonitors {
111 fn drop(&mut self) {
112 self.abort_tasks();
113 }
114}
115
116impl TrafficShield {
117 pub fn new(config: TrafficShieldConfig) -> Self {
124 Self {
125 config,
126 event_loop_lag_ms: Arc::new(AtomicU64::new(0)),
127 db_latency_ms: Arc::new(AtomicU64::new(0)),
128 active_requests: Arc::new(AtomicUsize::new(0)),
129 monitors: Arc::new(TrafficShieldMonitors {
130 state: AtomicU8::new(MONITORS_IDLE),
131 shutdown: Arc::new(tokio::sync::Notify::new()),
132 tasks: Mutex::new(Vec::new()),
133 }),
134 }
135 }
136
137 #[cfg_attr(mutants, mutants::skip)]
149 pub fn start(&self) -> Result<(), TrafficShieldError> {
150 match self.monitors.state.load(Ordering::Acquire) {
151 MONITORS_RUNNING => return Ok(()),
152 MONITORS_SHUT_DOWN => return Err(TrafficShieldError::AlreadyShutDown),
153 _ => {}
154 }
155 let runtime = tokio::runtime::Handle::try_current()
156 .map_err(|_| TrafficShieldError::RuntimeUnavailable)?;
157
158 match self.monitors.state.compare_exchange(
159 MONITORS_IDLE,
160 MONITORS_RUNNING,
161 Ordering::AcqRel,
162 Ordering::Acquire,
163 ) {
164 Ok(_) => {}
165 Err(MONITORS_RUNNING) => return Ok(()),
166 Err(_) => return Err(TrafficShieldError::AlreadyShutDown),
167 }
168
169 let mut tasks = match self.monitors.tasks.lock() {
170 Ok(tasks) => tasks,
171 Err(poisoned) => poisoned.into_inner(),
172 };
173 if self.monitors.state.load(Ordering::Acquire) != MONITORS_RUNNING {
174 return Err(TrafficShieldError::AlreadyShutDown);
175 }
176
177 let lag_ms = self.event_loop_lag_ms.clone();
178 let lag_shutdown = Arc::clone(&self.monitors.shutdown);
179 tasks.push(runtime.spawn(async move {
180 let interval = Duration::from_millis(100);
181 loop {
182 let start = Instant::now();
183 tokio::select! {
184 _ = tokio::time::sleep(interval) => {}
185 _ = lag_shutdown.notified() => break,
186 }
187 let elapsed = start.elapsed();
188 let lag = elapsed.saturating_sub(interval);
189 lag_ms.store(duration_millis_u64(lag), Ordering::Relaxed);
190 }
191 }));
192
193 #[cfg(feature = "orm")]
194 if self.config.enable_db_probe {
195 let db_lat_ms = self.db_latency_ms.clone();
196 let db_shutdown = Arc::clone(&self.monitors.shutdown);
197 tasks.push(runtime.spawn(async move {
198 let interval = Duration::from_millis(1000);
199 loop {
200 tokio::select! {
201 _ = tokio::time::sleep(interval) => {}
202 _ = db_shutdown.notified() => break,
203 }
204 if let Some(pool) = crate::db::safe_pool() {
205 let start = Instant::now();
206 let res = sqlx::query("SELECT 1").execute(pool).await;
207 match res {
208 Ok(_) => {
209 let latency = start.elapsed();
210 db_lat_ms.store(duration_millis_u64(latency), Ordering::Relaxed);
211 }
212 Err(_) => {
213 db_lat_ms.store(9999, Ordering::Relaxed);
214 }
215 }
216 } else {
217 db_lat_ms.store(0, Ordering::Relaxed);
218 }
219 }
220 }));
221 }
222
223 Ok(())
224 }
225
226 pub fn shutdown(&self) {
229 self.monitors.abort_tasks();
230 }
231
232 pub fn is_running(&self) -> bool {
234 self.monitors.state.load(Ordering::Acquire) == MONITORS_RUNNING
235 }
236
237 pub fn event_loop_lag(&self) -> Duration {
240 Duration::from_millis(self.event_loop_lag_ms.load(Ordering::Relaxed))
241 }
242
243 pub fn db_latency(&self) -> Duration {
247 Duration::from_millis(self.db_latency_ms.load(Ordering::Relaxed))
248 }
249
250 pub fn active_requests(&self) -> usize {
252 self.active_requests.load(Ordering::Relaxed)
253 }
254}
255
256fn duration_millis_u64(duration: Duration) -> u64 {
257 u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
258}
259
260#[derive(Clone, Copy, Debug, PartialEq, Eq)]
261enum TrafficPressure {
262 Normal,
263 Moderate,
264 Critical,
265}
266
267fn classify_traffic_pressure(
268 config: &TrafficShieldConfig,
269 lag: Duration,
270 db_latency: Duration,
271 active_requests: usize,
272) -> TrafficPressure {
273 let critical = lag >= config.max_event_loop_lag
274 || (config.enable_db_probe && db_latency >= config.max_db_latency)
275 || active_requests >= config.max_active_requests;
276 if critical {
277 return TrafficPressure::Critical;
278 }
279
280 let moderate = lag >= config.max_event_loop_lag / 2
281 || (config.enable_db_probe && db_latency >= config.max_db_latency / 2)
282 || active_requests >= config.max_active_requests / 2;
283 if moderate {
284 TrafficPressure::Moderate
285 } else {
286 TrafficPressure::Normal
287 }
288}
289
290struct ActiveRequestGuard<'a>(&'a AtomicUsize);
291
292impl<'a> Drop for ActiveRequestGuard<'a> {
293 fn drop(&mut self) {
294 self.0.fetch_sub(1, Ordering::SeqCst);
295 }
296}
297
298#[cfg_attr(mutants, mutants::skip)]
300pub async fn backpressure_middleware(shield: TrafficShield, req: Request, next: Next) -> Response {
301 if let Err(error) = shield.start() {
302 eprintln!("Traffic Shield is unavailable: {error}");
303 let mut response = Response::new(axum::body::Body::from(
304 "Traffic Shield monitoring is unavailable.",
305 ));
306 *response.status_mut() = StatusCode::SERVICE_UNAVAILABLE;
307 return response;
308 }
309
310 let active = shield.active_requests.fetch_add(1, Ordering::SeqCst);
311 let _guard = ActiveRequestGuard(&shield.active_requests);
312
313 let lag = shield.event_loop_lag();
314 let db_lat = shield.db_latency();
315
316 let pressure = classify_traffic_pressure(&shield.config, lag, db_lat, active);
317
318 if pressure == TrafficPressure::Critical {
319 eprintln!(
320 "⚠️ [Rullst Backpressure] Load shedding active! CPU lag: {:?}, DB latency: {:?}, Active requests: {}",
321 lag, db_lat, active
322 );
323
324 match Response::builder()
325 .status(StatusCode::SERVICE_UNAVAILABLE)
326 .header(axum::http::header::RETRY_AFTER, "5")
327 .header(
328 axum::http::header::CONTENT_TYPE,
329 "text/plain; charset=utf-8",
330 )
331 .body(axum::body::Body::from(
332 "Service Temporarily Saturated. Please try again soon.",
333 )) {
334 Ok(res) => return res,
335 Err(_) => {
336 let mut res = Response::new(axum::body::Body::empty());
337 *res.status_mut() = StatusCode::SERVICE_UNAVAILABLE;
338 return res;
339 }
340 }
341 }
342
343 if pressure == TrafficPressure::Moderate {
344 tokio::time::sleep(Duration::from_millis(25)).await;
345 }
346
347 next.run(req).await
348}
349
350#[non_exhaustive]
352#[derive(Clone, Debug)]
353pub struct RateLimitConfig {
354 pub max_tokens: f64,
356 pub refill_rate: f64,
358}
359
360impl RateLimitConfig {
361 pub fn new(max_tokens: f64, refill_rate: f64) -> Self {
364 Self {
365 max_tokens,
366 refill_rate,
367 }
368 }
369
370 pub fn per_second(limit: f64) -> Self {
372 Self::new(limit, limit)
373 }
374
375 pub fn per_minute(limit: f64) -> Self {
377 Self::new(limit, limit / 60.0)
378 }
379
380 pub fn per_hour(limit: f64) -> Self {
382 Self::new(limit, limit / 3600.0)
383 }
384}
385
386#[derive(Clone, Debug)]
387struct TokenBucket {
388 tokens: f64,
389 last_refill: Instant,
390}
391
392fn refill_token_count(current_tokens: f64, elapsed_secs: f64, config: &RateLimitConfig) -> f64 {
393 (current_tokens + elapsed_secs * config.refill_rate).min(config.max_tokens)
394}
395
396#[derive(Clone)]
398pub struct RateLimiter {
399 pub(crate) config: RateLimitConfig,
400 buckets: Arc<DashMap<String, TokenBucket>>,
401 key_extractor: Arc<dyn Fn(&Request) -> String + Send + Sync>,
402}
403
404impl RateLimiter {
405 pub fn new(config: RateLimitConfig) -> Self {
412 Self {
413 config,
414 buckets: Arc::new(DashMap::new()),
415 key_extractor: Arc::new(default_key_extractor),
416 }
417 }
418
419 pub fn with_key_extractor<F>(mut self, extractor: F) -> Self
421 where
422 F: Fn(&Request) -> String + Send + Sync + 'static,
423 {
424 self.key_extractor = Arc::new(extractor);
425 self
426 }
427
428 pub fn check_and_consume(&self, key: &str) -> bool {
430 let now = Instant::now();
431 let mut entry = self
432 .buckets
433 .entry(key.to_string())
434 .or_insert_with(|| TokenBucket {
435 tokens: self.config.max_tokens,
436 last_refill: now,
437 });
438
439 let elapsed = now.duration_since(entry.last_refill).as_secs_f64();
440 entry.tokens = refill_token_count(entry.tokens, elapsed, &self.config);
441 entry.last_refill = now;
442
443 if entry.tokens >= 1.0 {
444 entry.tokens -= 1.0;
445 true
446 } else {
447 false
448 }
449 }
450}
451
452pub fn default_key_extractor(req: &Request) -> String {
454 if let Some(conn_info) = req
455 .extensions()
456 .get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
457 {
458 return conn_info.0.ip().to_string();
459 }
460
461 "missing-peer-address".to_string()
462}
463
464pub async fn rate_limit_middleware(limiter: RateLimiter, req: Request, next: Next) -> Response {
466 let key = (limiter.key_extractor)(&req);
467 if limiter.check_and_consume(&key) {
468 next.run(req).await
469 } else {
470 match Response::builder()
471 .status(StatusCode::TOO_MANY_REQUESTS)
472 .header(
473 axum::http::header::CONTENT_TYPE,
474 "text/plain; charset=utf-8",
475 )
476 .body(axum::body::Body::from(
477 "Rate limit exceeded. Please try again later.",
478 )) {
479 Ok(res) => res,
480 Err(_) => {
481 let mut res = Response::new(axum::body::Body::empty());
482 *res.status_mut() = StatusCode::TOO_MANY_REQUESTS;
483 res
484 }
485 }
486 }
487}
488
489#[cfg(test)]
490#[allow(clippy::unwrap_used, clippy::expect_used)]
491mod tests {
492 use super::*;
493 use axum::http::Request;
494 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
495
496 #[test]
497 fn traffic_shield_construction_is_runtime_independent() {
498 let shield = TrafficShield::new(TrafficShieldConfig::new().with_db_probe(false));
499
500 assert!(!shield.is_running());
501 assert_eq!(shield.start(), Err(TrafficShieldError::RuntimeUnavailable));
502 assert!(!shield.is_running());
503 }
504
505 #[tokio::test]
506 async fn traffic_shield_has_explicit_shared_shutdown() {
507 let shield = TrafficShield::new(TrafficShieldConfig::new().with_db_probe(false));
508 let clone = shield.clone();
509
510 shield.start().unwrap();
511 assert!(clone.is_running());
512 clone.shutdown();
513 assert!(!shield.is_running());
514 assert_eq!(shield.start(), Err(TrafficShieldError::AlreadyShutDown));
515 }
516
517 #[tokio::test]
518 async fn dropping_final_shield_aborts_monitor_tasks() {
519 let shield = TrafficShield::new(TrafficShieldConfig::new().with_db_probe(false));
520 let monitored_value = Arc::downgrade(&shield.event_loop_lag_ms);
521 shield.start().unwrap();
522 drop(shield);
523
524 tokio::time::timeout(Duration::from_millis(100), async {
525 while monitored_value.upgrade().is_some() {
526 tokio::task::yield_now().await;
527 }
528 })
529 .await
530 .unwrap();
531 }
532
533 #[test]
534 fn test_default_key_extractor() {
535 let req1 = Request::builder()
536 .header("x-forwarded-for", "192.168.1.1, 10.0.0.1")
537 .body(axum::body::Body::empty())
538 .unwrap();
539 assert_eq!(default_key_extractor(&req1), "missing-peer-address");
540
541 let req2 = Request::builder()
542 .header("x-real-ip", "10.0.0.2")
543 .body(axum::body::Body::empty())
544 .unwrap();
545 assert_eq!(default_key_extractor(&req2), "missing-peer-address");
546
547 let mut req3 = Request::builder().body(axum::body::Body::empty()).unwrap();
548 let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
549 req3.extensions_mut()
550 .insert(axum::extract::ConnectInfo(socket));
551 assert_eq!(default_key_extractor(&req3), "127.0.0.1");
552
553 let req4 = Request::builder().body(axum::body::Body::empty()).unwrap();
554 assert_eq!(default_key_extractor(&req4), "missing-peer-address");
555 }
556
557 #[tokio::test]
558 async fn test_traffic_shield_active_requests() {
559 let config = TrafficShieldConfig::new().with_db_probe(false);
560 let shield = TrafficShield::new(config);
561
562 assert_eq!(shield.active_requests(), 0);
563 shield
564 .active_requests
565 .fetch_add(5, std::sync::atomic::Ordering::SeqCst);
566 assert_eq!(shield.active_requests(), 5);
567 }
568
569 #[test]
570 fn test_traffic_pressure_classification() {
571 let config = TrafficShieldConfig::new().with_db_probe(false);
572
573 assert_eq!(
574 classify_traffic_pressure(&config, Duration::ZERO, Duration::ZERO, 0),
575 TrafficPressure::Normal
576 );
577 assert_eq!(
578 classify_traffic_pressure(&config, config.max_event_loop_lag / 2, Duration::ZERO, 0,),
579 TrafficPressure::Moderate
580 );
581 assert_eq!(
582 classify_traffic_pressure(&config, config.max_event_loop_lag, Duration::ZERO, 0,),
583 TrafficPressure::Critical
584 );
585 assert_eq!(
586 classify_traffic_pressure(&config, Duration::ZERO, config.max_db_latency, 0,),
587 TrafficPressure::Normal
588 );
589 }
590
591 #[test]
592 fn test_rate_limit_config_per_minute() {
593 let config = RateLimitConfig::per_minute(60.0);
594 assert_eq!(config.max_tokens, 60.0);
595 assert_eq!(config.refill_rate, 1.0);
596 }
597
598 #[test]
599 fn test_rate_limit_config_per_second() {
600 let config = RateLimitConfig::per_second(10.0);
601 assert_eq!(config.max_tokens, 10.0);
602 assert_eq!(config.refill_rate, 10.0);
603 }
604
605 #[test]
606 fn test_rate_limit_config_per_hour() {
607 let config = RateLimitConfig::per_hour(3600.0);
608 assert_eq!(config.max_tokens, 3600.0);
609 assert_eq!(config.refill_rate, 1.0);
610 }
611
612 #[tokio::test]
613 async fn test_traffic_shield_db_latency() {
614 let config = TrafficShieldConfig::new().with_db_probe(false);
615 let shield = TrafficShield::new(config);
616
617 assert_eq!(shield.db_latency().as_millis(), 0);
618 shield
619 .db_latency_ms
620 .store(50, std::sync::atomic::Ordering::Relaxed);
621 assert_eq!(shield.db_latency().as_millis(), 50);
622 }
623
624 #[tokio::test]
625 async fn test_traffic_shield_event_loop_lag() {
626 let config = TrafficShieldConfig::new().with_db_probe(false);
627 let shield = TrafficShield::new(config);
628
629 assert_eq!(shield.event_loop_lag().as_millis(), 0);
630 shield
631 .event_loop_lag_ms
632 .store(100, std::sync::atomic::Ordering::Relaxed);
633 assert_eq!(shield.event_loop_lag().as_millis(), 100);
634 }
635
636 #[test]
637 fn test_check_and_consume() {
638 let config = RateLimitConfig::per_second(2.0); let limiter = RateLimiter::new(config);
640
641 assert!(limiter.check_and_consume("test_key"));
643 assert!(limiter.check_and_consume("test_key"));
645 assert!(!limiter.check_and_consume("test_key"));
647 }
648 #[tokio::test]
649 async fn test_traffic_shield_config_builders() {
650 let config = TrafficShieldConfig::new()
651 .with_max_event_loop_lag(Duration::from_millis(999))
652 .with_max_db_latency(Duration::from_millis(888))
653 .with_max_active_requests(777);
654
655 assert_eq!(config.max_event_loop_lag, Duration::from_millis(999));
656 assert_eq!(config.max_db_latency, Duration::from_millis(888));
657 assert_eq!(config.max_active_requests, 777);
658 }
659
660 #[tokio::test]
661 async fn test_check_and_consume_refill() {
662 let config = RateLimitConfig::per_second(2.0); let limiter = RateLimiter::new(config);
664
665 assert!(limiter.check_and_consume("test_key"));
666 assert!(limiter.check_and_consume("test_key"));
667 assert!(!limiter.check_and_consume("test_key")); tokio::time::sleep(Duration::from_millis(600)).await; assert!(limiter.check_and_consume("test_key"));
672 assert!(!limiter.check_and_consume("test_key")); }
674
675 #[tokio::test]
676 async fn test_backpressure_middleware_critical() {
677 use axum::{Router, routing::get};
678 use tower::ServiceExt;
679
680 let config = TrafficShieldConfig::new().with_max_active_requests(0); let shield = TrafficShield::new(config);
682
683 let app =
684 Router::new()
685 .route("/", get(|| async { "OK" }))
686 .layer(axum::middleware::from_fn(move |req, next| {
687 let s = shield.clone();
688 async move { backpressure_middleware(s, req, next).await }
689 }));
690
691 let req = axum::http::Request::builder()
692 .uri("/")
693 .body(axum::body::Body::empty())
694 .unwrap();
695 let res = app.oneshot(req).await.unwrap();
696 assert_eq!(res.status(), axum::http::StatusCode::SERVICE_UNAVAILABLE);
697 }
698
699 #[tokio::test]
700 async fn test_backpressure_middleware_moderate() {
701 use axum::{Router, routing::get};
702 use std::time::Instant;
703 use tower::ServiceExt;
704
705 let config = TrafficShieldConfig::new().with_max_active_requests(100);
706 let shield = TrafficShield::new(config);
707 shield
708 .active_requests
709 .store(50, std::sync::atomic::Ordering::SeqCst); let app =
712 Router::new()
713 .route("/", get(|| async { "OK" }))
714 .layer(axum::middleware::from_fn(move |req, next| {
715 let s = shield.clone();
716 async move { backpressure_middleware(s, req, next).await }
717 }));
718
719 let req = axum::http::Request::builder()
720 .uri("/")
721 .body(axum::body::Body::empty())
722 .unwrap();
723 let start = Instant::now();
724 let res = app.oneshot(req).await.unwrap();
725 let elapsed = start.elapsed();
726
727 assert_eq!(res.status(), axum::http::StatusCode::OK);
728 assert!(elapsed >= Duration::from_millis(25)); }
730
731 #[tokio::test]
732 async fn test_rate_limit_middleware_rejection() {
733 use axum::{Router, routing::get};
734 use tower::ServiceExt;
735
736 let config = RateLimitConfig::per_second(1.0); let limiter = RateLimiter::new(config);
738
739 let app =
740 Router::new()
741 .route("/", get(|| async { "OK" }))
742 .layer(axum::middleware::from_fn(move |req, next| {
743 let l = limiter.clone();
744 async move { rate_limit_middleware(l, req, next).await }
745 }));
746
747 let req1 = axum::http::Request::builder()
749 .uri("/")
750 .body(axum::body::Body::empty())
751 .unwrap();
752 let res1 = app.clone().oneshot(req1).await.unwrap();
753 assert_eq!(res1.status(), axum::http::StatusCode::OK);
754
755 let req2 = axum::http::Request::builder()
757 .uri("/")
758 .body(axum::body::Body::empty())
759 .unwrap();
760 let res2 = app.oneshot(req2).await.unwrap();
761 assert_eq!(res2.status(), axum::http::StatusCode::TOO_MANY_REQUESTS);
762
763 let body_bytes = axum::body::to_bytes(res2.into_body(), 1024).await.unwrap();
764 assert_eq!(
765 String::from_utf8_lossy(&body_bytes),
766 "Rate limit exceeded. Please try again later."
767 );
768 }
769}
770
771#[cfg(test)]
772#[path = "resilience_contract_tests.rs"]
773mod contract_tests;
774
775#[cfg(kani)]
776#[cfg_attr(mutants, mutants::skip)]
777mod kani_proofs {
778 use super::*;
779
780 #[kani::proof]
781 fn verify_token_bucket_math_safety() {
782 let max_tokens: f64 = kani::any();
783 let refill_rate: f64 = kani::any();
784 let current_tokens: f64 = kani::any();
785 let elapsed_secs: f64 = kani::any();
786
787 kani::assume(max_tokens > 0.0 && max_tokens < 1_000_000.0);
789 kani::assume(refill_rate >= 0.0 && refill_rate < 100_000.0);
790 kani::assume(current_tokens >= 0.0 && current_tokens <= max_tokens);
791 kani::assume(elapsed_secs >= 0.0 && elapsed_secs < 31_536_000.0); let config = RateLimitConfig::new(max_tokens, refill_rate);
794 let final_tokens = refill_token_count(current_tokens, elapsed_secs, &config);
795
796 assert!(!final_tokens.is_nan());
798 assert!(!final_tokens.is_infinite());
799 assert!(final_tokens >= 0.0);
800 assert!(final_tokens <= max_tokens);
801 }
802
803 #[kani::proof]
804 fn verify_traffic_shield_thresholds() {
805 let max_event_loop_lag_ns: u64 = kani::any();
806 let max_db_latency_ns: u64 = kani::any();
807 let max_active_requests: usize = kani::any();
808 let enable_db_probe: bool = kani::any();
809 let lag_ns: u64 = kani::any();
810 let db_latency_ns: u64 = kani::any();
811 let active: usize = kani::any();
812
813 let config = TrafficShieldConfig {
814 max_event_loop_lag: Duration::from_nanos(max_event_loop_lag_ns),
815 max_db_latency: Duration::from_nanos(max_db_latency_ns),
816 max_active_requests,
817 enable_db_probe,
818 };
819 let lag = Duration::from_nanos(lag_ns);
820 let db_latency = Duration::from_nanos(db_latency_ns);
821 let pressure = classify_traffic_pressure(&config, lag, db_latency, active);
822
823 let critical = lag >= config.max_event_loop_lag
824 || (enable_db_probe && db_latency >= config.max_db_latency)
825 || active >= max_active_requests;
826 let moderate = lag >= config.max_event_loop_lag / 2
827 || (enable_db_probe && db_latency >= config.max_db_latency / 2)
828 || active >= max_active_requests / 2;
829
830 match pressure {
831 TrafficPressure::Critical => {
832 assert!(critical);
833 }
834 TrafficPressure::Moderate => {
835 assert!(!critical);
836 assert!(moderate);
837 }
838 TrafficPressure::Normal => {
839 assert!(!critical);
840 assert!(!moderate);
841 }
842 }
843 }
844}