From 2bbbb6da2b4c2af4f4e0568bf9071abe7d6e8118 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 12 Apr 2026 21:24:49 -0500 Subject: [PATCH] =?UTF-8?q?Fix=20API=20hang=20=E2=80=94=20run=20gaze=20pus?= =?UTF-8?q?h=20in=20detached=20thread?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Synchronous urllib.urlopen at 10Hz was starving uvicorn's event loop via GIL contention. Now each push runs in its own daemon thread, and skips if the previous push is still in flight (natural rate limiting). Co-Authored-By: Claude Opus 4.6 (1M context) --- headmic.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/headmic.py b/headmic.py index 950b958..79df6c7 100644 --- a/headmic.py +++ b/headmic.py @@ -463,17 +463,28 @@ def doa_track_loop(): time.sleep(interval) +_gaze_push_thread = None + def _push_gaze(x: int, y: int): - """Fire-and-forget gaze push to eye service. Uses urllib to avoid httpx connection overhead.""" - try: - import urllib.request - data = json.dumps({"x": x, "y": y}).encode() - req = urllib.request.Request( - f"{EYE_SERVICE_URL}/gaze", data=data, - headers={"Content-Type": "application/json"}) - urllib.request.urlopen(req, timeout=0.3) - except Exception: - pass + """Fire-and-forget gaze push to eye service in a detached thread.""" + global _gaze_push_thread + # Skip if previous push is still in flight + if _gaze_push_thread and _gaze_push_thread.is_alive(): + return + + def _send(): + try: + import urllib.request + data = json.dumps({"x": x, "y": y}).encode() + req = urllib.request.Request( + f"{EYE_SERVICE_URL}/gaze", data=data, + headers={"Content-Type": "application/json"}) + urllib.request.urlopen(req, timeout=0.5) + except Exception: + pass + + _gaze_push_thread = threading.Thread(target=_send, daemon=True) + _gaze_push_thread.start() # ============================================================================