fix(issue-50): an injected record holds its session whatever its FIFO place; the ambient pane claim locks the caller's server (reviews 5a09a13f F-A, e8e471ff R1)
open_jobs reports a live pending record nobody signals on as `injected`: its producer registered and injected it without a waiter (create --onboard, a job sent by hand), so it holds the session like a running or admitted job, and a waiter of the same second with a lower job id, or one registered a moment earlier and still in its preflight, no longer passes it in the FIFO. The waiter signals on its own record before its first scan, so its record is never mistaken for one. lib_py.pane_lock resolves the shim's ambient target (no -L, HERDR_SESSION_NAME empty or `default`) to the server of the pane the caller runs in - HERDR_SESSION, else HERDR_SOCKET_PATH's layout, as lib.sh _herdr_caller_server reads them - so an ambient and a named call into one server claim under one lock; outside a pane it stays the default server's. Tests: u2h (the tie, the later-registered onboarding, the signalled control), u1/u4 labels, c10 forces the same-second registration, p1b, s2 (real herdr: held lock stops both call forms; mixed concurrent calls take distinct panes). Two FIFO fixtures now carry a waiter's signal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
d50610c1ab
commit
6c99fedf40
7 files changed
+168
-22
No files matched your search
@@ -115,6 +115,8 @@ def test_u1_open_jobs_table(tmp_path, status, age, signal, ack, want):
|
||||
got = {j for _c, j, _s in live}, {j for j, _r in stale}
|
||||
if want == "live":
|
||||
assert got == ({other}, set())
|
||||
if status == "pending": # never signalled: injected by its producer, a holder; signalled: a waiter in the FIFO
|
||||
assert live[0][2] == ("injected" if signal is None else "pending")
|
||||
elif want == "stale":
|
||||
assert got == (set(), {other}), got
|
||||
assert stale[0][1].startswith(status + ", no ")
|
||||
@@ -169,8 +171,8 @@ def test_u2_clear_only_when_no_live_running_and_this_job_is_the_earliest_pending
|
||||
# A ends: clear
|
||||
_edit(tmp_path, a, status="completed")
|
||||
assert decide.wait_open_jobs(_ns(tmp_path, me)) == "clear"
|
||||
# an earlier live pending waiter B: this job waits behind it (busy at the bound)
|
||||
b = _register(tmp_path, created_at=_iso(time.time() - 30))
|
||||
# an earlier live pending waiter B (signalled: a waiter is on it): this job waits behind it (busy at the bound)
|
||||
b = _register(tmp_path, created_at=_iso(time.time() - 30), dispatch_wait_at=_iso(time.time()))
|
||||
assert decide.wait_open_jobs(_ns(tmp_path, me)) == f"busy {b}"
|
||||
# a later one does not hold this job up
|
||||
_edit(tmp_path, b, created_at=_iso(time.time() + 30))
|
||||
@@ -180,11 +182,32 @@ def test_u2_clear_only_when_no_live_running_and_this_job_is_the_earliest_pending
|
||||
def test_u2b_a_tie_on_created_at_is_broken_by_job_id(tmp_path):
|
||||
same = _iso(time.time())
|
||||
_register_as(tmp_path, "aaaa0001", created_at=same)
|
||||
_register_as(tmp_path, "aaaa0002", created_at=same)
|
||||
_register_as(tmp_path, "aaaa0002", created_at=same, dispatch_wait_at=same) # a live waiter
|
||||
assert decide.wait_open_jobs(_ns(tmp_path, "aaaa0001")) == "clear"
|
||||
assert decide.wait_open_jobs(_ns(tmp_path, "aaaa0002")) == "busy aaaa0001"
|
||||
|
||||
|
||||
# review 5a09a13f F-A: a live pending record nobody signals on is one its
|
||||
# producer injected without a waiter (create --onboard, a job sent by hand);
|
||||
# the prompt is in the pane, so it holds the session - never a FIFO peer that
|
||||
# a lower job id in the same second, or an earlier second, could pass
|
||||
|
||||
@pytest.mark.parametrize("onboarding,waiter", [("ffffffff", "00000001"), ("00000001", "ffffffff")])
|
||||
def test_u2h_an_injected_record_holds_the_session_whatever_its_place_in_the_fifo(tmp_path, onboarding, waiter):
|
||||
same = _iso(time.time())
|
||||
_register_as(tmp_path, onboarding, created_at=same) # unsignalled, ack 180: live
|
||||
_register_as(tmp_path, waiter, created_at=same)
|
||||
assert decide.wait_open_jobs(_ns(tmp_path, waiter)) == f"busy {onboarding}"
|
||||
assert "dispatch_admitted_token" not in _rec(tmp_path, waiter) and "dispatch_wait_at" in _rec(tmp_path, waiter)
|
||||
assert "dispatch_wait_at" not in _rec(tmp_path, onboarding)
|
||||
# registered after the waiter (which was still in its preflight): the same
|
||||
_edit(tmp_path, onboarding, created_at=_iso(_at(same) + 1))
|
||||
assert decide.wait_open_jobs(_ns(tmp_path, waiter)) == f"busy {onboarding}"
|
||||
# control: the same record with a waiter's signal on it is a FIFO peer
|
||||
_edit(tmp_path, onboarding, created_at=same, dispatch_wait_at=same)
|
||||
assert decide.wait_open_jobs(_ns(tmp_path, waiter)) == ("clear" if waiter < onboarding else f"busy {onboarding}")
|
||||
|
||||
|
||||
def test_u2c_a_dead_waiter_is_ignored_and_reported(tmp_path, capsys):
|
||||
b = _register(tmp_path, ack=5, created_at=_iso(time.time() - 60), dispatch_wait_at=_iso(time.time() - WINDOW - 1))
|
||||
me = _register(tmp_path)
|
||||
@@ -473,11 +496,13 @@ def test_u4_open_jobs_prints_live_and_stale(tmp_path):
|
||||
a = _register(tmp_path, status="running")
|
||||
_event(tmp_path, a, time.time())
|
||||
b = _register(tmp_path, created_at=_iso(time.time() - 1000))
|
||||
c = _register(tmp_path) # within its ack budget, no waiter on it
|
||||
me = _register(tmp_path)
|
||||
res = _cli(tmp_path, "open-jobs", "--job", me, "--agent-session", "herdr:s", "--registry-dir", str(tmp_path))
|
||||
assert res.returncode == 0, res.stderr
|
||||
assert f"{a} running" in res.stdout
|
||||
assert f"{b} stale pending, no dispatch signal and no started within 180s" in res.stdout
|
||||
assert f"{c} injected" in res.stdout
|
||||
|
||||
|
||||
def _hold_pids(jid):
|
||||
|
||||
Reference in new issue
Block a user