OxygenFlow claims hand a runner only jobs whose sandbox fits the room it reports; oxygenflow pin moved to the budgeted runner 76bc5a058351
refs andymccall/oxygenflow#6
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-09-28 20:33 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
76bc5a0583516542e19b76b914ddcb29f9da1078a9639f223dc46455ff3482ea- Depends on
- 25091e5c3bba 380996db84e5 42fa95540468 fd3db644df22
- Depended on by
- 55673c650986 44a3d2ebaddd 92a797b20912 4d2e44d79b03 efd29a0033e8 b0612e7dce30 c4c87f481d05 74be97962ed8 5e9dbc57bc31 941abdd6bb4c
- In bookmarks
- api-unknown-fields cancel-landed-runs clear-waiting close-proposal-140 diffusion-pin-bookmark draft-ready image-check job-waits main oh-comment-body-file proposal-list-fast run-one-job wiki-says-so
- In tags
- v0.1.0 v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.4.1 v0.4.2 v0.5.0 v0.5.1 v0.5.2 v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
- Edits
- 7 atoms, 0 tree operations, 3 files
Browse the tree at this change
modified crates/hub/src/db/flow.rs +22 −1
| … | 176 unchanged lines |
||
| 177 | 177 | } |
|
| 178 | 178 | } |
|
| 179 | 179 | ||
| 180 | + | /// What a runner can take on: free memory (MiB) and vCPUs, `None` for no limit. |
|
| 181 | + | #[derive(Clone, Copy, Debug, Default)] |
|
| 182 | + | pub struct Room { |
|
| 183 | + | pub memory_mib: Option<u32>, |
|
| 184 | + | pub cpus: Option<u32>, |
|
| 185 | + | } |
|
| 186 | + | ||
| 187 | + | impl Room { |
|
| 188 | + | /// Whether a job's sandbox (its stored JSON) fits. |
|
| 189 | + | pub fn fits(&self, sandbox_json: &str) -> bool { |
|
| 190 | + | let v: serde_json::Value = serde_json::from_str(sandbox_json).unwrap_or_default(); |
|
| 191 | + | let need = |k: &str| v[k].as_u64().unwrap_or(0); |
|
| 192 | + | self.memory_mib.is_none_or(|m| need("memory_mib") <= m as u64) && self.cpus.is_none_or(|c| need("cpus") <= c as u64) |
|
| 193 | + | } |
|
| 194 | + | } |
|
| 195 | + | ||
| 180 | 196 | pub async fn claim(pool: &Pool, runtimes: &[String], runner: &str, token_hash: &str) -> sqlx::Result<Option<Job>> { |
|
| 197 | + | claim_within(pool, runtimes, runner, token_hash, Room::default()).await |
|
| 198 | + | } |
|
| 199 | + | ||
| 200 | + | /// The oldest runnable job that fits in `room`. |
|
| 201 | + | pub async fn claim_within(pool: &Pool, runtimes: &[String], runner: &str, token_hash: &str, room: Room) -> sqlx::Result<Option<Job>> { |
|
| 181 | 202 | let mut tx = pool.begin().await?; |
|
| 182 | 203 | let queued: Vec<Job> = sqlx::query_as( |
|
| 183 | 204 | "SELECT j.* FROM flow_jobs j JOIN flow_runs r ON r.id = j.run_id WHERE j.status = 'queued' AND r.status IN ('queued', 'running') ORDER BY j.run_id, j.depth, j.id LIMIT 200", |
|
| … | 1 unchanged lines |
||
| 185 | 206 | .fetch_all(&mut *tx) |
|
| 186 | 207 | .await?; |
|
| 187 | 208 | for j in queued { |
|
| 188 | - | if !runtimes.iter().any(|r| r == &j.runtime) { |
|
| 209 | + | if !runtimes.iter().any(|r| r == &j.runtime) || !room.fits(&j.sandbox) { |
|
| 189 | 210 | continue; |
|
| 190 | 211 | } |
|
| 191 | 212 | let deps = j.depends_on(); |
|
| … | 142 unchanged lines |
modified crates/hub/src/flow.rs +2 −1
| … | 128 unchanged lines |
||
| 129 | 129 | runner_auth(&app, &headers)?; |
|
| 130 | 130 | let runner: String = req.runner.chars().filter(|c| !c.is_control()).take(64).collect(); |
|
| 131 | 131 | let secret = format!("{JOB_TOKEN_PREFIX}{}", crate::auth::random_hex(32)); |
|
| 132 | - | let Some(job) = fdb::claim(&app.db, &req.runtimes, &runner, &crate::auth::token_hash(&secret)).await? else { |
|
| 132 | + | let room = fdb::Room { memory_mib: req.free_memory_mib, cpus: req.free_cpus }; |
|
| 133 | + | let Some(job) = fdb::claim_within(&app.db, &req.runtimes, &runner, &crate::auth::token_hash(&secret), room).await? else { |
|
| 133 | 134 | return Ok(StatusCode::NO_CONTENT.into_response()); |
|
| 134 | 135 | }; |
|
| 135 | 136 | let run = fdb::run_by_id(&app.db, job.run_id).await?.ok_or_else(|| ApiError(StatusCode::NOT_FOUND, "no such run".into()))?; |
|
| … | 206 unchanged lines |
replaced deps/oxygenflow +0 −0
No line changes.