fix(issue-50): a waiter whose own attempt is already running answers clear, not superseded
The registry wait judged its record by one rule: not this attempt's pending record in this session -> superseded (redispatched, moved or closed by someone else). That also caught the record's own `started` landing before the prompt went out - the Issue #14 registry ack winning over an unreachable broker, as test_issue30_stall_watch_stop acknowledges at registration - and the wrapper left on exit 5 instead of its broker_unavailable path (rc 3). A record that is running under this attempt's token in this session is this attempt's own progress: the waiter answers `clear` at once, writes no admission and starts no hold. Another token, another session or a terminal status stay `superseded` (review 55219be1 R1 unchanged). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
7844ae2c5f
commit
d50610c1ab
2 files changed
+42
-3
No files matched your search
@@ -334,6 +334,16 @@ def _own(rec: Optional[dict], token: Optional[str], session: str) -> bool:
|
||||
and _bare(str(rec.get("agent_session") or "")) == _bare(session)
|
||||
|
||||
|
||||
def _started_own(rec: Optional[dict], token: Optional[str], session: str) -> bool:
|
||||
"""This attempt's record, in this session, already `running`: its own
|
||||
`started` landed before the prompt went out (the registry ack of Issue
|
||||
#14 wins over an unreachable broker; a test that acknowledges at
|
||||
registration). Nothing was redispatched, moved or closed by someone else
|
||||
- there is just nothing left to wait for."""
|
||||
return bool(rec) and bool(token) and rec.get("status") == "running" and rec.get("dispatch_token") == token \
|
||||
and _bare(str(rec.get("agent_session") or "")) == _bare(session)
|
||||
|
||||
|
||||
def _merge_own(registry_dir: str, job_id: str, token: Optional[str], session: str, source: str, fields: dict,
|
||||
guard=None) -> bool:
|
||||
"""One merge through the engine, committed only while the record re-read
|
||||
@@ -434,7 +444,9 @@ def wait_open_jobs(a: argparse.Namespace) -> str:
|
||||
and its admission is committed (_admit); `busy <ids>` when the bound runs
|
||||
out first; `superseded` once the record is not this attempt's pending one
|
||||
in this session any more (redispatched, moved, closed by someone else) -
|
||||
nothing is written or closed then, the record is its owner's. The bound
|
||||
nothing is written or closed then, the record is its owner's; `clear`
|
||||
at once, with no admission, when it is this attempt's and already
|
||||
running (_started_own). The bound
|
||||
is min(--wait-s, this job's remaining wall clock). The identity is pinned
|
||||
at the start: --dispatch-token (the token the wrapper read for this
|
||||
attempt), else the record's token read once. Every DISPATCH_SIGNAL_EVERY s
|
||||
@@ -450,8 +462,9 @@ def wait_open_jobs(a: argparse.Namespace) -> str:
|
||||
last_signal = None
|
||||
while True:
|
||||
now = _now()
|
||||
if not _own(_load_record(a.registry_dir, a.job), token, a.agent_session):
|
||||
return "superseded"
|
||||
rec = _load_record(a.registry_dir, a.job)
|
||||
if not _own(rec, token, a.agent_session):
|
||||
return "clear" if _started_own(rec, token, a.agent_session) else "superseded"
|
||||
live, stale = open_jobs(a.registry_dir, a.agent_session, a.job, now)
|
||||
for other, reason in stale:
|
||||
if other not in reported:
|
||||
|
||||
@@ -422,6 +422,32 @@ def test_u3g_a_waiter_whose_attempt_changed_in_place_leaves_the_admission_to_the
|
||||
proc.communicate()
|
||||
|
||||
|
||||
def test_u3i_an_attempt_already_running_under_its_own_token_is_clear_not_superseded(tmp_path):
|
||||
# the Issue #14 registry ack: the agent's `started` for this attempt lands
|
||||
# before the prompt goes out (test_issue30_stall_watch_stop). Nothing to
|
||||
# wait for, nothing foreign: `clear` at once, no admission written
|
||||
a = _register(tmp_path, status="running")
|
||||
_event(tmp_path, a, time.time())
|
||||
me = _register(tmp_path)
|
||||
tok = _rec(tmp_path, me)["dispatch_token"]
|
||||
_edit(tmp_path, me, status="running")
|
||||
before = _rec(tmp_path, me)
|
||||
t0 = time.time()
|
||||
assert decide.wait_open_jobs(_ns(tmp_path, me, wait_s=30)) == "clear"
|
||||
assert time.time() - t0 < 5 and _rec(tmp_path, me) == before, "no wait, no write"
|
||||
res = _cli(tmp_path, "wait-open-jobs", "--job", me, "--agent-session", "herdr:s", "--registry-dir", str(tmp_path),
|
||||
"--wait-s", "30", "--timeout", "3600", "--dispatch-token", tok, "--hold")
|
||||
assert res.stdout.strip() == "clear" and not _hold_pids(me), res.stderr
|
||||
# running under another attempt's token, or in another session, is still someone else's
|
||||
assert decide.wait_open_jobs(_ns(tmp_path, me, session="herdr:t")) == "superseded"
|
||||
_edit(tmp_path, me, dispatch_token="another-attempt")
|
||||
res = _cli(tmp_path, "wait-open-jobs", "--job", me, "--agent-session", "herdr:s", "--registry-dir", str(tmp_path),
|
||||
"--wait-s", "30", "--timeout", "3600", "--dispatch-token", tok)
|
||||
assert res.stdout.strip() == "superseded", res.stderr
|
||||
_edit(tmp_path, me, dispatch_token=tok, status="completed")
|
||||
assert decide.wait_open_jobs(_ns(tmp_path, me)) == "superseded"
|
||||
|
||||
|
||||
def test_u3h_the_pinned_token_is_the_wrappers_not_the_records(tmp_path):
|
||||
# the record's token changed before the waiter started (a redispatch raced
|
||||
# the wrapper): the waiter given the old token is superseded at once
|
||||
|
||||
Reference in new issue
Block a user