1mod context;
11mod fallback;
12mod fallback_metrics;
13pub(crate) mod filters;
14mod handlers;
15mod http_trait;
16mod model_routing;
17mod model_routing_metrics;
18
19pub use context::{FallbackReason, RequestContext};
20pub use fallback::{FallbackDecision, FallbackEvaluator};
21pub use fallback_metrics::{get_fallback_metrics, init_fallback_metrics, FallbackMetrics};
22pub use model_routing::{extract_model_from_headers, find_upstream_for_model, ModelRoutingResult};
23pub use model_routing_metrics::{
24 get_model_routing_metrics, init_model_routing_metrics, ModelRoutingMetrics,
25};
26
27use anyhow::{Context, Result};
28use parking_lot::RwLock;
29use pingora::http::ResponseHeader;
30use pingora::prelude::*;
31use std::collections::HashMap;
32use std::sync::Arc;
33use std::time::Duration;
34use tokio::sync::broadcast;
35use tracing::{debug, error, info, warn};
36use uuid::Uuid;
37
38use zentinel_common::ids::{QualifiedId, Scope};
39use zentinel_common::{Registry, ScopedMetrics, ScopedRegistry};
40
41use crate::agents::AgentManager;
42use crate::app::AppState;
43use crate::builtin_handlers::BuiltinHandlerState;
44use crate::cache::{CacheConfig, CacheManager};
45use crate::errors::ErrorHandler;
46use crate::geo_filter::{GeoDatabaseWatcher, GeoFilterManager};
47use crate::health::PassiveHealthChecker;
48use crate::http_helpers;
49use crate::inference::InferenceRateLimitManager;
50use crate::logging::{LogManager, SharedLogManager};
51use crate::rate_limit::{RateLimitConfig, RateLimitManager};
52use crate::reload::{
53 ConfigManager, GracefulReloadCoordinator, ReloadEvent, RouteValidator, UpstreamValidator,
54};
55use crate::routing::RouteMatcher;
56use crate::scoped_routing::ScopedRouteMatcher;
57use crate::static_files::StaticFileServer;
58use crate::upstream::{ActiveHealthChecker, HealthCheckRunner, UpstreamPool};
59use crate::validation::SchemaValidator;
60
61use zentinel_common::TraceIdFormat;
62use zentinel_config::{Config, FlattenedConfig};
63
64pub struct ZentinelProxy {
66 pub config_manager: Arc<ConfigManager>,
68 pub(super) route_matcher: Arc<RwLock<RouteMatcher>>,
70 pub(super) scoped_route_matcher: Arc<tokio::sync::RwLock<ScopedRouteMatcher>>,
72 pub(super) upstream_pools: Registry<UpstreamPool>,
74 pub(super) scoped_upstream_pools: ScopedRegistry<UpstreamPool>,
76 pub(super) agent_manager: Arc<AgentManager>,
78 pub(super) passive_health: Arc<PassiveHealthChecker>,
80 pub(super) metrics: Arc<zentinel_common::observability::RequestMetrics>,
82 pub(super) scoped_metrics: Arc<ScopedMetrics>,
84 pub(super) app_state: Arc<AppState>,
86 pub(super) reload_coordinator: Arc<GracefulReloadCoordinator>,
88 pub(super) error_handlers: Registry<ErrorHandler>,
90 pub(super) validators: Registry<SchemaValidator>,
92 pub(super) static_servers: Registry<StaticFileServer>,
94 pub(super) builtin_state: Arc<BuiltinHandlerState>,
96 pub(super) log_manager: SharedLogManager,
98 pub(super) trace_id_format: TraceIdFormat,
100 pub(super) health_check_runner: Arc<HealthCheckRunner>,
102 pub(super) rate_limit_manager: Arc<RateLimitManager>,
104 pub(super) cache_manager: Arc<CacheManager>,
106 pub(super) geo_filter_manager: Arc<GeoFilterManager>,
108 pub(super) inference_rate_limit_manager: Arc<InferenceRateLimitManager>,
110 pub(super) warmth_tracker: Arc<crate::health::WarmthTracker>,
112 pub(super) guardrail_processor: Arc<crate::inference::GuardrailProcessor>,
114 pub acme_challenges: Option<Arc<crate::acme::ChallengeManager>>,
117 pub acme_clients: Vec<Arc<crate::acme::AcmeClient>>,
120}
121
122impl ZentinelProxy {
123 pub async fn new(config_path: Option<&str>) -> Result<Self> {
128 info!("Starting Zentinel Proxy");
129
130 let (config, effective_config_path) = match config_path {
132 Some(path) => {
133 let cfg = Config::from_file(path).context("Failed to load configuration file")?;
134 (cfg, path.to_string())
135 }
136 None => {
137 let cfg = Config::default_embedded()
138 .context("Failed to load embedded default configuration")?;
139 (cfg, "_embedded_".to_string())
141 }
142 };
143
144 config
145 .validate()
146 .context("Initial configuration validation failed")?;
147
148 if let Some(ref cache_config) = config.cache {
150 info!(
151 max_size_mb = cache_config.max_size_bytes / 1024 / 1024,
152 backend = ?cache_config.backend,
153 "Configuring HTTP cache storage"
154 );
155 crate::cache::configure_cache(cache_config.clone());
156 crate::cache::init_disk_cache_state().await;
157 }
158
159 let config_manager =
161 Arc::new(ConfigManager::new(&effective_config_path, config.clone()).await?);
162
163 config_manager.add_validator(Box::new(RouteValidator)).await;
165 config_manager
166 .add_validator(Box::new(UpstreamValidator))
167 .await;
168
169 let route_matcher = Arc::new(RwLock::new(RouteMatcher::new(config.routes.clone(), None)?));
171
172 let flattened = config.flatten();
174
175 let scoped_route_matcher = Arc::new(tokio::sync::RwLock::new(
177 ScopedRouteMatcher::from_flattened(&flattened)
178 .await
179 .context("Failed to create scoped route matcher")?,
180 ));
181
182 let mut pools = HashMap::new();
184 let mut health_check_runner = HealthCheckRunner::new();
185
186 for (upstream_id, upstream_config) in &config.upstreams {
187 let mut config_with_id = upstream_config.clone();
188 config_with_id.id = upstream_id.clone();
189 let pool = Arc::new(UpstreamPool::new(config_with_id.clone()).await?);
190 pools.insert(upstream_id.clone(), pool);
191
192 if let Some(checker) = ActiveHealthChecker::new(&config_with_id) {
194 health_check_runner.add_checker(checker);
195 }
196 }
197 let upstream_pools = Registry::from_map(pools);
198
199 let scoped_upstream_pools =
201 Self::create_scoped_upstream_pools(&flattened, &mut health_check_runner).await?;
202
203 let health_check_runner = Arc::new(health_check_runner);
204
205 let passive_health = Arc::new(PassiveHealthChecker::new(
207 0.5, 100, None, ));
211
212 let agent_manager = Arc::new(AgentManager::new(config.agents.clone()).await?);
214 agent_manager.initialize().await?;
215
216 let metrics = Arc::new(zentinel_common::observability::RequestMetrics::new()?);
218 let scoped_metrics =
219 Arc::new(ScopedMetrics::new().context("Failed to create scoped metrics collector")?);
220
221 let app_state = Arc::new(AppState::new(Uuid::new_v4().to_string()));
223
224 let reload_coordinator = Arc::new(GracefulReloadCoordinator::new(
226 Duration::from_secs(30), ));
228
229 Self::setup_reload_handler(
231 config_manager.clone(),
232 route_matcher.clone(),
233 upstream_pools.clone(),
234 scoped_route_matcher.clone(),
235 scoped_upstream_pools.clone(),
236 )
237 .await;
238
239 let (error_handlers, validators, static_servers) =
241 Self::initialize_route_components(&config).await?;
242
243 let builtin_state = Arc::new(BuiltinHandlerState::new(
245 env!("CARGO_PKG_VERSION").to_string(),
246 app_state.instance_id.clone(),
247 ));
248
249 let log_manager = match LogManager::new(&config.observability.logging) {
251 Ok(manager) => {
252 if manager.access_log_enabled() {
253 info!("Access logging enabled");
254 }
255 if manager.error_log_enabled() {
256 info!("Error logging enabled");
257 }
258 if manager.audit_log_enabled() {
259 info!("Audit logging enabled");
260 }
261 Arc::new(manager)
262 }
263 Err(e) => {
264 warn!(
265 "Failed to initialize log manager, file logging disabled: {}",
266 e
267 );
268 Arc::new(LogManager::disabled())
269 }
270 };
271
272 {
274 use crate::reload::AuditReloadHook;
275 let audit_hook = AuditReloadHook::new(log_manager.clone());
276 config_manager.add_hook(Box::new(audit_hook)).await;
277 debug!("Registered audit reload hook");
278 }
279
280 if health_check_runner.checker_count() > 0 {
282 let runner = health_check_runner.clone();
283 tokio::spawn(async move {
284 runner.run().await;
285 });
286 info!(
287 "Started active health checking for {} upstreams",
288 health_check_runner.checker_count()
289 );
290 }
291
292 let rate_limit_manager = Arc::new(Self::initialize_rate_limiters(&config));
294
295 let inference_rate_limit_manager =
297 Arc::new(Self::initialize_inference_rate_limiters(&config));
298
299 let warmth_tracker = Arc::new(crate::health::WarmthTracker::with_defaults());
301
302 let guardrail_processor = Arc::new(crate::inference::GuardrailProcessor::new(
304 agent_manager.clone(),
305 ));
306
307 let geo_filter_manager = Arc::new(Self::initialize_geo_filters(&config));
309
310 Self::spawn_cleanup_task(rate_limit_manager.clone(), geo_filter_manager.clone());
312
313 Self::spawn_geo_database_watcher(geo_filter_manager.clone());
315
316 app_state.set_ready(true);
318
319 let trace_id_format = config.server.trace_id_format;
321
322 let cache_manager = Arc::new(Self::initialize_cache_manager(&config));
324
325 if let Err(e) = init_fallback_metrics() {
327 warn!("Failed to initialize fallback metrics: {}", e);
328 }
329
330 if let Err(e) = init_model_routing_metrics() {
332 warn!("Failed to initialize model routing metrics: {}", e);
333 }
334
335 if let Err(e) = crate::tls_metrics::init_tls_metrics() {
337 warn!("Failed to initialize TLS metrics: {}", e);
338 }
339
340 Ok(Self {
341 config_manager,
342 route_matcher,
343 scoped_route_matcher,
344 upstream_pools,
345 scoped_upstream_pools,
346 agent_manager,
347 passive_health,
348 metrics,
349 scoped_metrics,
350 app_state,
351 reload_coordinator,
352 error_handlers,
353 validators,
354 static_servers,
355 builtin_state,
356 log_manager,
357 trace_id_format,
358 health_check_runner,
359 rate_limit_manager,
360 cache_manager,
361 geo_filter_manager,
362 inference_rate_limit_manager,
363 warmth_tracker,
364 guardrail_processor,
365 acme_challenges: None,
367 acme_clients: Vec::new(),
368 })
369 }
370
371 async fn setup_reload_handler(
373 config_manager: Arc<ConfigManager>,
374 route_matcher: Arc<RwLock<RouteMatcher>>,
375 upstream_pools: Registry<UpstreamPool>,
376 scoped_route_matcher: Arc<tokio::sync::RwLock<ScopedRouteMatcher>>,
377 scoped_upstream_pools: ScopedRegistry<UpstreamPool>,
378 ) {
379 let mut reload_rx = config_manager.subscribe();
380 let config_manager_clone = config_manager.clone();
381
382 tokio::spawn(async move {
383 loop {
384 match reload_rx.recv().await {
385 Ok(ReloadEvent::Applied { .. }) => {}
386 Ok(_) => continue,
387 Err(broadcast::error::RecvError::Lagged(n)) => {
388 warn!("Reload handler lagged by {n} events, applying latest config");
389 }
391 Err(broadcast::error::RecvError::Closed) => break,
392 };
393 {
394 let new_config = config_manager_clone.current();
396 let flattened = new_config.flatten();
397
398 match RouteMatcher::new(new_config.routes.clone(), None) {
400 Ok(new_matcher) => {
401 *route_matcher.write() = new_matcher;
402 info!(
403 routes = new_config.routes.len(),
404 "Global routes reloaded successfully"
405 );
406 }
407 Err(e) => {
408 error!(error = %e, "Failed to compile route matcher");
409 }
410 }
411
412 if let Err(e) = scoped_route_matcher
414 .write()
415 .await
416 .load_from_flattened(&flattened)
417 .await
418 {
419 error!("Failed to reload scoped routes: {}", e);
420 }
421
422 let pool_update = async {
425 let mut new_pools = HashMap::new();
426 for (upstream_id, upstream_config) in &new_config.upstreams {
427 let mut config_with_id = upstream_config.clone();
428 config_with_id.id = upstream_id.clone();
429 match UpstreamPool::new(config_with_id).await {
430 Ok(pool) => {
431 new_pools.insert(upstream_id.clone(), Arc::new(pool));
432 }
433 Err(e) => {
434 error!("Failed to create upstream pool {}: {}", upstream_id, e);
435 }
436 }
437 }
438 new_pools
439 };
440
441 match tokio::time::timeout(Duration::from_secs(10), pool_update).await {
442 Ok(new_pools) => {
443 let old_pools = upstream_pools.replace(new_pools).await;
444
445 let new_scoped_pools = Self::build_scoped_pools_list(&flattened).await;
447 let old_scoped_pools =
448 scoped_upstream_pools.replace_all(new_scoped_pools).await;
449
450 tokio::spawn(async move {
452 let tracker = crate::upstream::drain::DrainTracker::default();
453 tracker.track_pools(old_pools).await;
454 tracker.track_pools(old_scoped_pools).await;
455 });
456 }
457 Err(_) => {
458 warn!("Upstream pool update timed out after 10s, routes still updated");
459 }
460 }
461 }
462 }
463 });
464 }
465
466 async fn create_scoped_upstream_pools(
468 flattened: &FlattenedConfig,
469 health_check_runner: &mut HealthCheckRunner,
470 ) -> Result<ScopedRegistry<UpstreamPool>> {
471 let registry = ScopedRegistry::new();
472
473 for (qid, upstream_config) in &flattened.upstreams {
474 let mut config_with_id = upstream_config.clone();
475 config_with_id.id = qid.canonical();
476
477 let pool = Arc::new(
478 UpstreamPool::new(config_with_id.clone())
479 .await
480 .with_context(|| {
481 format!("Failed to create upstream pool '{}'", qid.canonical())
482 })?,
483 );
484
485 let is_exported = flattened
487 .exported_upstreams
488 .contains_key(&upstream_config.id);
489
490 if is_exported {
491 registry.insert_exported(qid.clone(), pool).await;
492 } else {
493 registry.insert(qid.clone(), pool).await;
494 }
495
496 if let Some(checker) = ActiveHealthChecker::new(&config_with_id) {
498 health_check_runner.add_checker(checker);
499 }
500
501 debug!(
502 upstream_id = %qid.canonical(),
503 scope = ?qid.scope,
504 exported = is_exported,
505 "Created scoped upstream pool"
506 );
507 }
508
509 info!("Created {} scoped upstream pools", registry.len().await);
510
511 Ok(registry)
512 }
513
514 async fn build_scoped_pools_list(
516 flattened: &FlattenedConfig,
517 ) -> Vec<(QualifiedId, Arc<UpstreamPool>, bool)> {
518 let mut result = Vec::new();
519
520 for (qid, upstream_config) in &flattened.upstreams {
521 let mut config_with_id = upstream_config.clone();
522 config_with_id.id = qid.canonical();
523
524 match UpstreamPool::new(config_with_id).await {
525 Ok(pool) => {
526 let is_exported = flattened
527 .exported_upstreams
528 .contains_key(&upstream_config.id);
529 result.push((qid.clone(), Arc::new(pool), is_exported));
530 }
531 Err(e) => {
532 error!(
533 "Failed to create scoped upstream pool {}: {}",
534 qid.canonical(),
535 e
536 );
537 }
538 }
539 }
540
541 result
542 }
543
544 async fn initialize_route_components(
546 config: &Config,
547 ) -> Result<(
548 Registry<ErrorHandler>,
549 Registry<SchemaValidator>,
550 Registry<StaticFileServer>,
551 )> {
552 let mut error_handlers_map = HashMap::new();
553 let mut validators_map = HashMap::new();
554 let mut static_servers_map = HashMap::new();
555
556 for route in &config.routes {
557 info!(
558 "Initializing components for route: {} with service type: {:?}",
559 route.id, route.service_type
560 );
561
562 if let Some(ref error_config) = route.error_pages {
564 let handler =
565 ErrorHandler::new(route.service_type.clone(), Some(error_config.clone()));
566 error_handlers_map.insert(route.id.clone(), Arc::new(handler));
567 debug!("Initialized error handler for route: {}", route.id);
568 } else {
569 let handler = ErrorHandler::new(route.service_type.clone(), None);
571 error_handlers_map.insert(route.id.clone(), Arc::new(handler));
572 }
573
574 if route.service_type == zentinel_config::ServiceType::Api {
576 if let Some(ref api_schema) = route.api_schema {
577 match SchemaValidator::new(api_schema.clone()) {
578 Ok(validator) => {
579 validators_map.insert(route.id.clone(), Arc::new(validator));
580 info!("Initialized schema validator for route: {}", route.id);
581 }
582 Err(e) => {
583 warn!(
584 "Failed to initialize schema validator for route {}: {}",
585 route.id, e
586 );
587 }
588 }
589 }
590 }
591
592 if route.service_type == zentinel_config::ServiceType::Static {
594 if let Some(ref static_config) = route.static_files {
595 let server = StaticFileServer::new(static_config.clone());
596 static_servers_map.insert(route.id.clone(), Arc::new(server));
597 info!("Initialized static file server for route: {}", route.id);
598 } else {
599 warn!(
600 "Static route {} has no static_files configuration",
601 route.id
602 );
603 }
604 }
605 }
606
607 Ok((
608 Registry::from_map(error_handlers_map),
609 Registry::from_map(validators_map),
610 Registry::from_map(static_servers_map),
611 ))
612 }
613
614 pub(super) fn get_trace_id(&self, session: &pingora::proxy::Session) -> String {
616 http_helpers::get_or_create_trace_id(session, self.trace_id_format)
617 }
618
619 fn initialize_rate_limiters(config: &Config) -> RateLimitManager {
621 use zentinel_config::RateLimitAction;
622
623 let manager = if let Some(ref global) = config.rate_limits.global {
625 info!(
626 max_rps = global.max_rps,
627 burst = global.burst,
628 key = ?global.key,
629 "Initializing global rate limiter"
630 );
631 RateLimitManager::with_global_limit(global.max_rps, global.burst)
632 } else {
633 RateLimitManager::new()
634 };
635
636 for route in &config.routes {
637 if let Some(ref rate_limit) = route.policies.rate_limit {
639 let rl_config = RateLimitConfig {
640 max_rps: rate_limit.requests_per_second,
641 burst: rate_limit.burst,
642 key: rate_limit.key.clone(),
643 action: RateLimitAction::Reject,
644 status_code: 429,
645 message: None,
646 backend: zentinel_config::RateLimitBackend::Local,
647 max_delay_ms: 5000, };
649 manager.register_route(&route.id, rl_config);
650 info!(
651 route_id = %route.id,
652 max_rps = rate_limit.requests_per_second,
653 burst = rate_limit.burst,
654 key = ?rate_limit.key,
655 "Registered rate limiter for route"
656 );
657 }
658
659 for filter_id in &route.filters {
661 if let Some(filter_config) = config.filters.get(filter_id) {
662 if let zentinel_config::Filter::RateLimit(ref rl_filter) = filter_config.filter
663 {
664 let rl_config = RateLimitConfig {
665 max_rps: rl_filter.max_rps,
666 burst: rl_filter.burst,
667 key: rl_filter.key.clone(),
668 action: rl_filter.on_limit.clone(),
669 status_code: rl_filter.status_code,
670 message: rl_filter.limit_message.clone(),
671 backend: rl_filter.backend.clone(),
672 max_delay_ms: rl_filter.max_delay_ms,
673 };
674 manager.register_route(&route.id, rl_config);
675 info!(
676 route_id = %route.id,
677 filter_id = %filter_id,
678 max_rps = rl_filter.max_rps,
679 backend = ?rl_filter.backend,
680 "Registered rate limiter from filter for route"
681 );
682 }
683 }
684 }
685 }
686
687 if manager.route_count() > 0 {
688 info!(
689 route_count = manager.route_count(),
690 "Rate limiting initialized"
691 );
692 }
693
694 manager
695 }
696
697 fn initialize_inference_rate_limiters(config: &Config) -> InferenceRateLimitManager {
702 let manager = InferenceRateLimitManager::new();
703
704 for route in &config.routes {
705 if route.service_type == zentinel_config::ServiceType::Inference {
707 if let Some(ref inference_config) = route.inference {
708 manager.register_route(&route.id, inference_config);
709 }
710 }
711 }
712
713 if manager.route_count() > 0 {
714 info!(
715 route_count = manager.route_count(),
716 "Inference rate limiting initialized"
717 );
718 }
719
720 manager
721 }
722
723 fn initialize_cache_manager(config: &Config) -> CacheManager {
725 let manager = CacheManager::new();
726
727 let mut enabled_count = 0;
728
729 for route in &config.routes {
730 let cache_config = if let Some(ref rc) = route.policies.cache {
732 let exclude_paths = rc
734 .exclude_paths
735 .iter()
736 .filter_map(|pattern| {
737 let regex_str = crate::cache::compile_glob_to_regex(pattern);
738 match regex::Regex::new(®ex_str) {
739 Ok(re) => Some(re),
740 Err(e) => {
741 warn!(
742 route_id = %route.id,
743 pattern = %pattern,
744 error = %e,
745 "Failed to compile cache exclude-path pattern"
746 );
747 None
748 }
749 }
750 })
751 .collect();
752
753 CacheConfig {
754 enabled: rc.enabled,
755 default_ttl_secs: rc.default_ttl_secs,
756 max_size_bytes: rc.max_size_bytes,
757 cache_private: rc.cache_private,
758 stale_while_revalidate_secs: rc.stale_while_revalidate_secs,
759 stale_if_error_secs: rc.stale_if_error_secs,
760 cacheable_methods: rc.cacheable_methods.clone(),
761 cacheable_status_codes: rc.cacheable_status_codes.clone(),
762 exclude_extensions: rc.exclude_extensions.clone(),
763 exclude_paths,
764 }
765 } else {
766 match route.service_type {
767 zentinel_config::ServiceType::Static => CacheConfig {
768 enabled: true,
769 default_ttl_secs: 3600,
770 max_size_bytes: 50 * 1024 * 1024, stale_while_revalidate_secs: 60,
772 stale_if_error_secs: 300,
773 ..Default::default()
774 },
775 zentinel_config::ServiceType::Api => CacheConfig {
776 enabled: false,
777 default_ttl_secs: 60,
778 ..Default::default()
779 },
780 zentinel_config::ServiceType::Web => CacheConfig {
781 enabled: false,
782 default_ttl_secs: 300,
783 ..Default::default()
784 },
785 _ => CacheConfig::default(),
786 }
787 };
788
789 if cache_config.enabled {
790 enabled_count += 1;
791 info!(
792 route_id = %route.id,
793 default_ttl_secs = cache_config.default_ttl_secs,
794 from_config = route.policies.cache.is_some(),
795 "HTTP caching enabled for route"
796 );
797 }
798 manager.register_route(&route.id, cache_config);
799 }
800
801 if enabled_count > 0 {
802 info!(enabled_routes = enabled_count, "HTTP caching initialized");
803 } else {
804 debug!("HTTP cache manager initialized (no routes with caching enabled)");
805 }
806
807 manager
808 }
809
810 fn initialize_geo_filters(config: &Config) -> GeoFilterManager {
812 let manager = GeoFilterManager::new();
813
814 for (filter_id, filter_config) in &config.filters {
815 if let zentinel_config::Filter::Geo(ref geo_filter) = filter_config.filter {
816 match manager.register_filter(filter_id, geo_filter.clone()) {
817 Ok(_) => {
818 info!(
819 filter_id = %filter_id,
820 database_path = %geo_filter.database_path,
821 action = ?geo_filter.action,
822 countries_count = geo_filter.countries.len(),
823 "Registered geo filter"
824 );
825 }
826 Err(e) => {
827 error!(
828 filter_id = %filter_id,
829 error = %e,
830 "Failed to register geo filter"
831 );
832 }
833 }
834 }
835 }
836
837 let filter_ids = manager.filter_ids();
838 if !filter_ids.is_empty() {
839 info!(
840 filter_count = filter_ids.len(),
841 filter_ids = ?filter_ids,
842 "GeoIP filtering initialized"
843 );
844 }
845
846 manager
847 }
848
849 fn spawn_cleanup_task(
851 rate_limit_manager: Arc<RateLimitManager>,
852 geo_filter_manager: Arc<GeoFilterManager>,
853 ) {
854 const CLEANUP_INTERVAL: Duration = Duration::from_secs(300);
856
857 tokio::spawn(async move {
858 let mut interval = tokio::time::interval(CLEANUP_INTERVAL);
859 interval.tick().await;
861
862 loop {
863 interval.tick().await;
864
865 rate_limit_manager.cleanup();
867
868 geo_filter_manager.clear_expired_caches();
870
871 debug!("Periodic cleanup completed");
872 }
873 });
874
875 info!(
876 interval_secs = CLEANUP_INTERVAL.as_secs(),
877 "Started periodic cleanup task"
878 );
879 }
880
881 fn spawn_geo_database_watcher(geo_filter_manager: Arc<GeoFilterManager>) {
883 let watcher = Arc::new(GeoDatabaseWatcher::new(geo_filter_manager));
884
885 match watcher.start_watching() {
887 Ok(mut rx) => {
888 let watcher_clone = watcher.clone();
889 tokio::spawn(async move {
890 const DEBOUNCE_MS: u64 = 500;
892
893 while let Some(path) = rx.recv().await {
894 tokio::time::sleep(Duration::from_millis(DEBOUNCE_MS)).await;
896
897 while rx.try_recv().is_ok() {}
899
900 watcher_clone.handle_change(&path);
902 }
903 });
904
905 info!("Started geo database file watcher");
906 }
907 Err(e) => {
908 warn!(
909 error = %e,
910 "Failed to start geo database file watcher, auto-reload disabled"
911 );
912 }
913 }
914 }
915}