From 01ae7a5668ce0fa2efca524a4567cacdd0786c95 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Sun, 6 Sep 2026 14:06:12 +0530 Subject: [PATCH] refactor(tui-gateway): _rebind_live_transport lives with the reattach contract in session_lifecycle It is lifecycle logic (detached sentinel, reap cancel) and its siblings _reattach_refusal / _cancel_ws_orphan_reap are already there; server.py is the facade and gets no new definitions. Also trims the _claim_or_reuse_live comment to point at where the reap cancel now happens. --- tui_gateway/server.py | 15 ++------------- tui_gateway/session_lifecycle.py | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 82e3c20d6f..671def454a 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -2444,8 +2444,8 @@ def _claim_or_reuse_live(sid: str, session_key: str, record: dict, lease) -> tup if live is not None: if lease is not None: lease.release() - # Guarded reuse cancels the reap only after accepting reattachment; - # a rejected resume must leave an in-flight orphan interrupt polling. + # The reap is cancelled by the guarded reuse (_reattach_refusal), not here: a rejected + # reattach must leave an in-flight orphan interrupt polling. return live with _sessions_lock: _sessions[sid] = record @@ -2686,17 +2686,6 @@ def _live_visible_history(session: dict, db, in_memory_fallback: list[dict]) -> return in_memory_fallback -def _rebind_live_transport(sid: str, session: dict, transport: Transport) -> None: - """Point a live session at ``transport`` (caller holds ``history_lock``).""" - session["transport"] = transport - # Every transport that showed this session (pop-outs resume the same sid); on disconnect the last - # viewer becomes the transport instead of the drop sentinel. - session.setdefault("viewers", {})[transport] = time.time() - # See #83716. - if transport is not _detached_ws_transport: - _cancel_ws_orphan_reap(sid) # the client is back — a pending ws-orphan reap must not fire - - def _live_session_payload( sid: str, session: dict, *, cols: int | None = None, touch: bool = False, transport: Transport | None = None, omit_messages: bool = False) -> dict: diff --git a/tui_gateway/session_lifecycle.py b/tui_gateway/session_lifecycle.py index 5116186764..e7a77fcb37 100644 --- a/tui_gateway/session_lifecycle.py +++ b/tui_gateway/session_lifecycle.py @@ -444,9 +444,9 @@ def _cancel_ws_orphan_reap(sid: str) -> None: def _reattach_refusal(rid, sid: str, session: dict) -> dict | None: - """Under ``_session_resume_lock``: the error a reattaching RPC (resume/activate/prompt.submit) must return - instead of rebinding — ``session`` is no longer the live record for ``sid``, or a client-gone interrupt is - still settling and the reap Timer must keep polling. None when the reattach may proceed.""" + """Under ``_session_resume_lock``: why a reattaching RPC (resume/activate/prompt.submit) must NOT rebind + ``session`` — it is stale, or a client-gone interrupt is still settling and the reap Timer must keep + polling. None when the reattach may proceed.""" if _sessions.get(sid) is not session: return _err(rid, 4007, "session no longer live; retry resume") if session.get("_client_gone_interrupt_requested"): @@ -454,6 +454,17 @@ def _reattach_refusal(rid, sid: str, session: dict) -> dict | None: return None +def _rebind_live_transport(sid: str, session: dict, transport: Transport) -> None: + """Point a live session at ``transport`` (caller holds ``history_lock``).""" + session["transport"] = transport + # Every transport that showed this session (pop-outs resume the same sid); on disconnect the last + # viewer becomes the transport instead of the drop sentinel. + session.setdefault("viewers", {})[transport] = time.time() + # See #83716. + if transport is not _detached_ws_transport: + _cancel_ws_orphan_reap(sid) # the client is back — a pending ws-orphan reap must not fire + + def _ws_orphan_turn_activity_is_fresh(session: dict) -> bool: """Whether a detached RUNNING turn's activity clock (``_touch_activity``) is still fresh — the reaper must NOT interrupt healthy detached work (closed laptop). Conservative: disabled threshold, missing/opaque agent, unreadable