project-nomad/admin/app/utils/benchmark_status.ts
Matt Moyles 2aa55a43ec
fix(benchmark): report run status from the queue so the double-run guard works
Benchmarks execute as a BullMQ job in the queue worker, but
/api/benchmark/status and the 409 double-run guards read the web
process's in-memory BenchmarkService, which never sees the run. Status
always read idle, and a second POST /api/benchmark/run was accepted
mid-flight and queued behind the first.

Status now comes from the queue, the one place both processes can see.
The worker publishes each stage on its job via job.updateProgress(),
and resolveBenchmarkStatus() reports the active job's stage, "starting"
for a job still queued, or the local status for a sync=true run. The
status endpoint and all three run guards read it. If the queue can't be
read they fall back to the local status, as before.

Closes #1326
2026-10-09 13:54:37 -04:00

39 lines
1.8 KiB
TypeScript

import type { BenchmarkStatus } from '../../types/benchmark.js'
/** A benchmark job still in the queue, as RunBenchmarkJob.getInFlight() reports it. */
export interface InFlightBenchmark {
state: 'active' | 'waiting' | 'delayed'
benchmarkId: string | null
/** The job's BullMQ progress; the worker writes `{ status }` into it at every stage. */
progress: unknown
}
const NOT_RUNNING: ReadonlySet<BenchmarkStatus> = new Set(['idle', 'completed', 'error'])
/**
* Decide the current benchmark status from the queue.
*
* Benchmarks run in the queue worker, so the web process's BenchmarkService
* never sees them and its in-memory status always reads idle (#1326). The queue
* is the one place both processes can see: a job that is active or still
* waiting means a run is in flight, and the worker reports its stage through
* the job's progress. `local` is the web process's own status, which is only
* ever non-idle for a `sync=true` run executing in this process.
*/
export function resolveBenchmarkStatus(
inFlight: InFlightBenchmark[],
local: { status: BenchmarkStatus; benchmarkId: string | null }
): { status: BenchmarkStatus; benchmarkId: string | null } {
const active = inFlight.find((j) => j.state === 'active')
if (active) {
const reported = (active.progress as { status?: BenchmarkStatus } | null)?.status
// Before the worker's first stage update, or in the moment between it reporting
// `completed` and the job leaving the active set, the run is still in flight
const status = reported && !NOT_RUNNING.has(reported) ? reported : 'starting'
return { status, benchmarkId: active.benchmarkId }
}
const queued = inFlight.find((j) => j.state === 'waiting' || j.state === 'delayed')
if (queued) return { status: 'starting', benchmarkId: queued.benchmarkId }
return local
}