TUI launch: two launches that create the control session at the same moment race, and the loser fails with "duplicate session" (tmux 3.4, psmux 3.4.0)
Maintainer thường phản hồi trong vòng 1 ngày
Một pull request liên quan đã được merge.
- #955 của @dracic — đã merge
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 25/100
Hướng nghiên cứu
The fix belongs in _ensure_ctl_session in src/bmad_loop/tui/launch.py, where a MultiplexerError from mux.new_session is turned into a LaunchError without re-checking has_session. Read adapters/tmux_base.py new_session to see the duplicate session path. A change is done when a failed create re-checks the session and continues if it now exists. Linked PR #955 is already open for this, so check it before starting.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
tui.launch._ensure_ctl_session checks has_session and then calls new_session, with nothing between them. When two processes both observe that the control session is absent, a competing create can fail after another process has created the session. The launcher converts that create failure into LaunchError: multiplexer ctl-session setup failed: ... without re-checking whether the desired session now exists. The control session exists a moment later, so the launch was refused for nothing. On tmux the control session is one fixed name on the default socket, shared by every project (source-derived, runs.ctl_session_for; not measured across two projects), so two TUIs in two different projects that start their first run together should be enough. It should re-check after a failed create and use the session that now exists.
Environment
psmux 3.4.0 (20aa1d7), Windows 11 Enterprise 10.0.26200, PowerShell 7.6.6 (launching shell; SHELL removed), PSMUX_NO_WARM=1; tmux 3.4 in WSL2 Ubuntu 24.04.4 (bash 5.2.21) with TMUX_TMPDIR, HOME and XDG_CONFIG_HOME set to a throwaway dir; bmad-loop 88c1879c
Steps to reproduce
No coding CLI is started. The psmux steps run from PowerShell 7 with SHELL removed; the tmux steps run from bash in WSL. Save the script below as ctl_race.py in the directory you run the commands from (for the tmux steps, /tmp/ctlrace/proj, after the mkdir), and run it with the bmad-loop checkout's interpreter. It starts N processes that each call the TUI launcher's own control-session setup (_ensure_ctl_session, called before start_detached creates the run's window) at the same instant, on a project whose control session does not exist yet, and prints what each one got.
# usage: python ctl_race.py <project> <N> [fixed] (driver)
import json, subprocess, sys, time
from pathlib import Path
from bmad_loop import cli
from bmad_loop.tui import launch
project = Path(sys.argv[1])
cli._configure_mux(project)
if sys.argv[2] == "worker":
if sys.argv[4:] == ["fixed"]: # candidate fix, in this process only: a create that lost the race re-checks
m = launch.get_multiplexer(); orig = m.new_session
def new(name, cwd, *a, **k):
try:
return orig(name, cwd, *a, **k)
except launch.MultiplexerError:
if m.has_session(name):
return None
raise
m.new_session = new
while time.time() < float(sys.argv[3]):
time.sleep(0.002)
try:
print("OK", launch._ensure_ctl_session(project), flush=True)
except launch.LaunchError as e:
print("LaunchError", e, flush=True)
sys.exit(0)
mux = launch.get_multiplexer(); ctl = launch.ctl_session(project)
print("backend", type(mux).__name__, "ctl", ctl, "exists before:", mux.has_session(ctl))
go = time.time() + 3
ps = [subprocess.Popen([sys.executable, __file__, str(project), "worker", str(go)] + sys.argv[3:4],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) for _ in range(int(sys.argv[2]))]
outs = [p.communicate(timeout=120)[0].strip() for p in ps]
for o in outs:
print(" worker:", o)
print("exists after:", mux.has_session(ctl), "launch_errors:", sum("LaunchError" in o for o in outs))
tmux 3.4 (WSL, bash). A private tmux server through TMUX_TMPDIR, and a project whose policy selects tmux; python is the checkout's Linux venv interpreter:
export TMUX_TMPDIR=/tmp/ctlrace/tmux HOME=/tmp/ctlrace XDG_CONFIG_HOME=/tmp/ctlrace BMAD_LOOP_STATE_DIR=/tmp/ctlrace/state
mkdir -p $TMUX_TMPDIR /tmp/ctlrace/proj/.bmad-loop && : > /tmp/ctlrace/.tmux.conf
printf '[mux]\nbackend = "tmux"\n' > /tmp/ctlrace/proj/.bmad-loop/policy.toml
cd /tmp/ctlrace/proj
for i in 1 2 3; do python ctl_race.py /tmp/ctlrace/proj 2; tmux kill-server; done # two launchers, three trials
python ctl_race.py /tmp/ctlrace/proj 1; tmux kill-server # control: one launcher
for i in 1 2 3; do python ctl_race.py /tmp/ctlrace/proj 2 fixed; tmux kill-server; done # candidate fix
Measured output of one two-launcher trial (10 of 10 trials looked like this, in two separate series of 10):
backend TmuxMultiplexer ctl bmad-loop-ctl exists before: False
worker: OK bmad-loop-ctl
worker: LaunchError multiplexer ctl-session setup failed: tmux new-session -d failed: duplicate session: bmad-loop-ctl
exists after: True launch_errors: 1
The one-launcher control printed launch_errors: 0; the fixed variant printed launch_errors: 0 in 5 of 5 trials. In a separate run, the same two launchers released 1.5 s apart instead of at one instant also gave 0 errors, so the failure needs overlapping creates.
psmux 3.4.0 (PowerShell 7, SHELL removed). Same script, a throwaway project with [mux] backend = "psmux", its own state dir, and kill-server scoped to that project's registry after each trial:
Remove-Item Env:SHELL -ErrorAction SilentlyContinue
$Py = 'C:\path\to\bmad-loop\.venv\Scripts\python.exe' # the checkout's interpreter
$env:PSMUX_NO_WARM = '1'; $env:PYTHONUTF8 = '1'
$root = Join-Path $env:TEMP "ctlrace-$PID"; $proj = Join-Path $root 'proj'
$env:BMAD_LOOP_STATE_DIR = Join-Path $root 'state'
New-Item -ItemType Directory -Force (Join-Path $proj '.bmad-loop') | Out-Null
Set-Content (Join-Path $proj '.bmad-loop\policy.toml') "[mux]`nbackend = `"psmux`"`n"
$reg = & $Py -c "import sys; from pathlib import Path; from bmad_loop import runs; print(runs.export_psmux_registry_root(Path(sys.argv[1])))" $proj
foreach ($i in 1..10) {
& $Py ctl_race.py $proj 2
$env:PSMUX_DATA_DIR = $reg; psmux kill-server; Remove-Item Env:PSMUX_DATA_DIR # this project's registry only
}
Measured (rates vary between runs): with two launchers, 1 to 3 of 10 trials had one failure; with five launchers, 2 of 6 to 8 of 13 trials had at least one, each as
worker: LaunchError multiplexer ctl-session setup failed: psmux new-session failed: psmux: failed to create session 'bmad-loop-ctl'
followed by exists after: True. Not every simultaneous pair has a loser on psmux: in trials without a failure both creates returned success. One launcher: 0 errors. Five launchers released 1.5 s apart: 0 errors. The fixed variant with five launchers: 0 errors in 5 of 5 trials. In these samples psmux failed less often than tmux. The sample does not establish why the rates differ. Independently, psmux v3.4.0 source contains a duplicate-server mutex; this is source-derived, not a measured explanation of the rate.
Through the TUI launch path. Five processes calling tui.launch.start_run_detached(project, <own run id>) at the same instant on a fresh project (psmux 3.4.0, one run): four runs started in their own control windows, the fifth returned LaunchError: multiplexer ctl-session setup failed: psmux new-session failed: psmux: failed to create session 'bmad-loop-ctl' and its run was never started (no run directory).
Expected vs actual
| actual | expected | |
|---|---|---|
| two launches both observe no control session; one create loses the race | one can be refused even though the control session now exists, with multiplexer ctl-session setup failed: ... duplicate session (tmux) / failed to create session (psmux) |
both start in the one control session |
| control session after the race | exists (has_session True) |
exists |
Source
src/bmad_loop/tui/launch.py at 88c1879c, _ensure_ctl_session (line 1048), the only creator of the control session, called by start_detached (defined at line 1252), call site line 1329:
try:
if not mux.has_session(name):
mux.new_session(name, project)
except MultiplexerError as e:
raise LaunchError(f"multiplexer ctl-session setup failed: {e}") from e
A failed new_session is not re-checked: a create that failed because the session was created in between reads the same as one that failed for any other reason. On tmux, new_session (adapters/tmux_base.py:366) runs new-session -d -s bmad-loop-ctl, which tmux refuses with duplicate session once the name exists.
Impact on bmad-loop
Every TUI launch (run, sweep, resume, resolve) goes through start_detached -> _ensure_ctl_session (source-derived). Source-derived: this exception occurs before this invocation creates its control window or records that window. A new launch therefore does not start its child command through this path; existing resume/resolve run state is a separate concern. The operator sees an error for a launch that would have worked, and has to start it again. The window is only open while the control session does not exist yet (the first launch after a server start, or after cleanup removed it). On tmux that one session is shared by every project on the default socket, so the race should be between any two bmad-loop processes on the machine, not only within one project (source-derived, not measured).
Suggested fix
Treat a failed create as "maybe someone else created it": after new_session raises, re-check has_session(name) and continue when it is now there; raise only when it is still absent. Measured with that change applied in the probe process only (not in the checkout): 0 failures on both backends in the trials above. Not validated beyond those trials.
- Ngôn ngữ chính
- Python
- Star
- 165
- Fork
- 69
- Merge trung bình
- 1 ngày 3 giờ
- Pull request đã merge (30 ngày)
- 75
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của bmad-code-org/bmad-loop
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 69/100
bmad-code-org/bmad-loop#973 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
bmad-code-org/bmad-loop#965 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
bmad-code-org/bmad-loop#835 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
tickets: an injected plan_halt result for a built plan escalates with stopped-attempt wordingĐang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 36/100
bmad-code-org/bmad-loop#972 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 1-2 ngày Mức phù hợp với người mới 49/100
bmad-code-org/bmad-loop#971 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của bmad-code-org/bmad-loop
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
Arekkazu/sgpmp-backend#744 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Review backlog (security): master-branch remote guard trusts unbound provenance package fieldĐang mởagent/reviewer
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
projectbluefin/utah-packages#634 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement good first issue help wanted
Độ khó 2/5 1-2 ngày Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
home-assistant/core#188613 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
WebsiteĐang mởgood first issue
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 91/100
TanbirRamim/open-source-radar#314 ·
Maintainer thường phản hồi trong vòng 1 ngày