Merge master into unify/skills-20261111

This commit is contained in:
Kevin Veen-Birkenbach committed 2026-11-12 08:23:22 +00:00
commit 4df1c2e48c
2 files changed
+36 -9

No files matched your search

+15 -8
View File
@@ -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 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. 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 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 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 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 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: if reason is not None and kind in exclusive:
drain = entry["id"] drain = entry["id"]
if reason is None: if reason is None:
reason, used, available = resources(need, share, used, available, floor, budget, taken) reason, after, left = resources(need, share, used, available, floor, budget, taken)
if reason is None and entry["hotfix"] and entry["id"] in loaded: if reason is None and entry["id"] in loaded and (entry["hotfix"] or not active):
reason, held = loaded[entry["id"]], entry["id"] reason = loaded[entry["id"]]
elif reason is None: if entry["hotfix"]:
taken += 1 held = entry["id"]
counts[kind] += 1 else:
busy.add(workdir) used, available = after, left
if reason is None:
taken += 1
counts[kind] += 1
busy.add(workdir)
verdicts[entry["id"]] = reason verdicts[entry["id"]] = reason
for entry in waiting: for entry in waiting:
if silent(entry, now): if silent(entry, now):
+21 -1
View File
@@ -296,9 +296,29 @@ class TestEvaluate(unittest.TestCase):
self.decide([hotfix, light], loaded={"j1": load}), self.decide([hotfix, light], loaded={"j1": load}),
{"j1": load, "j2": "waits behind hotfix j1, which the host load holds back"}, {"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}) 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): class TestHistory(unittest.TestCase):
@classmethod @classmethod