fix(issue-50): the registry wait admits one job per session and holds it (review aa1bff92 F1, F2)
F1: `clear` is now an admission persisted on the released record - a merge through the engine whose new `guard` re-reads the session's open jobs under the registry lock, so two waiters cannot both be admitted. It is scoped to the attempt by the dispatch token (a redispatch voids it) and reported as `admitted` to every other waiter, which holds the session as a running job does, whatever its FIFO place: a lower id in the same second or a redispatched older loop job now waits. F2: a pending record's liveness window is the dispatcher's signal cadence times three misses (10 s x 3 = 30 s), no longer ack_timeout_sec, which the agent's budget takes down to 1 s. After `clear` a detached hold renews the admitted record's signal until the record leaves pending, its attempt changes, the wrapper is gone, or the wall clock ends. Tests: u1 boundaries, u1d/u2e/u2f/u2g/u3d/u3e for the admission, u4c/u4d for the hold, s1 pins --hold; real herdr: C3/C7/C8 adjusted, C9 is C8's live-owner counterpart with short ack windows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
9e78ff3ba3
commit
9cbe08e8de
5 files changed
+363
-41
No files matched your search
@@ -329,7 +329,8 @@ def test_c3_a_dead_running_record_does_not_block_the_session(box):
|
||||
assert rc == 0, out + err
|
||||
assert f"stale open job {a} ignored (running, no event for 2s)" in err, err
|
||||
assert box.record(b)["status"] == "completed"
|
||||
assert "dispatch_wait_at" not in box.record(b), "nothing to wait for: no signal written"
|
||||
rec = box.record(b) # nothing to wait for: the only signal is the admission's own
|
||||
assert rec["dispatch_admitted_token"] == rec["dispatch_token"] and rec["dispatch_wait_at"] == rec["dispatch_admitted_at"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -434,6 +435,9 @@ def test_c7_two_waiters_run_one_after_the_other_and_neither_is_cancelled(box):
|
||||
assert "cancelled" not in out_b + out_c and "stale open job" not in err_b + err_c
|
||||
assert _ts(box.events(b)[0]) >= _ts(box.events(a)[-1])
|
||||
assert _ts(box.events(c)[0]) >= _ts(box.events(b)[-1]), "C started only after B ended"
|
||||
# the admission (review aa1bff92 F1): B's before its own `started`, C's only once B ended
|
||||
assert box.record(b)["dispatch_admitted_at"] <= _ts(box.events(b)[0])
|
||||
assert box.record(c)["dispatch_admitted_at"] >= _ts(box.events(b)[-1])
|
||||
pane = box.pane_text()
|
||||
assert pane.index(f'Your job_id is "{b}"') < pane.index(f'Your job_id is "{c}"'), "injected in FIFO order"
|
||||
|
||||
@@ -456,16 +460,49 @@ def test_c8_a_dead_waiter_is_skipped_once_its_signal_is_stale(box):
|
||||
c = box.wait_new_job(known | {b})
|
||||
assert box.record(b)["created_at"] < box.record(c)["created_at"]
|
||||
_after(2, box.publish, a, "completed", "prior done")
|
||||
_after(14, box.publish, c, "started") # after B's 5 s ack window has long passed
|
||||
_after(16, box.publish, c, "completed", "c done")
|
||||
_after(40, box.publish, c, "started") # after B's signal window (30 s) has passed
|
||||
_after(42, box.publish, c, "completed", "c done")
|
||||
rc, out, err = box.finish(proc_c)
|
||||
assert rc == 0, out + err
|
||||
assert f"stale open job {b} ignored (pending, no dispatch signal for 5s)" in err, err
|
||||
assert f"stale open job {b} ignored (pending, no dispatch signal for 30s)" in err, err
|
||||
assert box.record(c)["status"] == "completed", (box.record(c), box.events(c), out, err)
|
||||
assert box.record(b)["status"] == "pending", "the dead waiter's record is left as it is"
|
||||
assert _ts(box.events(c)[0]) >= _ts(box.events(a)[-1])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# C9 - C8's live counterpart (review aa1bff92 F2): waiters with short ack
|
||||
# windows, held across many of them, are never stale and never co-admitted
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_c9_live_waiters_with_short_ack_windows_are_never_stale_nor_co_admitted(box):
|
||||
a = box.register()
|
||||
box.publish(a, "started")
|
||||
known = box.job_ids()
|
||||
proc_b = box.submit(prompt="work B", ack="5", MAM_DISPATCH_WAIT_S="60")
|
||||
b = box.wait_new_job(known)
|
||||
time.sleep(1.2)
|
||||
proc_c = box.submit(prompt="work C", ack="5", MAM_DISPATCH_WAIT_S="60")
|
||||
c = box.wait_new_job(known | {b})
|
||||
box.wait_field(c, "dispatch_wait_at")
|
||||
box.wait_field(b, "dispatch_wait_at")
|
||||
_after(12, box.publish, a, "completed", "prior done") # both waited through two ack windows
|
||||
_after(16, box.publish, b, "started") # B is injected at ~13 s
|
||||
_after(19, box.publish, b, "completed", "b done")
|
||||
_after(23, box.publish, c, "started") # C at ~20 s
|
||||
_after(25, box.publish, c, "completed", "c done")
|
||||
rc_b, out_b, err_b = box.finish(proc_b)
|
||||
rc_c, out_c, err_c = box.finish(proc_c)
|
||||
assert (rc_b, rc_c) == (0, 0), out_b + err_b + out_c + err_c
|
||||
assert "stale open job" not in err_b + err_c, err_b + err_c
|
||||
assert "cancelled" not in out_b + out_c
|
||||
assert box.record(b)["status"] == box.record(c)["status"] == "completed"
|
||||
assert box.record(c)["dispatch_admitted_at"] >= _ts(box.events(b)[-1]), "C admitted only once B ended"
|
||||
assert _ts(box.events(c)[0]) >= _ts(box.events(b)[-1])
|
||||
pane = box.pane_text()
|
||||
assert pane.index(f'Your job_id is "{b}"') < pane.index(f'Your job_id is "{c}"'), "injected in FIFO order"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# A - three `create --onboard` at once in one server
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user