From e50941ef24f9b2082199d628d5b2d48e819d2621 Mon Sep 17 00:00:00 2001 From: xfy Date: Mon, 13 Jul 2026 10:09:15 +0800 Subject: [PATCH] fix(runner): propagate duration_ms through SSE done event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SSE streaming path hardcoded '耗时: -' because duration_ms was never threaded through: - docker.rs: OutputChunk::Done now carries duration_ms, computed from start_container to wait completion via Instant::elapsed - sse.rs: DonePayload gains duration_ms, serialized into the done event - runner.rs: WASM done callback formats the real duration instead of '-' The polling fallback path already had correct duration via ExecResult; only the SSE path was missing it. --- src/api/code_runner/sse.rs | 3 +++ src/components/code_runner/runner.rs | 23 +++++++++++++++++++---- src/infra/docker.rs | 9 ++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/api/code_runner/sse.rs b/src/api/code_runner/sse.rs index 5be6001..e8dcd8f 100644 --- a/src/api/code_runner/sse.rs +++ b/src/api/code_runner/sse.rs @@ -31,6 +31,7 @@ struct DonePayload { exit_code: Option, oom_killed: bool, timed_out: bool, + duration_ms: u64, } /// SSE handler:从 EXEC_STREAMS 取出 receiver(取出即移除,防重复连接), @@ -52,12 +53,14 @@ pub async fn exec_stream( exit_code, oom_killed, timed_out, + duration_ms, } => Event::default() .event("done") .json_data(DonePayload { exit_code, oom_killed, timed_out, + duration_ms, }) .unwrap_or_else(|_| Event::default().event("done").data("{}")), }) diff --git a/src/components/code_runner/runner.rs b/src/components/code_runner/runner.rs index a82a101..625a824 100644 --- a/src/components/code_runner/runner.rs +++ b/src/components/code_runner/runner.rs @@ -569,6 +569,7 @@ mod sse_consumer { exit_code: Option, oom_killed: bool, timed_out: bool, + duration_ms: u64, } /// 启动 EventSource 消费 SSE 流。 @@ -630,17 +631,31 @@ mod sse_consumer { exit_code: None, oom_killed: false, timed_out: false, + duration_ms: 0, }); let (info, err) = if payload.timed_out { - ("耗时: - · 状态: 超时".to_string(), "超时".to_string()) + ( + format!("耗时: {}ms · 状态: 超时", payload.duration_ms), + "超时".to_string(), + ) } else if payload.oom_killed { - ("耗时: - · 状态: 内存超限".to_string(), "内存超限".to_string()) + ( + format!("耗时: {}ms · 状态: 内存超限", payload.duration_ms), + "内存超限".to_string(), + ) } else if payload.exit_code == Some(0) { - ("耗时: - · 状态: 成功".to_string(), String::new()) + ( + format!("耗时: {}ms · 状态: 成功", payload.duration_ms), + String::new(), + ) } else { ( - format!("退出码: {} · 运行错误", payload.exit_code.unwrap_or(-1)), + format!( + "耗时: {}ms · 退出码: {} · 运行错误", + payload.duration_ms, + payload.exit_code.unwrap_or(-1) + ), "运行错误".to_string(), ) }; diff --git a/src/infra/docker.rs b/src/infra/docker.rs index 7f9dda7..f676fba 100644 --- a/src/infra/docker.rs +++ b/src/infra/docker.rs @@ -231,7 +231,7 @@ pub async fn run_in_container( /// 流式输出 chunk:run_in_container_stream 边读日志边推送给 SSE handler。 /// -/// 序列化后作为 SSE event data;`Done` 同时携带终态信息(退出码 / OOM / 超时)。 +/// 序列化后作为 SSE event data;`Done` 同时携带终态信息(退出码 / OOM / 超时 / 耗时)。 #[derive(Clone, Debug)] pub enum OutputChunk { /// stdout 块(容器逐块产出)。 @@ -239,10 +239,12 @@ pub enum OutputChunk { /// stderr 块(容器逐块产出)。 Stderr(String), /// 终态:容器执行结束。exit_code=None 表示拿不到退出码(wait 出错)。 + /// duration_ms = start_container 到 wait 完成的耗时。 Done { exit_code: Option, oom_killed: bool, timed_out: bool, + duration_ms: u64, }, } @@ -320,6 +322,9 @@ pub async fn run_in_container_stream( .start_container(&container_id, None::) .await?; + // 容器开始执行的时刻,用于计算 duration_ms(start_container 返回即视为起点)。 + let start_time = std::time::Instant::now(); + // 写入源码到 stdin 后关闭 writer。 use tokio::io::AsyncWriteExt; let write_fut = async { @@ -477,12 +482,14 @@ pub async fn run_in_container_stream( .unwrap_or(false); // 推送终态 chunk(客户端已断开则跳过,send 必然失败)。 + let duration_ms = start_time.elapsed().as_millis() as u64; if !client_disconnected { let _ = tx .send(OutputChunk::Done { exit_code, oom_killed, timed_out, + duration_ms, }) .await; }