yggdrasil/api/code_runner/
progress.rs1use chrono::{DateTime, Duration, Utc};
9use dashmap::DashMap;
10use std::sync::LazyLock;
11
12use crate::api::code_runner::{ExecResult, ExecStatus, ExecTask};
13use crate::infra::docker::OutputChunk;
14use crate::infra::runner_config::RUNNER_CONFIG;
15
16pub static EXEC_TASKS: LazyLock<DashMap<String, ExecTask>> = LazyLock::new(DashMap::new);
18
19pub static EXEC_STREAMS: LazyLock<DashMap<String, StreamEntry>> = LazyLock::new(DashMap::new);
25
26pub struct StreamEntry {
28 pub rx: tokio::sync::mpsc::Receiver<OutputChunk>,
29 pub created_at: DateTime<Utc>,
30}
31
32pub fn insert_task(id: String) {
34 let task = ExecTask {
35 id: id.clone(),
36 status: ExecStatus::Queued,
37 stage: "排队中".to_string(),
38 created_at: Utc::now(),
39 result: None,
40 };
41 EXEC_TASKS.insert(id, task);
42}
43
44pub fn update_task_stage(id: &str, status: ExecStatus, stage: &str) {
46 if let Some(mut task) = EXEC_TASKS.get_mut(id) {
47 task.status = status;
48 task.stage = stage.to_string();
49 }
50}
51
52pub fn update_task_result(id: &str, status: ExecStatus, result: ExecResult) {
54 if let Some(mut task) = EXEC_TASKS.get_mut(id) {
55 task.status = status;
56 task.stage = "执行完毕".to_string();
57 task.result = Some(result);
58 }
59}
60
61pub fn gc_old_tasks() {
66 let ttl_secs = RUNNER_CONFIG.task_ttl_secs as i64;
67 let now = Utc::now();
68 EXEC_TASKS.retain(|_, task| {
69 let age = now - task.created_at;
70 age < Duration::seconds(ttl_secs)
71 });
72 EXEC_STREAMS.retain(|_, entry| {
73 let age = now - entry.created_at;
74 age < Duration::seconds(ttl_secs)
75 });
76}
77
78#[cfg(all(test, feature = "server"))]
79mod tests {
80 use super::*;
81
82 #[test]
83 #[serial_test::serial]
84 fn test_task_lifecycle_and_gc() {
85 let task_id = "test-task-progress-123".to_string();
86 insert_task(task_id.clone());
87 assert!(EXEC_TASKS.contains_key(&task_id));
88 assert_eq!(EXEC_TASKS.get(&task_id).unwrap().status, ExecStatus::Queued);
89
90 update_task_stage(&task_id, ExecStatus::Running, "运行中");
91 assert_eq!(
92 EXEC_TASKS.get(&task_id).unwrap().status,
93 ExecStatus::Running
94 );
95
96 let res = ExecResult {
97 status: ExecStatus::Success,
98 stdout: "hello".to_string(),
99 stderr: "".to_string(),
100 exit_code: Some(0),
101 duration_ms: 50,
102 language: "python".to_string(),
103 };
104 update_task_result(&task_id, ExecStatus::Success, res);
105 assert_eq!(
106 EXEC_TASKS.get(&task_id).unwrap().status,
107 ExecStatus::Success
108 );
109 assert!(EXEC_TASKS.get(&task_id).unwrap().result.is_some());
110
111 if let Some(mut task) = EXEC_TASKS.get_mut(&task_id) {
113 task.created_at = Utc::now() - Duration::seconds(1000);
114 }
115 gc_old_tasks();
116 assert!(!EXEC_TASKS.contains_key(&task_id));
117 }
118}