diff --git a/src/run.rs b/src/run.rs
index f3c8114..2f79bf2 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -231,8 +231,19 @@ impl Run {
.as_mut()
.map(|w| w.is_running())
.unwrap_or(false);
- if !running || elapsed >= Duration::from_secs(*duration_secs) {
- // Workload finished (on its own or we stop it now).
+ if !running {
+ // Workload self-exited — finish immediately.
+ return self.finish_current_workload();
+ }
+ // Workload still running. Stop it when the duration
+ // elapses, PLUS a grace period. Some workloads (y-cruncher)
+ // include setup time (allocation, topology detection)
+ // in their `-D:T` time limit, so their wall time is T + ~1s.
+ // Without the grace, the run loop's duration timer fires
+ // just before the workload self-exits → false Stopped
+ // verdict. The grace matches the smoke harness's 1s margin.
+ let grace = Duration::from_secs(2);
+ if elapsed >= Duration::from_secs(*duration_secs) + grace {
return self.finish_current_workload();
}
Ok(TickOutcome::Idle)
@@ -472,7 +483,7 @@ mod tests {
#[test]
fn stopped_verdict_when_duration_elapses() {
// MockWorkload that never self-exits (ticks_until_exit huge);
- // the run's duration timer must stop it → Stopped verdict.
+ // the run's duration timer + grace must stop it → Stopped verdict.
let _ = std::fs::remove_dir_all("/tmp/opencode/run-tests");
let cfg = mock_cfg(vec![
WorkloadSpec { name: "never-exits".to_string(), duration_secs: 0, cores: "0-15".to_string() },
@@ -480,6 +491,13 @@ mod tests {
let mut run = run_with_mocks(cfg, vec![
MockWorkload { name: "never-exits".to_string(), ticks_until_exit: 1000, ticks_seen: 0, started: false, stopped: false },
]);
+ // Tick once to start the workload (Cooldown → Running).
+ let _ = run.tick().unwrap();
+ // Manually rewind state_start so elapsed exceeds duration (0) +
+ // the 2s grace. Without this the test would need to sleep 2s.
+ run.state_start = Instant::now()
+ .checked_sub(Duration::from_secs(3))
+ .unwrap_or(Instant::now());
let mut verdict = None;
for _ in 0..10 {
if let TickOutcome::WorkloadFinished { verdict: v, .. } = run.tick().unwrap() {
@@ -487,7 +505,7 @@ mod tests {
break;
}
}
- // duration_secs=0 → first Running tick sees elapsed >= 0 → stops.
+ // elapsed (3s) >= duration (0) + grace (2s) → stops.
assert_eq!(verdict, Some(Verdict::Stopped));
}
Fix y-cruncher Stopped verdict: add 2s grace to duration timer
y-cruncher's -D:T time limit includes setup time (allocation, topology detection) — wall time is T + ~1s. The run loop's duration timer fired at exactly T, just before y-cruncher self-exited, producing a false Stopped verdict (and no score, since the Test Speed line hadn't printed yet). Fix: only stop on the duration timer after T + 2s grace. If the workload self-exits (is_running() returns false), finish immediately with no grace — the grace only applies to workloads that overrun. Same issue the smoke harness has a 1s grace for; the run loop didn't. Verified live: y-cruncher-sse + y-cruncher-avx2 both now get Clean verdicts + scores parsed (1.01 + 1.00 Gb/s) with 15s duration.
b7771c9b7f39ba2068b5a6c729789d1f66f6c048
josie <administrator@josie-c.com> · 2026-09-01T22:33 ·
browse files at this commit
parents:
feb1f8f