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 crate::server::console::stderr_line(format_args!("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 crate::server::console::stderr_line(format_args!(
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)]
403pub struct RateLimiter {
404 pub(crate) config: RateLimitConfig,
405 buckets: Arc<DashMap<String, TokenBucket>>,
406 key_extractor: Arc<dyn Fn(&Request) -> String + Send + Sync>,
407 new_keys: Arc<AtomicUsize>,
408 max_buckets: usize,
409}
410
411impl RateLimiter {
412 pub fn new(config: RateLimitConfig) -> Self {
419 Self {
420 config,
421 buckets: Arc::new(DashMap::new()),
422 key_extractor: Arc::new(default_key_extractor),
423 new_keys: Arc::new(AtomicUsize::new(0)),
424 max_buckets: buckets::MAX_RATE_LIMIT_BUCKETS,
425 }
426 }
427
428 pub fn with_key_extractor<F>(mut self, extractor: F) -> Self
430 where
431 F: Fn(&Request) -> String + Send + Sync + 'static,
432 {
433 self.key_extractor = Arc::new(extractor);
434 self
435 }
436
437 pub fn check_and_consume(&self, key: &str) -> bool {
439 let now = Instant::now();
440 if !self.buckets.contains_key(key) {
441 self.make_room_for_new_key(now);
442 }
443 let mut entry = self
444 .buckets
445 .entry(key.to_string())
446 .or_insert_with(|| TokenBucket {
447 tokens: self.config.max_tokens,
448 last_refill: now,
449 });
450
451 let elapsed = now.duration_since(entry.last_refill).as_secs_f64();
452 entry.tokens = refill_token_count(entry.tokens, elapsed, &self.config);
453 entry.last_refill = now;
454
455 if entry.tokens >= 1.0 {
456 entry.tokens -= 1.0;
457 true
458 } else {
459 false
460 }
461 }
462}
463
464pub fn default_key_extractor(req: &Request) -> String {
469 if let Some(conn_info) = req
470 .extensions()
471 .get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
472 {
473 return buckets::peer_rate_limit_key(conn_info.0.ip());
474 }
475
476 "missing-peer-address".to_string()
477}
478
479pub async fn rate_limit_middleware(limiter: RateLimiter, req: Request, next: Next) -> Response {
481 let key = (limiter.key_extractor)(&req);
482 if limiter.check_and_consume(&key) {
483 next.run(req).await
484 } else {
485 match Response::builder()
486 .status(StatusCode::TOO_MANY_REQUESTS)
487 .header(
488 axum::http::header::CONTENT_TYPE,
489 "text/plain; charset=utf-8",
490 )
491 .body(axum::body::Body::from(
492 "Rate limit exceeded. Please try again later.",
493 )) {
494 Ok(res) => res,
495 Err(_) => {
496 let mut res = Response::new(axum::body::Body::empty());
497 *res.status_mut() = StatusCode::TOO_MANY_REQUESTS;
498 res
499 }
500 }
501 }
502}
503
504#[cfg(test)]
505#[allow(clippy::unwrap_used, clippy::expect_used)]
506mod tests {
507 use super::*;
508 use axum::http::Request;
509 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
510
511 #[test]
512 fn traffic_shield_construction_is_runtime_independent() {
513 let shield = TrafficShield::new(TrafficShieldConfig::new().with_db_probe(false));
514
515 assert!(!shield.is_running());
516 assert_eq!(shield.start(), Err(TrafficShieldError::RuntimeUnavailable));
517 assert!(!shield.is_running());
518 }
519
520 #[tokio::test]
521 async fn traffic_shield_has_explicit_shared_shutdown() {
522 let shield = TrafficShield::new(TrafficShieldConfig::new().with_db_probe(false));
523 let clone = shield.clone();
524
525 shield.start().unwrap();
526 assert!(clone.is_running());
527 clone.shutdown();
528 assert!(!shield.is_running());
529 assert_eq!(shield.start(), Err(TrafficShieldError::AlreadyShutDown));
530 }
531
532 #[tokio::test]
533 async fn dropping_final_shield_aborts_monitor_tasks() {
534 let shield = TrafficShield::new(TrafficShieldConfig::new().with_db_probe(false));
535 let monitored_value = Arc::downgrade(&shield.event_loop_lag_ms);
536 shield.start().unwrap();
537 drop(shield);
538
539 tokio::time::timeout(Duration::from_millis(100), async {
540 while monitored_value.upgrade().is_some() {
541 tokio::task::yield_now().await;
542 }
543 })
544 .await
545 .unwrap();
546 }
547
548 #[test]
549 fn test_default_key_extractor() {
550 let req1 = Request::builder()
551 .header("x-forwarded-for", "192.168.1.1, 10.0.0.1")
552 .body(axum::body::Body::empty())
553 .unwrap();
554 assert_eq!(default_key_extractor(&req1), "missing-peer-address");
555
556 let req2 = Request::builder()
557 .header("x-real-ip", "10.0.0.2")
558 .body(axum::body::Body::empty())
559 .unwrap();
560 assert_eq!(default_key_extractor(&req2), "missing-peer-address");
561
562 let mut req3 = Request::builder().body(axum::body::Body::empty()).unwrap();
563 let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
564 req3.extensions_mut()
565 .insert(axum::extract::ConnectInfo(socket));
566 assert_eq!(default_key_extractor(&req3), "127.0.0.1");
567
568 let req4 = Request::builder().body(axum::body::Body::empty()).unwrap();
569 assert_eq!(default_key_extractor(&req4), "missing-peer-address");
570 }
571
572 #[tokio::test]
573 async fn test_traffic_shield_active_requests() {
574 let config = TrafficShieldConfig::new().with_db_probe(false);
575 let shield = TrafficShield::new(config);
576
577 assert_eq!(shield.active_requests(), 0);
578 shield
579 .active_requests
580 .fetch_add(5, std::sync::atomic::Ordering::SeqCst);
581 assert_eq!(shield.active_requests(), 5);
582 }
583
584 #[test]
585 fn test_traffic_pressure_classification() {
586 let config = TrafficShieldConfig::new().with_db_probe(false);
587
588 assert_eq!(
589 classify_traffic_pressure(&config, Duration::ZERO, Duration::ZERO, 0),
590 TrafficPressure::Normal
591 );
592 assert_eq!(
593 classify_traffic_pressure(&config, config.max_event_loop_lag / 2, Duration::ZERO, 0,),
594 TrafficPressure::Moderate
595 );
596 assert_eq!(
597 classify_traffic_pressure(&config, config.max_event_loop_lag, Duration::ZERO, 0,),
598 TrafficPressure::Critical
599 );
600 assert_eq!(
601 classify_traffic_pressure(&config, Duration::ZERO, config.max_db_latency, 0,),
602 TrafficPressure::Normal
603 );
604 }
605
606 #[test]
607 fn test_rate_limit_config_per_minute() {
608 let config = RateLimitConfig::per_minute(60.0);
609 assert_eq!(config.max_tokens, 60.0);
610 assert_eq!(config.refill_rate, 1.0);
611 }
612
613 #[test]
614 fn test_rate_limit_config_per_second() {
615 let config = RateLimitConfig::per_second(10.0);
616 assert_eq!(config.max_tokens, 10.0);
617 assert_eq!(config.refill_rate, 10.0);
618 }
619
620 #[test]
621 fn test_rate_limit_config_per_hour() {
622 let config = RateLimitConfig::per_hour(3600.0);
623 assert_eq!(config.max_tokens, 3600.0);
624 assert_eq!(config.refill_rate, 1.0);
625 }
626
627 #[tokio::test]
628 async fn test_traffic_shield_db_latency() {
629 let config = TrafficShieldConfig::new().with_db_probe(false);
630 let shield = TrafficShield::new(config);
631
632 assert_eq!(shield.db_latency().as_millis(), 0);
633 shield
634 .db_latency_ms
635 .store(50, std::sync::atomic::Ordering::Relaxed);
636 assert_eq!(shield.db_latency().as_millis(), 50);
637 }
638
639 #[tokio::test]
640 async fn test_traffic_shield_event_loop_lag() {
641 let config = TrafficShieldConfig::new().with_db_probe(false);
642 let shield = TrafficShield::new(config);
643
644 assert_eq!(shield.event_loop_lag().as_millis(), 0);
645 shield
646 .event_loop_lag_ms
647 .store(100, std::sync::atomic::Ordering::Relaxed);
648 assert_eq!(shield.event_loop_lag().as_millis(), 100);
649 }
650
651 #[test]
652 fn test_check_and_consume() {
653 let config = RateLimitConfig::per_second(2.0); let limiter = RateLimiter::new(config);
655
656 assert!(limiter.check_and_consume("test_key"));
658 assert!(limiter.check_and_consume("test_key"));
660 assert!(!limiter.check_and_consume("test_key"));
662 }
663 #[tokio::test]
664 async fn test_traffic_shield_config_builders() {
665 let config = TrafficShieldConfig::new()
666 .with_max_event_loop_lag(Duration::from_millis(999))
667 .with_max_db_latency(Duration::from_millis(888))
668 .with_max_active_requests(777);
669
670 assert_eq!(config.max_event_loop_lag, Duration::from_millis(999));
671 assert_eq!(config.max_db_latency, Duration::from_millis(888));
672 assert_eq!(config.max_active_requests, 777);
673 }
674
675 #[tokio::test]
676 async fn test_check_and_consume_refill() {
677 let config = RateLimitConfig::per_second(2.0); let limiter = RateLimiter::new(config);
679
680 assert!(limiter.check_and_consume("test_key"));
681 assert!(limiter.check_and_consume("test_key"));
682 assert!(!limiter.check_and_consume("test_key")); tokio::time::sleep(Duration::from_millis(600)).await; assert!(limiter.check_and_consume("test_key"));
687 assert!(!limiter.check_and_consume("test_key")); }
689
690 #[tokio::test]
691 async fn test_backpressure_middleware_critical() {
692 use axum::{Router, routing::get};
693 use tower::ServiceExt;
694
695 let config = TrafficShieldConfig::new().with_max_active_requests(0); let shield = TrafficShield::new(config);
697
698 let app =
699 Router::new()
700 .route("/", get(|| async { "OK" }))
701 .layer(axum::middleware::from_fn(move |req, next| {
702 let s = shield.clone();
703 async move { backpressure_middleware(s, req, next).await }
704 }));
705
706 let req = axum::http::Request::builder()
707 .uri("/")
708 .body(axum::body::Body::empty())
709 .unwrap();
710 let res = app.oneshot(req).await.unwrap();
711 assert_eq!(res.status(), axum::http::StatusCode::SERVICE_UNAVAILABLE);
712 }
713
714 #[tokio::test]
715 async fn test_backpressure_middleware_moderate() {
716 use axum::{Router, routing::get};
717 use std::time::Instant;
718 use tower::ServiceExt;
719
720 let config = TrafficShieldConfig::new().with_max_active_requests(100);
721 let shield = TrafficShield::new(config);
722 shield
723 .active_requests
724 .store(50, std::sync::atomic::Ordering::SeqCst); let app =
727 Router::new()
728 .route("/", get(|| async { "OK" }))
729 .layer(axum::middleware::from_fn(move |req, next| {
730 let s = shield.clone();
731 async move { backpressure_middleware(s, req, next).await }
732 }));
733
734 let req = axum::http::Request::builder()
735 .uri("/")
736 .body(axum::body::Body::empty())
737 .unwrap();
738 let start = Instant::now();
739 let res = app.oneshot(req).await.unwrap();
740 let elapsed = start.elapsed();
741
742 assert_eq!(res.status(), axum::http::StatusCode::OK);
743 assert!(elapsed >= Duration::from_millis(25)); }
745
746 #[tokio::test]
747 async fn test_rate_limit_middleware_rejection() {
748 use axum::{Router, routing::get};
749 use tower::ServiceExt;
750
751 let config = RateLimitConfig::per_second(1.0); let limiter = RateLimiter::new(config);
753
754 let app =
755 Router::new()
756 .route("/", get(|| async { "OK" }))
757 .layer(axum::middleware::from_fn(move |req, next| {
758 let l = limiter.clone();
759 async move { rate_limit_middleware(l, req, next).await }
760 }));
761
762 let req1 = axum::http::Request::builder()
764 .uri("/")
765 .body(axum::body::Body::empty())
766 .unwrap();
767 let res1 = app.clone().oneshot(req1).await.unwrap();
768 assert_eq!(res1.status(), axum::http::StatusCode::OK);
769
770 let req2 = axum::http::Request::builder()
772 .uri("/")
773 .body(axum::body::Body::empty())
774 .unwrap();
775 let res2 = app.oneshot(req2).await.unwrap();
776 assert_eq!(res2.status(), axum::http::StatusCode::TOO_MANY_REQUESTS);
777
778 let body_bytes = axum::body::to_bytes(res2.into_body(), 1024).await.unwrap();
779 assert_eq!(
780 String::from_utf8_lossy(&body_bytes),
781 "Rate limit exceeded. Please try again later."
782 );
783 }
784}
785
786#[path = "resilience_buckets.rs"]
787mod buckets;
788
789#[cfg(test)]
790#[path = "resilience_contract_tests.rs"]
791mod contract_tests;
792
793#[cfg(test)]
794#[path = "resilience_rate_limit_tests.rs"]
795mod rate_limit_tests;
796
797#[cfg(kani)]
798#[cfg_attr(mutants, mutants::skip)]
799mod kani_proofs {
800 use super::*;
801
802 #[kani::proof]
803 fn verify_token_bucket_math_safety() {
804 let max_tokens: f64 = kani::any();
805 let refill_rate: f64 = kani::any();
806 let current_tokens: f64 = kani::any();
807 let elapsed_secs: f64 = kani::any();
808
809 kani::assume(max_tokens > 0.0 && max_tokens < 1_000_000.0);
811 kani::assume(refill_rate >= 0.0 && refill_rate < 100_000.0);
812 kani::assume(current_tokens >= 0.0 && current_tokens <= max_tokens);
813 kani::assume(elapsed_secs >= 0.0 && elapsed_secs < 31_536_000.0); let config = RateLimitConfig::new(max_tokens, refill_rate);
816 let final_tokens = refill_token_count(current_tokens, elapsed_secs, &config);
817
818 assert!(!final_tokens.is_nan());
820 assert!(!final_tokens.is_infinite());
821 assert!(final_tokens >= 0.0);
822 assert!(final_tokens <= max_tokens);
823 }
824
825 #[kani::proof]
826 fn verify_traffic_shield_thresholds() {
827 let max_event_loop_lag_ns: u64 = kani::any();
828 let max_db_latency_ns: u64 = kani::any();
829 let max_active_requests: usize = kani::any();
830 let enable_db_probe: bool = kani::any();
831 let lag_ns: u64 = kani::any();
832 let db_latency_ns: u64 = kani::any();
833 let active: usize = kani::any();
834
835 let config = TrafficShieldConfig {
836 max_event_loop_lag: Duration::from_nanos(max_event_loop_lag_ns),
837 max_db_latency: Duration::from_nanos(max_db_latency_ns),
838 max_active_requests,
839 enable_db_probe,
840 };
841 let lag = Duration::from_nanos(lag_ns);
842 let db_latency = Duration::from_nanos(db_latency_ns);
843 let pressure = classify_traffic_pressure(&config, lag, db_latency, active);
844
845 let critical = lag >= config.max_event_loop_lag
846 || (enable_db_probe && db_latency >= config.max_db_latency)
847 || active >= max_active_requests;
848 let moderate = lag >= config.max_event_loop_lag / 2
849 || (enable_db_probe && db_latency >= config.max_db_latency / 2)
850 || active >= max_active_requests / 2;
851
852 match pressure {
853 TrafficPressure::Critical => {
854 assert!(critical);
855 }
856 TrafficPressure::Moderate => {
857 assert!(!critical);
858 assert!(moderate);
859 }
860 TrafficPressure::Normal => {
861 assert!(!critical);
862 assert!(!moderate);
863 }
864 }
865 }
866}