fix(tui): eliminate TOCTOU race between goal followup and concurrent turn submission
H1: When a turn finished and a goal followup existed, the finally block set running=False (under history_lock), then the goal followup code re-acquired the lock and set running=True. Between these two lock acquisitions, a notification poller or concurrent prompt.submit could acquire the lock, see running=False, and start a second turn on the same session — leading to interleaved history writes. Fix: instead of releasing and re-acquiring the turn guard, keep running=True across the finally→goal-followup boundary. Only set running=False in the finally when there is NO goal followup to chain. This eliminates the TOCTOU window entirely. All 183 TUI tests pass, including goal command tests.
This commit is contained in:
parent
8077e7d2fb
commit
42d7054f65
|
|
@ -4574,24 +4574,22 @@ def _run_prompt_submit(rid, sid: str, session: dict, text: Any) -> None:
|
|||
pass
|
||||
_clear_session_context(session_tokens)
|
||||
with session["history_lock"]:
|
||||
session["running"] = False
|
||||
if not goal_followup:
|
||||
# Only release the turn guard when there is no followup
|
||||
# to chain. When a goal continuation exists we keep
|
||||
# running=True so no other thread (notification poller,
|
||||
# concurrent prompt.submit) can steal the turn during the
|
||||
# gap between this finally and the followup dispatch below.
|
||||
session["running"] = False
|
||||
session["last_active"] = time.time()
|
||||
_clear_inflight_turn(session)
|
||||
_emit("session.info", sid, _session_info(agent, session))
|
||||
|
||||
# Chain a goal-continuation turn if the judge said so. We do
|
||||
# this AFTER the finally releases session["running"], so the
|
||||
# nested _run_prompt_submit doesn't deadlock on the busy
|
||||
# guard. A real user prompt that races us wins because
|
||||
# prompt.submit sets running=True under the history_lock and
|
||||
# we check that guard before re-firing.
|
||||
# Chain a goal-continuation turn if the judge said so. Because the
|
||||
# finally block above intentionally kept running=True when a followup
|
||||
# exists, there is no TOCTOU window — no other thread can sneak in
|
||||
# and start a concurrent turn on this session.
|
||||
if goal_followup:
|
||||
with session["history_lock"]:
|
||||
if session.get("running"):
|
||||
# User already sent something — their turn wins,
|
||||
# the judge will re-run on the next turn anyway.
|
||||
return
|
||||
session["running"] = True
|
||||
try:
|
||||
_emit("message.start", sid)
|
||||
_run_prompt_submit(rid, sid, session, goal_followup)
|
||||
|
|
|
|||
Loading…
Reference in New Issue