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
@@ -19,6 +19,7 @@ so the herdr idle wait stays and the registry wait runs before it.
|
||||
from __future__ import annotations
|
||||
|
||||
import calendar
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -522,15 +523,29 @@ def _onboarding(box: Box, ack: int) -> str:
|
||||
|
||||
|
||||
def test_c10_a_pending_onboarding_within_its_ack_budget_blocks_the_session_past_the_signal_window(box):
|
||||
# review 5a09a13f F-A: the onboarding and the first job are registered in
|
||||
# the same second (create registers, then injects; the job's wrapper
|
||||
# registers a moment later), where the FIFO's (created_at, job_id) once
|
||||
# let a lower id pass the injected onboarding; the second job comes a
|
||||
# different second
|
||||
while time.time() % 1 > 0.05:
|
||||
time.sleep(0.005)
|
||||
t0 = time.time()
|
||||
a = _onboarding(box, 180)
|
||||
a = box.register(ack_timeout_sec=180)
|
||||
known = box.job_ids()
|
||||
for label in ("before", "after"):
|
||||
if label == "after":
|
||||
time.sleep(max(0.0, t0 + 31.5 - time.time())) # A is now older than the signal window
|
||||
proc = box.submit(prompt="work " + label, MAM_DISPATCH_WAIT_S="3")
|
||||
if label == "before":
|
||||
prompt = box.herdr("agent", "prompt", SESSION, "Onboarding " + a)
|
||||
assert prompt.returncode == 0, prompt.stdout + prompt.stderr
|
||||
b = box.wait_new_job(known)
|
||||
known.add(b)
|
||||
print(label, "created", a, box.record(a)["created_at"], b, box.record(b)["created_at"],
|
||||
"registered at +%.2fs" % (time.time() - t0))
|
||||
if label == "before":
|
||||
assert box.record(b)["created_at"] == box.record(a)["created_at"], "the same-second tie this exercises"
|
||||
rc, out, err = box.finish(proc)
|
||||
assert rc == 5, (label, out + err)
|
||||
rec = box.record(b)
|
||||
@@ -727,6 +742,49 @@ def test_a3_three_concurrent_creates_in_one_folder_take_distinct_panes(local_mqt
|
||||
_teardown(b)
|
||||
|
||||
|
||||
def test_s2_an_ambient_and_a_named_call_share_the_servers_pane_claim():
|
||||
"""review e8e471ff R1: a call with the ambient target - no -L, HERDR_SESSION_NAME
|
||||
empty or `default` - from a pane of server S is answered by S (the pane's
|
||||
HERDR_SESSION / HERDR_SOCKET_PATH), so it claims under S's lock, the one a
|
||||
call naming S takes: held, both wait; racing, they take distinct panes."""
|
||||
b = _make_box(f"t50-{uuid.uuid4().hex[:8]}")
|
||||
ambient = dict(HERDR_SESSION_NAME="default", HERDR_SESSION=b.sess,
|
||||
HERDR_SOCKET_PATH=str(b.home / ".config/herdr/sessions" / b.sess / "herdr.sock"))
|
||||
new_session = 'herdr new-session -d -s {name} -c "$WORKSPACE_ROOT" "claude --dangerously-skip-permissions"'
|
||||
try:
|
||||
assert b.sh("herdr agent list").returncode == 0 # the server is up before the race
|
||||
lock = b.home / ".local/state/mam/pane-lock" / f"{b.sess}.lock"
|
||||
lock.parent.mkdir(parents=True, exist_ok=True)
|
||||
with lock.open("a") as held:
|
||||
fcntl.flock(held, fcntl.LOCK_EX)
|
||||
for env in ({}, ambient):
|
||||
res = b.sh(new_session.format(name="t50-held-creator-claude"), MAM_PANE_CLAIM_WAIT_S="1", **env)
|
||||
assert res.returncode == 1 and "another pane claim" in res.stderr, (env, res.stdout, res.stderr)
|
||||
assert json.loads(b.herdr("agent", "list").stdout)["result"]["agents"] == []
|
||||
_hold_pane_creation(b, 8)
|
||||
procs = []
|
||||
for name, env in (("t50-named-creator-claude", {}), ("t50-ambient-creator-claude", ambient)):
|
||||
if procs:
|
||||
time.sleep(2)
|
||||
out = tempfile.TemporaryFile("w+", dir=str(b.home))
|
||||
err = tempfile.TemporaryFile("w+", dir=str(b.home))
|
||||
procs.append((name, subprocess.Popen(["bash", "-c", f'source "{SKILLS}/lib.sh"\n' + new_session.format(name=name)],
|
||||
stdout=out, stderr=err, text=True, cwd=str(b.ctl), env=b.env(**env)), out, err))
|
||||
outcome = {}
|
||||
for name, proc, out, err in procs:
|
||||
rc = proc.wait(timeout=120)
|
||||
out.seek(0); err.seek(0)
|
||||
outcome[name] = (rc, out.read(), err.read())
|
||||
assert all(o[0] == 0 for o in outcome.values()), {n: (o[0], o[2][-800:]) for n, o in outcome.items()}
|
||||
assert "agent_pane_busy" not in "".join(o[1] + o[2] for o in outcome.values())
|
||||
panes = _agent_panes(b)
|
||||
assert sorted(panes) == sorted(n for n, *_ in procs), panes
|
||||
assert len({p for p, _c in panes.values()}) == 2, ("two agents on one pane", panes)
|
||||
assert all(Path(c).resolve() == b.ctl.resolve() for _p, c in panes.values()), panes
|
||||
finally:
|
||||
_teardown(b)
|
||||
|
||||
|
||||
def test_s_three_concurrent_new_sessions_in_one_folder_take_distinct_panes():
|
||||
"""The shim alone, no create around it: three `new-session` into one cwd on
|
||||
one running server at once. Before the pane claim was serialised, two of
|
||||
|
||||
Reference in new issue
Block a user