1#![allow(unused)]
2
3use crate::cacheable::{CacheAble, CacheService};
11use crate::errors::{CacheError, Error, Result};
12use crate::utils::lock::DistributedLockManager;
13use log::warn;
14use serde::{Deserialize, Serialize};
15use std::sync::Arc;
16use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub enum ErrorCategory {
21 Download,
23 Parse,
25 Auth,
27 RateLimit,
29 Other,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35pub enum ErrorSeverity {
36 Minor,
38 Major,
40 Fatal,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct ErrorRecord {
47 pub timestamp: u64,
48 pub category: ErrorCategory,
49 pub severity: ErrorSeverity,
50 pub message: String,
51 pub retry_count: usize,
52}
53impl CacheAble for ErrorRecord {
54 fn field() -> impl AsRef<str> {
55 "error_record".to_string()
56 }
57}
58#[derive(Debug, Clone, Serialize, Deserialize, Default)]
60pub struct ErrorStats {
61 pub total_errors: usize,
63 pub success_count: usize,
65 pub consecutive_errors: usize,
67 pub errors_by_category: std::collections::HashMap<ErrorCategory, usize>,
69 pub last_error_time: Option<u64>,
71 pub last_success_time: Option<u64>,
73 pub is_task_terminated: bool,
74 pub is_module_terminated: bool,
75}
76
77impl CacheAble for ErrorStats {
78 fn field() -> impl AsRef<str> {
79 "error_stats".to_string()
80 }
81}
82
83impl ErrorStats {
84 pub fn error_rate(&self) -> f64 {
86 let total = self.total_errors + self.success_count;
87 if total == 0 {
88 0.0
89 } else {
90 self.total_errors as f64 / total as f64
91 }
92 }
93
94 pub fn health_score(&self) -> f64 {
111 let error_rate = self.error_rate();
113 let consecutive_penalty = (self.consecutive_errors as f64 * 0.1).min(0.5);
114 (1.0 - error_rate - consecutive_penalty).max(0.0)
115 }
116}
117#[derive(Debug, Clone, Serialize, Deserialize, Default)]
118pub struct ModuleLocker {
119 pub is_locker: bool,
120 pub ts: u64,
121}
122impl CacheAble for ModuleLocker {
123 fn field() -> impl AsRef<str> {
124 "module_locker".to_string()
125 }
126}
127#[derive(Debug, Clone)]
129pub enum ErrorDecision {
130 Continue,
132 RetryAfter(Duration),
134 Skip,
136 Terminate(String),
138}
139
140#[derive(Debug, Clone)]
142pub struct ErrorTrackerConfig {
143 pub task_max_errors: usize,
145 pub module_max_errors: usize,
147 pub request_max_retries: usize,
149 pub parse_max_retries: usize,
151
152 pub enable_success_decay: bool,
154 pub success_decay_amount: usize,
156
157 pub enable_time_window: bool,
159 pub time_window_seconds: u64,
161
162 pub consecutive_error_threshold: usize,
164
165 pub error_ttl: u64,
167}
168
169impl Default for ErrorTrackerConfig {
170 fn default() -> Self {
171 Self {
172 task_max_errors: 100,
173 module_max_errors: 10,
174 request_max_retries: 3,
175 parse_max_retries: 3,
176 enable_success_decay: true,
177 success_decay_amount: 1,
178 enable_time_window: false,
179 time_window_seconds: 3600,
180 consecutive_error_threshold: 3,
181 error_ttl: 3600,
182 }
183 }
184}
185
186use dashmap::DashMap;
187
188#[derive(Clone)]
190pub struct StatusTracker {
191 cache_service: Arc<CacheService>,
192 config: ErrorTrackerConfig,
193 locker: Arc<DistributedLockManager>,
194 cache: Arc<DashMap<String, (ErrorStats, Instant)>>,
195}
196
197impl StatusTracker {
198 pub fn new(
199 cache_service: Arc<CacheService>,
200 config: ErrorTrackerConfig,
201 locker: Arc<DistributedLockManager>,
202 ) -> Self {
203 Self {
204 cache_service,
205 config,
206 locker,
207 cache: Arc::new(DashMap::new()),
208 }
209 }
210
211 pub async fn record_download_error(
215 &self,
216 task_id: &str,
217 module_id: &str,
218 request_id: &str,
219 error: &Error,
220 ) -> Result<ErrorDecision> {
221 let category = ErrorCategory::Download;
222 let severity = self.classify_error_severity(error);
223
224 let request_key = format!("request:{}:download", request_id);
225 let module_key = format!("module:{}:download", module_id);
226 let task_key = format!("task:{}:total", task_id);
227
228 let (request_res, module_res, task_res) = tokio::join!(
230 self.increment_error(&request_key, category, severity),
231 self.increment_error(&module_key, category, severity),
232 self.increment_error(&task_key, category, severity)
233 );
234
235 let request_errors = request_res?;
236 let module_errors = module_res?;
237 let task_errors = task_res?;
238
239 if severity == ErrorSeverity::Fatal {
241 return Ok(ErrorDecision::Terminate(format!(
242 "Fatal error encountered: {}",
243 error
244 )));
245 }
246
247 if task_errors >= self.config.task_max_errors {
248 return Ok(ErrorDecision::Terminate(format!(
249 "Task {} reached max errors: {}/{}",
250 task_id, task_errors, self.config.task_max_errors
251 )));
252 }
253
254 if module_errors >= self.config.module_max_errors {
255 self.release_module_locker(module_id).await;
257
258 return Ok(ErrorDecision::Terminate(format!(
259 "Module {} reached max errors: {}/{}",
260 module_id, module_errors, self.config.module_max_errors
261 )));
262 }
263
264 if request_errors >= self.config.request_max_retries {
265 return Ok(ErrorDecision::Skip);
266 }
267
268 let delay = match category {
270 ErrorCategory::RateLimit => Duration::from_secs(60),
271 ErrorCategory::Auth => Duration::from_secs(10),
272 _ => Duration::from_secs(2u64.pow(request_errors as u32).min(30)),
273 };
274
275 Ok(ErrorDecision::RetryAfter(delay))
276 }
277
278 pub async fn record_download_success(&self, request_id: &str) -> Result<()> {
282 let request_key = format!("request:{}:download", request_id);
283
284 if self.cache.contains_key(&request_key) {
290 self.cache.remove(&request_key);
291 }
292
293 Ok(())
294 }
295
296 pub async fn record_parse_error(
298 &self,
299 task_id: &str,
300 module_id: &str,
301 request_id: &str,
302 error: &Error,
303 ) -> Result<ErrorDecision> {
304 let category = ErrorCategory::Parse;
305 let severity = self.classify_error_severity(error);
306
307 log::debug!(
308 "[ErrorTracker] record_parse_error: task_id={} module_id={} request_id={} error={}",
309 task_id,
310 module_id,
311 request_id,
312 error
313 );
314
315 let request_key = format!("request:{}:parse", request_id);
317 let module_key = format!("module:{}:parse", module_id);
318 let task_key = format!("task:{}:total", task_id);
319
320 let (request_res, module_res, task_res) = tokio::join!(
321 self.increment_error(&request_key, category, severity),
322 self.increment_error(&module_key, category, severity),
323 self.increment_error(&task_key, category, severity)
324 );
325
326 let request_errors = request_res?;
327 let module_errors = module_res?;
328 let task_errors = task_res?;
329
330 log::debug!(
331 "[ErrorTracker] parse error counts: task={}/{} module={}/{} request={}/{}",
332 task_errors,
333 self.config.task_max_errors,
334 module_errors,
335 self.config.module_max_errors,
336 request_errors,
337 self.config.parse_max_retries
338 );
339
340 if task_errors >= self.config.task_max_errors {
342 return Ok(ErrorDecision::Terminate(format!(
343 "Task {} reached max errors: {}/{}",
344 task_id, task_errors, self.config.task_max_errors
345 )));
346 }
347
348 if module_errors >= self.config.module_max_errors {
349 self.release_module_locker(module_id).await;
351
352 warn!(
353 "[ErrorTracker] module parse errors exceeded threshold: module_id={} errors={}/{}",
354 module_id, module_errors, self.config.module_max_errors
355 );
356
357 return Ok(ErrorDecision::Terminate(format!(
358 "Module {} reached max errors: {}/{}",
359 module_id, module_errors, self.config.module_max_errors
360 )));
361 }
362
363 if request_errors >= self.config.parse_max_retries {
364 return Ok(ErrorDecision::Skip);
365 }
366
367 Ok(ErrorDecision::RetryAfter(Duration::from_secs(1)))
368 }
369
370 pub async fn record_parse_success(&self, request_id: &str) -> Result<()> {
372 let request_key = format!("request:{}:parse", request_id);
373
374 if self.cache.contains_key(&request_key) {
376 self.cache.remove(&request_key);
377 }
378
379 Ok(())
380 }
381
382 pub async fn should_module_continue(&self, module_id: &str) -> Result<ErrorDecision> {
386 if let Some(entry) = self.cache.get(module_id) {
388 let (stats, _) = entry.value();
389 if stats.is_module_terminated {
390 return Ok(ErrorDecision::Terminate(format!(
391 "Module {} has been terminated (cached)",
392 module_id
393 )));
394 }
395 }
396
397 let download_key = format!("module:{}:download", module_id);
398 let parse_key = format!("module:{}:parse", module_id);
399
400 let (terminated_res, download_res, parse_res) = tokio::join!(
401 self.is_module_terminated(module_id),
402 self.get_error_count(&download_key),
403 self.get_error_count(&parse_key)
404 );
405
406 if terminated_res? {
408 warn!(
409 "[ErrorTracker] module already terminated: module_id={}",
410 module_id
411 );
412 return Ok(ErrorDecision::Terminate(format!(
413 "Module {} has been terminated",
414 module_id
415 )));
416 }
417
418 let download_errors = download_res?;
419 let parse_errors = parse_res?;
420 let total_errors = download_errors + parse_errors;
421
422 log::debug!(
423 "[ErrorTracker] should_module_continue: module_id={} total_errors={}/{} (download={}, parse={})",
424 module_id,
425 total_errors,
426 self.config.module_max_errors,
427 download_errors,
428 parse_errors
429 );
430
431 if total_errors >= self.config.module_max_errors {
432 self.mark_module_terminated(module_id).await?;
434 self.release_module_locker(module_id).await;
435
436 log::error!(
437 "[ErrorTracker] module exceeded error threshold: module_id={} total_errors={}/{} (download={}, parse={})",
438 module_id,
439 total_errors,
440 self.config.module_max_errors,
441 download_errors,
442 parse_errors
443 );
444
445 Ok(ErrorDecision::Terminate(format!(
446 "Module {} total errors: {}/{} (download: {}, parse: {})",
447 module_id,
448 total_errors,
449 self.config.module_max_errors,
450 download_errors,
451 parse_errors
452 )))
453 } else {
454 Ok(ErrorDecision::Continue)
455 }
456 }
457
458 pub async fn should_task_continue(&self, task_id: &str) -> Result<ErrorDecision> {
460 if let Some(entry) = self.cache.get(task_id) {
462 let (stats, _) = entry.value();
463 if stats.is_task_terminated {
464 return Ok(ErrorDecision::Terminate(format!(
465 "Task {} has been terminated (cached)",
466 task_id
467 )));
468 }
469 }
470
471 let task_key = format!("task:{}:total", task_id);
472
473 let (terminated_res, count_res) = tokio::join!(
474 self.is_task_terminated(task_id),
475 self.get_error_count(&task_key)
476 );
477
478 if terminated_res? {
480 return Ok(ErrorDecision::Terminate(format!(
481 "Task {} has been terminated",
482 task_id
483 )));
484 }
485
486 let task_errors = count_res?;
487
488 if task_errors >= self.config.task_max_errors {
489 self.mark_task_terminated(task_id).await?;
491
492 Ok(ErrorDecision::Terminate(format!(
493 "Task {} reached max errors: {}/{}",
494 task_id, task_errors, self.config.task_max_errors
495 )))
496 } else {
497 Ok(ErrorDecision::Continue)
498 }
499 }
500
501 pub async fn get_stats(&self, key: &str) -> Result<ErrorStats> {
503 if let Some(entry) = self.cache.get(key) {
504 let (stats, expires_at) = entry.value();
505 if Instant::now() < *expires_at {
506 return Ok(stats.clone());
508 } else {
509 }
511 } else {
512 }
514 self.get_stats_no_cache(key).await
515 }
516
517 pub async fn get_stats_no_cache(&self, key: &str) -> Result<ErrorStats> {
518 let stats_res = ErrorStats::sync(key, &self.cache_service).await;
519
520 match stats_res {
521 Ok(Some(stats)) => {
522 self.cache.insert(
523 key.to_string(),
524 (stats.clone(), Instant::now() + Duration::from_secs(10)),
525 );
526 Ok(stats)
527 }
528 Ok(None) => {
529 let stats = ErrorStats::default();
532 self.cache.insert(
533 key.to_string(),
534 (stats.clone(), Instant::now() + Duration::from_secs(5)),
535 );
536 Ok(stats)
537 }
538 Err(e) => Err(Error::from(e)),
539 }
540 }
541
542 pub async fn mark_task_terminated(&self, task_id: &str) -> Result<()> {
544 let cache_id = ErrorStats::cache_id(task_id, &self.cache_service);
545 self.locker
546 .with_lock(&cache_id, 10, Duration::from_secs(10), async {
547 let mut error_stats = self.get_stats_no_cache(task_id).await.unwrap_or_default();
548 error_stats.is_task_terminated = true;
549 error_stats.send(task_id, &self.cache_service).await
550 })
551 .await
552 .ok();
553
554 Ok(())
555 }
556
557 pub async fn is_task_terminated(&self, task_id: &str) -> Result<bool> {
559 match self.get_stats(task_id).await {
560 Ok(stats) => Ok(stats.is_task_terminated),
561 Err(e) => {
562 if let Some(cache_err) = e
564 .inner
565 .source
566 .as_ref()
567 .and_then(|s| s.downcast_ref::<CacheError>())
568 && matches!(cache_err, CacheError::NotFound)
569 {
570 return Ok(false);
571 }
572 warn!(
573 "[ErrorTracker] task terminated check failed: task_id={}, error: {}",
574 task_id, e
575 );
576 Err(e)
577 }
578 }
579 }
580
581 pub async fn mark_module_terminated(&self, module_id: &str) -> Result<()> {
583 let cache_id = ErrorStats::cache_id(module_id, &self.cache_service);
584 self.locker
585 .with_lock(&cache_id, 10, Duration::from_secs(10), async {
586 let mut error_stats = self.get_stats_no_cache(module_id).await.unwrap_or_default();
587 error_stats.is_module_terminated = true;
588 error_stats.send(module_id, &self.cache_service).await
589 })
590 .await
591 .ok();
592
593 Ok(())
594 }
595
596 pub async fn is_module_terminated(&self, module_id: &str) -> Result<bool> {
598 match self.get_stats(module_id).await {
599 Ok(stats) => Ok(stats.is_module_terminated),
600 Err(e) => {
601 if let Some(cache_err) = e
603 .inner
604 .source
605 .as_ref()
606 .and_then(|s| s.downcast_ref::<CacheError>())
607 && matches!(cache_err, CacheError::NotFound)
608 {
609 return Ok(false);
610 }
611 warn!(
612 "[ErrorTracker] module terminated check failed: module_id={}, error: {}",
613 module_id, e
614 );
615 Err(e)
616 }
617 }
618 }
619
620 async fn increment_error(
624 &self,
625 key: &str,
626 category: ErrorCategory,
627 _severity: ErrorSeverity,
628 ) -> Result<usize> {
629 if key.starts_with("request:") {
631 let count_key = format!("{}:total_errors", key);
634 let total = self.cache_service.incr(&count_key, 1).await? as usize;
636 return Ok(total);
637 }
638
639 let count_key = format!("{}:total_errors", key);
642 let total = self.cache_service.incr(&count_key, 1).await? as usize;
643
644 let consecutive_key = format!("{}:consecutive_errors", key);
645 let _ = self.cache_service.incr(&consecutive_key, 1).await;
646
647 Ok(total)
651 }
652
653 async fn decrement_error(&self, key: &str, amount: usize) -> Result<()> {
655 let count_key = format!("{}:total_errors", key);
656 let _ = self.cache_service.incr(&count_key, -(amount as i64)).await;
658
659 let consecutive_key = format!("{}:consecutive_errors", key);
660 let _ = self.cache_service.set(&consecutive_key, b"0", None).await;
662
663 Ok(())
664 }
665
666 async fn increment_success(&self, key: &str) -> Result<()> {
668 let mut stats = self.get_stats(key).await.unwrap_or_default();
669 stats.success_count += 1;
670 stats.last_success_time = Some(Self::now());
671 self.save_stats_with_ttl(key, &stats).await.ok();
672 Ok(())
673 }
674
675 async fn reset_consecutive_errors(&self, key: &str) -> Result<()> {
677 let mut stats = self.get_stats(key).await.unwrap_or_default();
678 stats.consecutive_errors = 0;
679 self.save_stats_with_ttl(key, &stats).await.ok();
680 Ok(())
681 }
682
683 async fn get_error_count(&self, key: &str) -> Result<usize> {
685 let count_key = format!("{}:total_errors", key);
686 if let Some(val) = self.cache_service.get(&count_key).await? {
687 let s = String::from_utf8(val).unwrap_or_default();
689 let count = s.parse::<usize>().unwrap_or(0);
690 return Ok(count);
691 }
692
693 Ok(0)
696 }
697
698 async fn save_stats(&self, key: &str, stats: &ErrorStats) -> Result<()> {
700 self.save_stats_with_ttl(key, stats).await
701 }
702
703 async fn save_stats_with_ttl(&self, key: &str, stats: &ErrorStats) -> Result<()> {
705 self.cache.insert(
706 key.to_string(),
707 (stats.clone(), Instant::now() + Duration::from_secs(10)),
708 );
709 stats
710 .send(key, &self.cache_service)
711 .await
712 .map_err(Error::from)
713 }
714
715 fn classify_error_severity(&self, error: &Error) -> ErrorSeverity {
717 let error_str = error.to_string().to_lowercase();
718
719 if error_str.contains("auth") || error_str.contains("unauthorized") {
720 ErrorSeverity::Fatal
721 } else if error_str.contains("rate limit") || error_str.contains("too many") {
722 ErrorSeverity::Major
723 } else {
724 ErrorSeverity::Minor
725 }
726 }
727
728 fn now() -> u64 {
729 SystemTime::now()
730 .duration_since(UNIX_EPOCH)
731 .unwrap()
732 .as_secs()
733 }
734
735 pub async fn lock_module(&self, module_id: &str) {
736 let ts = SystemTime::now()
737 .duration_since(SystemTime::UNIX_EPOCH)
738 .unwrap()
739 .as_secs();
740 let module_locker = ModuleLocker {
741 is_locker: true,
742 ts,
743 };
744 module_locker
745 .send(module_id, &self.cache_service)
746 .await
747 .ok();
748 }
749 pub async fn is_module_locker(&self, module_id: &str, ttl: u64) -> bool {
750 ModuleLocker::sync(module_id, &self.cache_service)
751 .await
752 .ok()
753 .flatten()
754 .is_some_and(|locker| {
755 if locker.is_locker {
756 let now = SystemTime::now()
757 .duration_since(SystemTime::UNIX_EPOCH)
758 .unwrap()
759 .as_secs();
760 now - locker.ts <= ttl
761 } else {
762 false
763 }
764 })
765 }
766 pub async fn release_module_locker(&self, module_id: &str) {
767 ModuleLocker::delete(module_id, &self.cache_service)
768 .await
769 .ok();
770 }
771}
772
773#[cfg(test)]
774mod tests {
775 use super::*;
776
777 #[test]
778 fn test_error_stats_calculations() {
779 let mut stats = ErrorStats::default();
780 stats.total_errors = 3;
781 stats.success_count = 7;
782
783 assert_eq!(stats.error_rate(), 0.3);
784 assert!(stats.health_score() > 0.6);
785
786 stats.consecutive_errors = 5;
787 assert!(stats.health_score() < 0.5);
788 }
789}