From 98804dbeef91c5f1ef517817c8f81dd2aeec523e Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Thu, 2 Jul 2026 21:32:08 -0300 Subject: [PATCH] fix(tui_gateway): back off notification poller when session is busy The busy-session branch of _notification_poller_loop re-queued the completion event and immediately re-polled it with no sleep, spinning at full speed (100% CPU, ~1100 futex/s of GIL churn) for as long as the session stayed running. This starved the dashboard asyncio loop: /api/status went from 0.14s to 3-6s with 10s timeouts. Sleep 0.25s outside history_lock before re-polling, mirroring the 0.1s back-off already used for foreign-session events. --- tui_gateway/server.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 412c89aaf15..fb98f615fa3 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -8487,11 +8487,19 @@ def _notification_poller_loop( _emit("status.update", sid, {"kind": "process", "text": text}) _emitted.add(_dedup_key) + _requeued = False with session["history_lock"]: if session.get("running"): process_registry.completion_queue.put(evt) - continue - session["running"] = True + _requeued = True + else: + session["running"] = True + if _requeued: + # Back off before re-polling: the re-queued event keeps the queue + # non-empty, so without a sleep this loop spins at full speed + # (100% CPU, GIL churn) for as long as the session stays busy. + time.sleep(0.25) + continue rid = f"__notif__{int(time.time() * 1000)}" try: