//! 内存任务缓冲表:基于 DashMap 的执行任务注册中心。 //! //! 异步执行模型:StartExec / StartExecStream 立即返回 task_id,容器在后台 tokio //! task 中运行。前端通过 GetExecResult 轮询 EXEC_TASKS 读取阶段与结果,或通过 //! SSE handler 取走 EXEC_STREAMS 的 receiver 做流式输出。任务条目在 TTL 过期后 //! 由 [`gc_old_tasks`] 回收,避免内存无限增长。 use chrono::{DateTime, Duration, Utc}; use dashmap::DashMap; use std::sync::LazyLock; use crate::api::code_runner::{ExecResult, ExecStatus, ExecTask}; use crate::infra::docker::OutputChunk; use crate::infra::runner_config::RUNNER_CONFIG; /// 全局任务注册表。 pub static EXEC_TASKS: LazyLock> = LazyLock::new(DashMap::new); /// 流式任务表:SSE handler 用 task_id 取出 receiver(一次性,取出即移除)。 /// /// 后台 task 持有对应的 sender,run_in_container_stream 推 chunk。前端连 SSE /// 时取走 receiver;若前端从不连接(或连接失败),entry 由 [`gc_old_tasks`] 按 /// TTL 回收,避免 receiver 泄漏。 pub static EXEC_STREAMS: LazyLock> = LazyLock::new(DashMap::new); /// EXEC_STREAMS 条目:receiver + 创建时间(供 GC)。 pub struct StreamEntry { pub rx: tokio::sync::mpsc::Receiver, pub created_at: DateTime, } /// 创建一个排队中的新任务。 pub fn insert_task(id: String) { let task = ExecTask { id: id.clone(), status: ExecStatus::Queued, stage: "排队中".to_string(), created_at: Utc::now(), result: None, }; EXEC_TASKS.insert(id, task); } /// 更新任务阶段(状态 + 描述),不改写 result。 pub fn update_task_stage(id: &str, status: ExecStatus, stage: &str) { if let Some(mut task) = EXEC_TASKS.get_mut(id) { task.status = status; task.stage = stage.to_string(); } } /// 写入最终结果:状态置为「执行完毕」并填充 result。 pub fn update_task_result(id: &str, status: ExecStatus, result: ExecResult) { if let Some(mut task) = EXEC_TASKS.get_mut(id) { task.status = status; task.stage = "执行完毕".to_string(); task.result = Some(result); } } /// 回收超过 `RUNNER_CONFIG.task_ttl_secs` 的历史任务。 /// /// 同时清理 EXEC_TASKS(轮询兜底)与 EXEC_STREAMS(SSE 流式)里的过期条目, /// 避免 receiver 泄漏(前端连了 start_exec_stream 但从不连 SSE 的情况)。 pub fn gc_old_tasks() { let ttl_secs = RUNNER_CONFIG.task_ttl_secs as i64; let now = Utc::now(); EXEC_TASKS.retain(|_, task| { let age = now - task.created_at; age < Duration::seconds(ttl_secs) }); EXEC_STREAMS.retain(|_, entry| { let age = now - entry.created_at; age < Duration::seconds(ttl_secs) }); } #[cfg(all(test, feature = "server"))] mod tests { use super::*; #[test] #[serial_test::serial] fn test_task_lifecycle_and_gc() { let task_id = "test-task-progress-123".to_string(); insert_task(task_id.clone()); assert!(EXEC_TASKS.contains_key(&task_id)); assert_eq!( EXEC_TASKS.get(&task_id).unwrap().status, ExecStatus::Queued ); update_task_stage(&task_id, ExecStatus::Running, "运行中"); assert_eq!( EXEC_TASKS.get(&task_id).unwrap().status, ExecStatus::Running ); let res = ExecResult { status: ExecStatus::Success, stdout: "hello".to_string(), stderr: "".to_string(), exit_code: Some(0), duration_ms: 50, language: "python".to_string(), }; update_task_result(&task_id, ExecStatus::Success, res); assert_eq!( EXEC_TASKS.get(&task_id).unwrap().status, ExecStatus::Success ); assert!(EXEC_TASKS.get(&task_id).unwrap().result.is_some()); // 修改创建时间以便测试 GC if let Some(mut task) = EXEC_TASKS.get_mut(&task_id) { task.created_at = Utc::now() - Duration::seconds(1000); } gc_old_tasks(); assert!(!EXEC_TASKS.contains_key(&task_id)); } }