revert(vegas): gate only the prefetch thread; gating ESPN fetches measured worse

84043468 also gated the ESPN chunk fetches and the background data
service's workers, for the hourly sports refresh. A burst test on hdpi
(baseball and football refreshing every 5 minutes, 10-minute soaks, G F F G):

  G  prefetch gated only        0.87%, 0.83% late; 6+ late 20, 16; fetches 0.3-1.8s
  F  + fetch threads gated      1.23%, 1.05% late; 6+ late 12, 16; fetches 1.6-4.6s

Every parked fetch thread wakes at each swap and has to take the GIL again
just to park at the end of the window, so twenty of them cost more than
they saved, and the fetches ran two to three times as long. The plugins'
own copies of espn_dates (half the burst) were never gated anyway.

espn_dates and the background data service go back to main's versions and
the module-level active gate goes. Kept from that commit: the render thread
is never gated, a live refresh from another thread can't take its place,
and nested blocks keep the outer boundary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-24 17:35:34 -04:00
co-authored by Claude Opus 5.5
parent e39f65fce0
commit 9a705c2ca4
5 changed files with 19 additions and 124 deletions
-56
View File
@@ -299,60 +299,6 @@ class TestWhoIsGated:
assert kept and final is None
class TestActiveGate:
@pytest.fixture(autouse=True)
def no_gate_left_behind(self):
yield
render_gate.set_active(None)
def test_without_a_vegas_run_nothing_is_gated(self):
render_gate.set_active(None)
with render_gate.yielding():
assert sys.getprofile() is None
def test_with_one_background_work_gives_way(self):
gate = RenderGate()
render_gate.set_active(gate)
with render_gate.yielding():
assert sys.getprofile() == gate._hook
assert sys.getprofile() is None
def test_espn_chunk_fetches_give_way(self):
from src.common import espn_dates
gate = RenderGate()
render_gate.set_active(gate)
seen = []
class Response:
content = b'{"events": []}'
def raise_for_status(self):
pass
def json(self):
return {"events": []}
class Session:
def get(self, *args, **kwargs):
seen.append(sys.getprofile() == gate._hook)
return Response()
assert espn_dates._fetch_one_chunk(
Session(), "http://x", {}, {}, 5, None, "20260924") == {"events": []}
assert seen == [True]
def test_background_data_fetches_give_way(self):
from src.background_data_service import BackgroundDataService
gate = RenderGate()
render_gate.set_active(gate)
service = BackgroundDataService.__new__(BackgroundDataService)
service._shutdown = True # never started: nothing for __del__ to stop
seen = []
service._fetch_data = lambda request: seen.append(
sys.getprofile() == gate._hook) or "result"
assert service._fetch_data_worker(object()) == "result"
assert seen == [True]
class TestDisplayManager:
@pytest.fixture
def dm(self):
@@ -443,10 +389,8 @@ class TestVegasWiring:
assert c._state_lock in gate._guarded
assert c.stream_manager._buffer_lock in gate._guarded
assert c.plugin_adapter._cache_lock in gate._guarded
assert render_gate.active() is gate # background fetches use it too
c._remove_render_gate()
assert c.display_manager.render_gate is None
assert render_gate.active() is None
@pytest.mark.parametrize("releases", [False, None])
def test_ignored_without_a_binding_that_releases_the_gil(self, monkeypatch, releases):