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
@@ -268,9 +268,14 @@ def open_jobs(registry_dir: str, session: str, job_id: str, now: Optional[int] =
|
||||
within DISPATCH_SIGNAL_WINDOW, or - never signalled - created within
|
||||
its own ack_timeout_sec: a producer that registers and injects
|
||||
without a waiter (create --onboard) waits that long for `started`
|
||||
(review 55219be1 R2). A live pending record whose admission names
|
||||
its current dispatch token is reported as `admitted`: it holds the
|
||||
session as a running job does, whatever its place in the FIFO.
|
||||
(review 55219be1 R2). Such a never-signalled record is reported as
|
||||
`injected` and a live pending record whose admission names its
|
||||
current dispatch token as `admitted`: both hold the session as a
|
||||
running job does, whatever their place in the FIFO - the prompt is
|
||||
already in the pane (review 5a09a13f F-A: in the FIFO an `injected`
|
||||
record of the same second lost to a lower job id). A waiter's own
|
||||
record is unsignalled only until its first scan (wait_open_jobs
|
||||
signals first).
|
||||
stale: [(job_id, reason)] - non-terminal records nothing has touched for
|
||||
longer than that: a wrapper killed before or while dispatching, a
|
||||
zombie's unclosed record, an onboarding never acknowledged. They
|
||||
@@ -307,17 +312,21 @@ def open_jobs(registry_dir: str, session: str, job_id: str, now: Optional[int] =
|
||||
seen, window = _parse_ts(rec.get("created_at")), _int_field(rec, "ack_timeout_sec", DEFAULT_ACK_TIMEOUT_SEC)
|
||||
why = f"no dispatch signal and no started within {window}s"
|
||||
if seen is not None and now - seen <= window:
|
||||
admitted = bool(rec.get(ADMITTED_TOKEN_FIELD)) and \
|
||||
rec.get(ADMITTED_TOKEN_FIELD) == rec.get("dispatch_token")
|
||||
live.append((str(rec.get("created_at") or ""), other, "admitted" if admitted else status))
|
||||
if not rec.get(DISPATCH_SIGNAL_FIELD):
|
||||
kind = "injected"
|
||||
elif rec.get(ADMITTED_TOKEN_FIELD) and rec.get(ADMITTED_TOKEN_FIELD) == rec.get("dispatch_token"):
|
||||
kind = "admitted"
|
||||
else:
|
||||
kind = status
|
||||
live.append((str(rec.get("created_at") or ""), other, kind))
|
||||
else:
|
||||
stale.append((other, f"pending, {why}"))
|
||||
return live, stale
|
||||
|
||||
|
||||
def _first_in_line(live, mine) -> bool:
|
||||
"""No live running or admitted job, and `mine` earliest among the live
|
||||
pending ones (FIFO by (created_at, job_id))."""
|
||||
"""No live running, admitted or injected job, and `mine` earliest among
|
||||
the live pending ones (FIFO by (created_at, job_id))."""
|
||||
if any(st != "pending" for _c, _j, st in live):
|
||||
return False
|
||||
return min([(c, j) for c, j, _st in live] + [mine]) == mine
|
||||
@@ -449,9 +458,12 @@ def wait_open_jobs(a: argparse.Namespace) -> str:
|
||||
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
|
||||
the waiter signals on its own record so later waiters see it alive; a
|
||||
waiter whose wrapper is gone stops (OwnerGone) and its signal goes stale.
|
||||
attempt), else the record's token read once. The waiter signals on its own
|
||||
record before its first scan - an unsignalled live record is one a
|
||||
producer injected without a waiter (open_jobs: `injected`), and this one
|
||||
is not - and every DISPATCH_SIGNAL_EVERY s after, so other waiters see it
|
||||
alive; a waiter whose wrapper is gone stops (OwnerGone) and its signal
|
||||
goes stale.
|
||||
With --hold the admission is kept alive after the answer (_start_hold)."""
|
||||
me = _load_record(a.registry_dir, a.job) or {}
|
||||
token = getattr(a, "dispatch_token", None) or me.get("dispatch_token")
|
||||
@@ -465,6 +477,9 @@ def wait_open_jobs(a: argparse.Namespace) -> str:
|
||||
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"
|
||||
if last_signal is None or now - last_signal >= DISPATCH_SIGNAL_EVERY:
|
||||
_signal_dispatch_wait(a.registry_dir, a.job, token, a.agent_session)
|
||||
last_signal = now
|
||||
live, stale = open_jobs(a.registry_dir, a.agent_session, a.job, now)
|
||||
for other, reason in stale:
|
||||
if other not in reported:
|
||||
@@ -476,9 +491,6 @@ def wait_open_jobs(a: argparse.Namespace) -> str:
|
||||
return "clear"
|
||||
if now >= deadline:
|
||||
return "busy " + " ".join(j for _c, j, _st in live)
|
||||
if last_signal is None or now - last_signal >= DISPATCH_SIGNAL_EVERY:
|
||||
_signal_dispatch_wait(a.registry_dir, a.job, token, a.agent_session)
|
||||
last_signal = now
|
||||
_sleep(a, 1)
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user