feat(vegas): ESPN fetches give way to the render thread too

The hourly sports refresh froze hdpi's Vegas scroll for 1.9s: about twenty
espn-chunk threads fetching and parsing at once, and the render thread
(and the stall watchdog) queued behind all of them for the GIL. The
prefetch gate only covered the prefetch thread.

Vegas now makes its gate the active one (render_gate.set_active), and
render_gate.yielding() gives way through it when there is one and does
nothing otherwise. espn_dates wraps each chunk fetch in it (behind the
same import fallback as json_body, for the copies plugins bundle), and the
background data service wraps each worker. The render thread is never
gated -- the first thread to swap is exempt, and a plugin pushing a live
refresh from its update thread cannot take its place -- and nested blocks
keep the outermost frame as the boundary for the lock checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-24 16:40:55 -04:00
co-authored by Claude Opus 5.5
parent a6648ec991
commit 84043468ca
5 changed files with 188 additions and 19 deletions
+7
View File
@@ -26,6 +26,7 @@ from enum import Enum
from concurrent.futures import ThreadPoolExecutor
import pytz
from src.cache_manager import CacheManager
from src.common import render_gate
from src.common.json_body import response_json
from src.common.espn_dates import (
RANGE_RETRY_SECONDS,
@@ -317,6 +318,12 @@ class BackgroundDataService:
return request_id
def _fetch_data_worker(self, request: FetchRequest) -> FetchResult:
"""Fetch one request on a worker thread, giving way to Vegas's render
thread while it scrolls (src/common/render_gate.py)."""
with render_gate.yielding():
return self._fetch_data(request)
def _fetch_data(self, request: FetchRequest) -> FetchResult:
"""
Worker function that performs the actual data fetching.