Skip to main content

icebox/core/
job.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::time::{Duration, Instant};
5
6use crate::core::module::ModuleResult;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9pub struct JobId(pub u64);
10
11impl JobId {
12    pub fn as_u64(&self) -> u64 {
13        self.0
14    }
15}
16
17impl std::fmt::Display for JobId {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        write!(f, "{}", self.0)
20    }
21}
22
23static NEXT_JOB_ID: AtomicU64 = AtomicU64::new(1);
24
25fn next_job_id() -> JobId {
26    JobId(NEXT_JOB_ID.fetch_add(1, Ordering::Relaxed))
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30pub enum JobStatus {
31    Running,
32    Completed,
33    Failed,
34    Cancelled,
35}
36
37impl JobStatus {
38    pub fn as_str(&self) -> &'static str {
39        match self {
40            JobStatus::Running => "running",
41            JobStatus::Completed => "completed",
42            JobStatus::Failed => "failed",
43            JobStatus::Cancelled => "cancelled",
44        }
45    }
46}
47
48#[derive(Debug, Clone)]
49pub struct Job {
50    pub id: JobId,
51    pub module_name: String,
52    pub target: String,
53    pub status: JobStatus,
54    pub started_at: Instant,
55    pub finished_at: Option<Instant>,
56    pub result: Option<ModuleResult>,
57}
58
59impl Job {
60    pub fn new(module_name: impl Into<String>, target: impl Into<String>) -> Self {
61        Job {
62            id: next_job_id(),
63            module_name: module_name.into(),
64            target: target.into(),
65            status: JobStatus::Running,
66            started_at: Instant::now(),
67            finished_at: None,
68            result: None,
69        }
70    }
71
72    pub fn elapsed(&self) -> Duration {
73        let end = self.finished_at.unwrap_or(Instant::now());
74        end.duration_since(self.started_at)
75    }
76}
77
78#[derive(Debug, Default)]
79pub struct JobManager {
80    jobs: HashMap<JobId, Job>,
81}
82
83impl JobManager {
84    pub fn new() -> Self {
85        JobManager {
86            jobs: HashMap::new(),
87        }
88    }
89
90    pub fn register(&mut self, job: Job) -> JobId {
91        let id = job.id;
92        self.jobs.insert(id, job);
93        id
94    }
95
96    pub fn get(&self, id: JobId) -> Option<&Job> {
97        self.jobs.get(&id)
98    }
99
100    pub fn get_mut(&mut self, id: JobId) -> Option<&mut Job> {
101        self.jobs.get_mut(&id)
102    }
103
104    pub fn list(&self) -> Vec<&Job> {
105        let mut v: Vec<&Job> = self
106            .jobs
107            .values()
108            .filter(|j| matches!(j.status, JobStatus::Running))
109            .collect();
110        v.sort_by_key(|j| j.id);
111        v
112    }
113
114    pub fn list_recent(&self, n: usize) -> Vec<&Job> {
115        let mut v: Vec<&Job> = self.jobs.values().collect();
116        v.sort_by_key(|j| j.id);
117        v.into_iter().rev().take(n).collect()
118    }
119
120    pub fn complete(&mut self, id: JobId, result: ModuleResult) -> bool {
121        if let Some(job) = self.jobs.get_mut(&id) {
122            job.status = if result.success {
123                JobStatus::Completed
124            } else {
125                JobStatus::Failed
126            };
127            job.finished_at = Some(Instant::now());
128            job.result = Some(result);
129            true
130        } else {
131            false
132        }
133    }
134
135    pub fn advance_counter(min: u64) {
136        let mut prev = NEXT_JOB_ID.load(Ordering::Relaxed);
137        while prev <= min {
138            match NEXT_JOB_ID.compare_exchange(prev, min + 1, Ordering::Relaxed, Ordering::Relaxed)
139            {
140                Ok(_) => break,
141                Err(current) => prev = current,
142            }
143        }
144    }
145
146    pub fn cancel(&mut self, id: JobId) -> bool {
147        if let Some(job) = self.jobs.get_mut(&id) {
148            job.status = JobStatus::Cancelled;
149            job.finished_at = Some(Instant::now());
150            true
151        } else {
152            false
153        }
154    }
155}