bash-tasks page: live interval poll + spawn_blocking the dir scan (review)

Two fixes from review:
- Liveness: /api/state isn't polled while online (only during login), so
  hooking refreshBashTasks to it only populated on cold load. Bash tasks
  start + finish asynchronously between turns, so add a light ~4s interval
  to keep the tasks pill live; the /api/state-time call now just does the
  first-paint populate. Doc note corrected to match.
- Move the blocking dir scan + per-file reads in /api/bash-tasks off the
  async executor via tokio::task::spawn_blocking (damocles nit).
This commit is contained in:
iris 2026-06-19 12:50:04 +02:00 committed by mara
commit cd7846b348
3 changed files with 37 additions and 19 deletions

View file

@ -539,8 +539,13 @@ async fn api_loose_ends(State(state): State<AppState>) -> Response {
/// fail the whole list.
async fn api_bash_tasks() -> Response {
let dir = crate::paths::harness_dir().join("bash-tasks");
let mut tasks: Vec<hive_sh4re::TaskFile> = Vec::new();
if let Ok(rd) = std::fs::read_dir(&dir) {
// The dir scan + per-file reads are blocking fs I/O; run them off the
// async executor so a slow or large tasks dir can't stall other requests.
let tasks = tokio::task::spawn_blocking(move || {
let mut tasks: Vec<hive_sh4re::TaskFile> = Vec::new();
let Ok(rd) = std::fs::read_dir(&dir) else {
return tasks;
};
for entry in rd.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("json") {
@ -559,17 +564,20 @@ async fn api_bash_tasks() -> Response {
tasks.push(task);
}
}
}
// Running before Pending, then oldest-first so a long-runner sits on top.
tasks.sort_by(|a, b| {
let rank = |s: &hive_sh4re::TaskStatus| match s {
hive_sh4re::TaskStatus::Running => 0,
_ => 1,
};
rank(&a.status)
.cmp(&rank(&b.status))
.then(a.created_at.cmp(&b.created_at))
});
// Running before Pending, then oldest-first so a long-runner sits on top.
tasks.sort_by(|a, b| {
let rank = |s: &hive_sh4re::TaskStatus| match s {
hive_sh4re::TaskStatus::Running => 0,
_ => 1,
};
rank(&a.status)
.cmp(&rank(&b.status))
.then(a.created_at.cmp(&b.created_at))
});
tasks
})
.await
.unwrap_or_default();
axum::Json(serde_json::json!({ "tasks": tasks })).into_response()
}