OXYGENHUB

A runner's claim lists the rootfs images it holds (refs andymccall/oxygenhub#73) 8c0a4e3a8a30

ClaimRequest gains images: the BLAKE3 hashes of cas/*.ext4 in the
runner's home, read at each claim so an image imported meanwhile counts.
The hub can then offer a runner only the jobs whose image it has; absent,
as from an older runner, any job may still be offered.
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-10-02 00:41 UTC
Signature
✔ signed by Andy McCall
Hash
8c0a4e3a8a30d6039e152abe1049c7973079c71e059b4da2a39b963d007acf71
Depends on
3007d8f011fa c3db750ed96e e0918b887850 f98a8f7881e0
Depended on by
1abadd4a2fa6 1daca0b6bb8c
In bookmarks
main newer-pipeline push-uses-login
In tags
v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.5.0 v0.5.1 v0.5.2 v0.5.3
Edits
4 atoms, 0 tree operations, 4 files

Browse the tree at this change

modified CHANGELOG.md +2 −0
…
3 unchanged lines
4 4
5 5
## Unreleased
6 6
7 +
- **A runner says which images it holds (oxygenhub#73).** Each claim lists the rootfs images in the runner's `cas/` (read each time, so an image imported while it runs counts), and the hub offers only jobs whose image is among them. plex-1 had failed metalgear's jobs for want of one.
8 +
7 9
- **A job's result is kept until the hub has it (oxygenhub#71).** The runner offered a finished job's result three times over seven seconds and then dropped it, so a hub that was briefly down (Earth's reset) left the job running forever. The result is now kept on disk (`pending/` in the Flow home) and offered again, every 5 s doubling to a minute, for up to two hours, through any server error or lost connection; a runner that restarts sends what it kept. A refusal (the hub ended the job already) is final.
8 10
9 11
- **The Rust build image vendors mimalloc (#20).** Oxygen's new allocator was missing from the image, so oxygen's runs #150 to #153 failed. The image was rebuilt with `images/rust/build.sh`, imported on Earth and pinned in the oxygen, oxygenhub and oxygenflow pipelines. Rebuild it whenever a `Cargo.lock` of those projects changes.
…
1 unchanged lines
modified crates/flow-core/src/api.rs +6 −0
…
21 unchanged lines
22 22
    /// What the runner is, for the hub's Runners page.
23 23
    #[serde(default, skip_serializing_if = "Option::is_none")]
24 24
    pub info: Option<RunnerInfo>,
25 +
    /// The rootfs images the runner holds (BLAKE3 hex, as pipelines pin
26 +
    /// them): the hub offers only jobs whose image is among them
27 +
    /// (oxygenhub#73). Absent: the runner does not say, and any job may be
28 +
    /// offered.
29 +
    #[serde(default, skip_serializing_if = "Option::is_none")]
30 +
    pub images: Option<Vec<String>>,
25 31
}
26 32
27 33
/// A runner's description: shown on the hub's Runners page, and what
…
158 unchanged lines
modified crates/flow/src/home.rs +16 −0
…
113 unchanged lines
114 114
        self.root.join("cas").join(format!("{hash}.ext4"))
115 115
    }
116 116
117 +
    /// The rootfs images this home holds: the hashes of `cas/*.ext4`.
118 +
    pub fn images(&self) -> Vec<String> {
119 +
        let Ok(dir) = std::fs::read_dir(self.root.join("cas")) else {
120 +
            return Vec::new();
121 +
        };
122 +
        let mut out: Vec<String> = dir
123 +
            .filter_map(|e| {
124 +
                let name = e.ok()?.file_name().into_string().ok()?;
125 +
                let hash = name.strip_suffix(".ext4")?;
126 +
                (hash.len() == 64 && hash.chars().all(|c| c.is_ascii_hexdigit())).then(|| hash.to_string())
127 +
            })
128 +
            .collect();
129 +
        out.sort();
130 +
        out
131 +
    }
132 +
117 133
    /// A pinned file, checked against its BLAKE3 before use (again only
118 134
    /// when its size or modification time changed since).
119 135
    pub fn verified(&self, path: &Path, blake3_hex: &str) -> Result<PathBuf> {
…
77 unchanged lines
modified crates/flow/src/runner.rs +2 −0
…
286 unchanged lines
287 287
                    version: env!("OXY_VERSION_LONG").into(),
288 288
                    ..RunnerInfo::here(cfg.labels.clone(), budget.max_jobs as u32, cfg.memory_mib, cfg.cpus)
289 289
                }),
290 +
                // Read each time: an image imported while the runner runs counts.
291 +
                images: Some(home.images()),
290 292
            };
291 293
            match hub.claim(&req) {
292 294
                Ok(Some(claim)) => {
…
410 unchanged lines