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)
|
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,
|
def _merge_own(registry_dir: str, job_id: str, token: Optional[str], session: str, source: str, fields: dict,
|
||||||
guard=None) -> bool:
|
guard=None) -> bool:
|
||||||
"""One merge through the engine, committed only while the record re-read
|
"""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
|
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
|
out first; `superseded` once the record is not this attempt's pending one
|
||||||
in this session any more (redispatched, moved, closed by someone else) -
|
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
|
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
|
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
|
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
|
last_signal = None
|
||||||
while True:
|
while True:
|
||||||
now = _now()
|
now = _now()
|
||||||
if not _own(_load_record(a.registry_dir, a.job), token, a.agent_session):
|
rec = _load_record(a.registry_dir, a.job)
|
||||||
return "superseded"
|
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)
|
live, stale = open_jobs(a.registry_dir, a.agent_session, a.job, now)
|
||||||
for other, reason in stale:
|
for other, reason in stale:
|
||||||
if other not in reported:
|
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()
|
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):
|
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 record's token changed before the waiter started (a redispatch raced
|
||||||
# the wrapper): the waiter given the old token is superseded at once
|
# the wrapper): the waiter given the old token is superseded at once
|
||||||
|
|||||||
Reference in new issue
Block a user