fix(agents): 같은 폴더의 같은 종류 에이전트가 서로의 대화 ID 를 쓰지 않는다 (Issue #63) #69
No files matched your search
@@ -180,15 +180,16 @@ def _registered_for_attempt(m: Dict[str, Any], row: Optional[Dict[str, Any]]) ->
|
|||||||
return int(row.get("herdr_session_epoch") or 0) > int(m.get("prev_epoch") or 0)
|
return int(row.get("herdr_session_epoch") or 0) > int(m.get("prev_epoch") or 0)
|
||||||
|
|
||||||
|
|
||||||
def _read_and_prune_markers(dpath: str, me: str, my_pid: int, state: Dict[str, Any],
|
def _read_and_prune_markers(dpath: str, my_pid: int, state: Dict[str, Any],
|
||||||
is_alive: Callable[[int], bool] = pid_alive,
|
is_alive: Callable[[int], bool] = pid_alive,
|
||||||
pane_state: Callable[[str, str], Optional[bool]] = (
|
pane_state: Callable[[str, str], Optional[bool]] = (
|
||||||
lambda server, session: None),
|
lambda server, session: None),
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""Markers that still count. A live owner keeps its marker (another live
|
"""Markers that still count. A live owner keeps its marker (another live
|
||||||
attempt on `me` included). A dead owner's marker goes once its row is
|
attempt on the caller's session included). A dead owner's marker goes once
|
||||||
registered for the attempt (the row decides from then on) or herdr answers
|
its row is registered for the attempt (the row decides from then on) or
|
||||||
that its session is gone; until then it is kept as an orphan."""
|
herdr answers that its session is gone; until then it is kept as an orphan,
|
||||||
|
one an earlier attempt left on the caller's session included."""
|
||||||
rows_by_name = {
|
rows_by_name = {
|
||||||
str(s.get("name") or ""): s
|
str(s.get("name") or ""): s
|
||||||
for s in (state.get("herdr_sessions") or []) if isinstance(s, dict)
|
for s in (state.get("herdr_sessions") or []) if isinstance(s, dict)
|
||||||
@@ -219,7 +220,7 @@ def _read_and_prune_markers(dpath: str, me: str, my_pid: int, state: Dict[str, A
|
|||||||
drop = True
|
drop = True
|
||||||
elif is_alive(owner_pid):
|
elif is_alive(owner_pid):
|
||||||
drop = False
|
drop = False
|
||||||
elif sess == me or not sess:
|
elif not sess:
|
||||||
drop = True
|
drop = True
|
||||||
elif _registered_for_attempt(data, rows_by_name.get(sess)):
|
elif _registered_for_attempt(data, rows_by_name.get(sess)):
|
||||||
drop = True
|
drop = True
|
||||||
@@ -304,7 +305,7 @@ def wait(cwd: str, agent: str, session: str, owner_pid: int,
|
|||||||
file=sys.stderr, flush=True,
|
file=sys.stderr, flush=True,
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
markers = _read_and_prune_markers(dpath, session, owner_pid, state,
|
markers = _read_and_prune_markers(dpath, owner_pid, state,
|
||||||
is_alive=is_alive, pane_state=bounded_probe)
|
is_alive=is_alive, pane_state=bounded_probe)
|
||||||
blks = blockers(state, cwd, agent, session, markers, is_alive=is_alive)
|
blks = blockers(state, cwd, agent, session, markers, is_alive=is_alive)
|
||||||
if not blks:
|
if not blks:
|
||||||
|
|||||||
@@ -1050,8 +1050,9 @@ def test_id_order_dead_owner_marker_blocks_while_its_unregistered_herdr_session_
|
|||||||
"""PR #69 review: a create/resume killed after spawning its pane but before
|
"""PR #69 review: a create/resume killed after spawning its pane but before
|
||||||
registering its row leaves a dead-owner marker. It keeps blocking while the
|
registering its row leaves a dead-owner marker. It keeps blocking while the
|
||||||
marker's herdr session is alive, goes once the row is registered (the row
|
marker's herdr session is alive, goes once the row is registered (the row
|
||||||
decides) or the session is gone; a live attempt on the same session name is
|
decides) or the session is gone; on the same session name a live attempt is
|
||||||
not pruned; a running row without a pane pid blocks (fail-closed)."""
|
not pruned and a dead one follows the orphan rules (PR #69 review r3); a
|
||||||
|
running row without a pane pid blocks (fail-closed)."""
|
||||||
from lib_py.agents import id_order
|
from lib_py.agents import id_order
|
||||||
|
|
||||||
home = tmp_path / "home"
|
home = tmp_path / "home"
|
||||||
@@ -1116,16 +1117,33 @@ def test_id_order_dead_owner_marker_blocks_while_its_unregistered_herdr_session_
|
|||||||
assert wait_b() == 0
|
assert wait_b() == 0
|
||||||
assert not marker_a.exists()
|
assert not marker_a.exists()
|
||||||
|
|
||||||
# A live attempt on the same session name is kept and blocks; a dead one is taken over
|
# Same session name: a live attempt is kept and blocks; a dead one is kept and blocks
|
||||||
|
# while its herdr session is alive or herdr cannot tell, and is taken over once gone
|
||||||
same = Path(dpath) / "same-agy.json"
|
same = Path(dpath) / "same-agy.json"
|
||||||
|
|
||||||
|
def wait_same():
|
||||||
|
return id_order.wait(str(ws), "agy", "same-agy", os.getpid(), yaml_path=str(yaml_path),
|
||||||
|
home=str(home), wait_s_val=0, pane_state=pane_state)
|
||||||
|
|
||||||
id_order.write_marker(dpath, "same-agy", live.pid, str(ws), "agy")
|
id_order.write_marker(dpath, "same-agy", live.pid, str(ws), "agy")
|
||||||
assert id_order.wait(str(ws), "agy", "same-agy", os.getpid(), yaml_path=str(yaml_path),
|
assert wait_same() == 1
|
||||||
home=str(home), wait_s_val=0, pane_state=pane_state) == 1
|
|
||||||
assert json.loads(same.read_text())["owner_pid"] == live.pid
|
assert json.loads(same.read_text())["owner_pid"] == live.pid
|
||||||
live.kill()
|
live.kill()
|
||||||
live.wait()
|
live.wait()
|
||||||
assert id_order.wait(str(ws), "agy", "same-agy", os.getpid(), yaml_path=str(yaml_path),
|
alive_sessions.add("same-agy")
|
||||||
home=str(home), wait_s_val=0, pane_state=pane_state) == 0
|
probes.clear()
|
||||||
|
capsys.readouterr()
|
||||||
|
assert wait_same() == 1
|
||||||
|
assert probes == [("", "same-agy")]
|
||||||
|
assert json.loads(same.read_text())["owner_pid"] == live.pid
|
||||||
|
err = capsys.readouterr().err
|
||||||
|
assert "its herdr session is alive" in err and "kill-session -t same-agy" in err
|
||||||
|
unknown_sessions.add("same-agy")
|
||||||
|
assert wait_same() == 1
|
||||||
|
assert json.loads(same.read_text())["owner_pid"] == live.pid
|
||||||
|
unknown_sessions.clear()
|
||||||
|
alive_sessions.clear()
|
||||||
|
assert wait_same() == 0
|
||||||
assert json.loads(same.read_text())["owner_pid"] == os.getpid()
|
assert json.loads(same.read_text())["owner_pid"] == os.getpid()
|
||||||
same.unlink()
|
same.unlink()
|
||||||
|
|
||||||
@@ -1233,7 +1251,8 @@ def test_id_order_real_probe_against_mock_herdr_fails_closed(mam_sandbox, mock_h
|
|||||||
"""PR #69 review r2 P1: lib.sh mam_id_order_wait with the real probe (sandbox
|
"""PR #69 review r2 P1: lib.sh mam_id_order_wait with the real probe (sandbox
|
||||||
copy, mock herdr). A listed orphan blocks; an unlisted one is pruned; a failed
|
copy, mock herdr). A listed orphan blocks; an unlisted one is pruned; a failed
|
||||||
`agent list` (server_not_running included) keeps the marker and refuses;
|
`agent list` (server_not_running included) keeps the marker and refuses;
|
||||||
with MAM_ID_ORDER_WAIT_S=0 herdr is not asked and the marker is kept."""
|
with MAM_ID_ORDER_WAIT_S=0 herdr is not asked and the marker is kept. A
|
||||||
|
retry on the orphan's own session name is held the same way (review r3)."""
|
||||||
from lib_py.agents import id_order
|
from lib_py.agents import id_order
|
||||||
|
|
||||||
ws = mam_sandbox / "probe-ws"
|
ws = mam_sandbox / "probe-ws"
|
||||||
@@ -1253,12 +1272,12 @@ def test_id_order_real_probe_against_mock_herdr_fails_closed(mam_sandbox, mock_h
|
|||||||
state.update(kw)
|
state.update(kw)
|
||||||
Path(mock_herdr).write_text(json.dumps(state))
|
Path(mock_herdr).write_text(json.dumps(state))
|
||||||
|
|
||||||
def wait_b(cap="2"):
|
def wait_b(cap="2", who=name_b):
|
||||||
for f in Path(dpath).glob(f"{name_b}.json"):
|
for f in Path(dpath).glob(f"{name_b}.json"):
|
||||||
f.unlink()
|
f.unlink()
|
||||||
id_order.write_marker(dpath, name_a, dead.pid, str(ws), "agy", herdr_server="probe-ws")
|
id_order.write_marker(dpath, name_a, dead.pid, str(ws), "agy", herdr_server="probe-ws")
|
||||||
return subprocess.run(
|
return subprocess.run(
|
||||||
["bash", "-c", 'source "$1" && mam_id_order_wait "$2" agy "$3"', "_", str(lib), str(ws), name_b],
|
["bash", "-c", 'source "$1" && mam_id_order_wait "$2" agy "$3"', "_", str(lib), str(ws), who],
|
||||||
capture_output=True, text=True, cwd=str(mam_sandbox), timeout=120,
|
capture_output=True, text=True, cwd=str(mam_sandbox), timeout=120,
|
||||||
env=dict(os.environ, MAM_ID_ORDER_WAIT_S=cap, HERDR_SESSION_NAME="probe-ws"),
|
env=dict(os.environ, MAM_ID_ORDER_WAIT_S=cap, HERDR_SESSION_NAME="probe-ws"),
|
||||||
)
|
)
|
||||||
@@ -1268,6 +1287,9 @@ def test_id_order_real_probe_against_mock_herdr_fails_closed(mam_sandbox, mock_h
|
|||||||
res = wait_b()
|
res = wait_b()
|
||||||
assert res.returncode == 1 and "its herdr session is alive" in res.stderr, res.stderr
|
assert res.returncode == 1 and "its herdr session is alive" in res.stderr, res.stderr
|
||||||
assert marker_a.exists()
|
assert marker_a.exists()
|
||||||
|
res = wait_b(who=name_a)
|
||||||
|
assert res.returncode == 1 and "its herdr session is alive" in res.stderr, res.stderr
|
||||||
|
assert json.loads(marker_a.read_text())["owner_pid"] == dead.pid
|
||||||
|
|
||||||
set_mock(force_error_code="protocol_mismatch")
|
set_mock(force_error_code="protocol_mismatch")
|
||||||
res = wait_b()
|
res = wait_b()
|
||||||
@@ -1290,6 +1312,9 @@ def test_id_order_real_probe_against_mock_herdr_fails_closed(mam_sandbox, mock_h
|
|||||||
res = wait_b()
|
res = wait_b()
|
||||||
assert res.returncode == 0, res.stderr
|
assert res.returncode == 0, res.stderr
|
||||||
assert not marker_a.exists()
|
assert not marker_a.exists()
|
||||||
|
res = wait_b(who=name_a)
|
||||||
|
assert res.returncode == 0, res.stderr
|
||||||
|
assert json.loads(marker_a.read_text())["owner_pid"] != dead.pid
|
||||||
finally:
|
finally:
|
||||||
holder.kill()
|
holder.kill()
|
||||||
holder.wait()
|
holder.wait()
|
||||||
Reference in new issue
Block a user