OXYGENHUB

A waiting WASM job no longer says runners lack its image (closes #317) 56b8dbe66974

runner_lacks checked a job's empty image against each runner's images,
so a WASM job explained its wait as every runner not holding image ''.
The claim never did; the explanation now skips it too.
Author
Andy McCall @andymccall <[email protected]>
via
agent agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key bc2298cfe828
When
· 2026-10-25 19:52 UTC
Signature
◈ sealed by agent:claude@andymccall
Hash
56b8dbe66974e89b34556d40d352cc5eef65efb7e5c727498b62f082b22f60f6
Depends on
234921ea0834 42fa95540468 4d2e44d79b03 501d8b268dd4 a7e6e8e6094c b5668108487f
Depended on by
951aed50c2f5 acb8d109e981
In bookmarks
api-unknown-fields diffusion-pin-bookmark main proposal-list-fast
In tags
v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
5 atoms, 0 tree operations, 2 files

Browse the tree at this change

modified CHANGELOG.md +1 −0
…
3 unchanged lines
4 4
5 5
## Unreleased
6 6
7 +
- **A waiting WASM job no longer says runners lack its image (#317):** a job with no image (the WASM tier) waited saying every runner "does not hold image" with an empty name; the explanation now skips the image check, as the claim always did.
7 8
## 0.9.3 (2026-10-25)
8 9
9 10
- **Runners hand jobs back, and the queue takes them first (#312, oxygenflow#76):** `POST /api/v1/flow/jobs/{id}/release` {reason} lets a draining runner hand back a job it holds as it leaves (a phone off Wi-Fi, a restart, a spot reclaim). The job is queued again **ahead of new work**, whatever its priority; its log says who handed it back and why, and its next attempt's log follows on; it is not a decline. A job whose runner the hub has not heard from for 3 minutes (claims, 30-second heartbeats, logs) is handed back the same way, rather than running on until its time limit and ending as lost. A job handed back more than 5 times ends as an error saying so. Migration 0094 adds `flow_jobs.handed_back_ms` and `handbacks`.
…
314 unchanged lines
modified crates/hub/src/flow.rs +7 −2
…
1492 unchanged lines
1493 1493
    if let Some(w) = wanted_arch.as_deref().filter(|w| !r.arch.is_empty() && *w != r.arch) {
1494 1494
        return Some(format!("is {}; the job needs {w}", r.arch));
1495 1495
    }
1496 +
    // A job with no image (the WASM tier) needs none on the runner, as its claim says.
1496 1497
    let fetches = r.fetches_images && fleet.hub_images.contains(rootfs);
1497 -
    if !fetches && r.image_list().is_some_and(|have| !have.iter().any(|h| h == rootfs)) {
1498 +
    if !rootfs.is_empty() && !fetches && r.image_list().is_some_and(|have| !have.iter().any(|h| h == rootfs)) {
1498 1499
        let why = if r.fetches_images { " (nor does the hub: send it with flow rootfs push)" } else { "" };
1499 1500
        return Some(format!("does not hold image {}{why}", &rootfs[..12.min(rootfs.len())]));
1500 1501
    }
…
1408 unchanged lines
2909 2910
            "no runner can take it: narvi is riscv64; the job needs x86_64"
2910 2911
        );
2911 2912
        let without = Runner { images: "[]".into(), ..runner(6, "new") };
2912 -
        assert_eq!(why(&[without], &[]), "no runner can take it: new does not hold image abababababab");
2913 +
        assert_eq!(why(std::slice::from_ref(&without), &[]), "no runner can take it: new does not hold image abababababab");
2914 +
        // A WASM job has no image: holding none is no reason to wait.
2915 +
        let wasm = fdb::Job { runtime: "wasm".into(), sandbox: r#"{"memory_mib":256}"#.into(), ..job.clone() };
2916 +
        let wasm_runner = Runner { tiers: "microvm,wasm".into(), ..without };
2917 +
        assert_eq!(why_waiting(&wasm, "andy", 1, &[wasm_runner], &archs, &[], now, &Default::default()), "", "it only waits its turn");
2913 2918
        let d = fdb::DeclineRow {
2914 2919
            job_id: 7,
2915 2920
            runner_id: 1,
…
12 unchanged lines