From 256fe2c327610aee885bf2c104e27e25c90edd49 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Thu, 12 Nov 2026 08:08:40 +0000 Subject: [PATCH] fix(kvb-org-queue): an idle queue no longer waits on a head the host load alone keeps out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit evaluate() counted a non-hotfix entry that passed every queue check but was held back by the live host load as granted for every entry after it: its slot, the exclusive-kind drain and its CPU/RAM share. With RAM held outside the queue the head never started and nothing behind it did either (2026-11-12 03:50 UTC: 27 entries, no job active, host idle). While no queue job is active, such an entry now reserves nothing and holds nothing back; with jobs active it keeps its reservation, and a load-held hotfix still holds back every entry after it. Replayed on the live queue, the fix grants test-nostack-1035 at once. 🎯 Fix: 93 % 💥 Break: 4 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 89 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 --- skills/kvb-org-queue/scripts/queue.py | 23 +++++++++++++++-------- tests/test_queue.py | 22 +++++++++++++++++++++- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/skills/kvb-org-queue/scripts/queue.py b/skills/kvb-org-queue/scripts/queue.py index cc26af5..ee61430 100755 --- a/skills/kvb-org-queue/scripts/queue.py +++ b/skills/kvb-org-queue/scripts/queue.py @@ -406,7 +406,10 @@ def evaluate(entries: list[dict], free_gb: float, floor: float, stage: str, memo holds it, so a lower entry never takes a higher entry's slot; one blocked by disk or budget reserves its need against the entries after it. A hotfix entry that passes every check but the host load holds back - every entry after it, so lighter jobs cannot keep the load up. An + every entry after it, so lighter jobs cannot keep the load up. While no + queue job is active, any other entry the host load alone keeps out + reserves nothing and holds nothing back: that load lies outside the + queue, so holding the queue frees none of it. An exclusive entry that only running jobs of other kinds keep out holds back every entry after it but light ones, so none overtakes it. A waiting entry whose holder has not polled for CLAIM_SECONDS reserves nothing and @@ -435,13 +438,17 @@ def evaluate(entries: list[dict], free_gb: float, floor: float, stage: str, memo if reason is not None and kind in exclusive: drain = entry["id"] if reason is None: - reason, used, available = resources(need, share, used, available, floor, budget, taken) - if reason is None and entry["hotfix"] and entry["id"] in loaded: - reason, held = loaded[entry["id"]], entry["id"] - elif reason is None: - taken += 1 - counts[kind] += 1 - busy.add(workdir) + reason, after, left = resources(need, share, used, available, floor, budget, taken) + if reason is None and entry["id"] in loaded and (entry["hotfix"] or not active): + reason = loaded[entry["id"]] + if entry["hotfix"]: + held = entry["id"] + else: + used, available = after, left + if reason is None: + taken += 1 + counts[kind] += 1 + busy.add(workdir) verdicts[entry["id"]] = reason for entry in waiting: if silent(entry, now): diff --git a/tests/test_queue.py b/tests/test_queue.py index 63f421c..79d005e 100644 --- a/tests/test_queue.py +++ b/tests/test_queue.py @@ -296,9 +296,29 @@ class TestEvaluate(unittest.TestCase): self.decide([hotfix, light], loaded={"j1": load}), {"j1": load, "j2": "waits behind hotfix j1, which the host load holds back"}, ) - self.assertEqual(self.decide([job(1), light], loaded={"j1": load}), {"j1": None, "j2": None}) + self.assertEqual(self.decide([job(1), light], loaded={"j1": load}), {"j1": load, "j2": None}) self.assertEqual(self.decide([hotfix, light]), {"j1": None, "j2": None}) + def test_with_no_job_active_a_head_the_host_load_alone_keeps_out_reserves_nothing_and_with_one_active_it_does(self): + load = "RAM 55 % used + 41 % still to come reach the limit 90 %" + head, twin = job(1, kind="quality-high"), job(2, kind="quality-high", task="work/b") + gate, light = job(3, task="work/b"), job(4, kind="test-nostack", task="work/c") + shares = {"j1": {"ram": 41, "cpu": 62}, "j2": {"ram": 41, "cpu": 62}, "j3": {"ram": 20, "cpu": 40}, "j4": {"ram": 6, "cpu": 40}} + rules = {"slot_limits": {"quality-high": 1, "test": 5, "test-nostack": 6}, "exclusive": {"quality-high", "test"}, "light": {"test-nostack"}} + loaded = {"j1": load, "j2": load} + self.assertEqual( + self.decide([head, twin, gate, light], loaded=loaded, footprints=shares, **rules), + {"j1": load, "j2": load, "j3": None, "j4": None}, + ) + running = job(5, kind="test-nostack", task="work/c", status="active") + self.assertEqual( + self.decide([running, head, twin, gate, light], loaded=loaded, footprints=shares | {"j5": {"ram": 6, "cpu": 11}}, **rules), + { + "j1": None, "j2": "all 1 quality-high slots taken", "j3": "test runs only beside test-nostack, but quality-high runs", + "j4": "CPU 73 % in use or reserved + 40 % > budget 100 %; reserved", + }, + ) + class TestHistory(unittest.TestCase): @classmethod