Skip to main content

xtalforge_modules/
jobs.rs

1//! Module jobs and reports (Task 5.10).
2
3use std::collections::VecDeque;
4
5use serde::{Deserialize, Serialize};
6use xtalforge_core::Error;
7
8/// Job state.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum JobStatus {
11    /// Waiting in the queue.
12    Queued,
13    /// Running.
14    Running,
15    /// Finished.
16    Done,
17    /// Failed.
18    Failed,
19    /// Cancelled.
20    Cancelled,
21}
22
23/// A queued module invocation.
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct Job {
26    /// Stable id.
27    pub id: u64,
28    /// Module name.
29    pub module: String,
30    /// JSON parameters.
31    pub parameters: serde_json::Value,
32    /// State.
33    pub status: JobStatus,
34}
35
36/// FIFO job queue.
37#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
38pub struct JobQueue {
39    jobs: VecDeque<Job>,
40    next: u64,
41}
42
43impl JobQueue {
44    /// Empty queue.
45    #[must_use]
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Adds a job and returns its id.
51    ///
52    /// # Errors
53    ///
54    /// Returns [`Error::InvalidInput`] for an empty module name.
55    pub fn submit(&mut self, module: &str, parameters: serde_json::Value) -> Result<u64, Error> {
56        if module.trim().is_empty() {
57            return Err(Error::InvalidInput("a job needs a module name".to_owned()));
58        }
59        let id = self.next;
60        self.next += 1;
61        self.jobs.push_back(Job {
62            id,
63            module: module.to_owned(),
64            parameters,
65            status: JobStatus::Queued,
66        });
67        Ok(id)
68    }
69
70    /// Pops the next queued job and marks it running.
71    pub fn start_next(&mut self) -> Option<Job> {
72        let mut job = self.jobs.pop_front()?;
73        job.status = JobStatus::Running;
74        self.jobs.push_back(job.clone());
75        Some(job)
76    }
77
78    /// Marks a running job finished.
79    pub fn finish(&mut self, id: u64) -> bool {
80        self.set_status(id, JobStatus::Done)
81    }
82
83    /// Marks a running job failed.
84    pub fn fail(&mut self, id: u64) -> bool {
85        self.set_status(id, JobStatus::Failed)
86    }
87
88    /// Cancels a queued job.
89    pub fn cancel(&mut self, id: u64) -> bool {
90        self.set_status(id, JobStatus::Cancelled)
91    }
92
93    /// All jobs in submission order.
94    #[must_use]
95    pub fn jobs(&self) -> Vec<&Job> {
96        self.jobs.iter().collect()
97    }
98
99    fn set_status(&mut self, id: u64, status: JobStatus) -> bool {
100        if let Some(job) = self.jobs.iter_mut().find(|job| job.id == id) {
101            job.status = status;
102            true
103        } else {
104            false
105        }
106    }
107}