Store: publish a search's completion and hand over its queue atomically

A query arriving between the queue handover and the completion event could be
queued with nobody to start it, leaving the source 'loading' forever.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-28 22:28:45 +10:00
1 parent 5ac109c381
commit 4fd8bb79af
2 files changed
+23 -1

No files matched your search

+22
View File
@@ -95,6 +95,28 @@ class SearchTests(SettingsTest):
finally: finally:
release.set() release.set()
def test_query_arriving_as_a_search_finishes_is_not_stranded(self):
mod = fake()
source = mod.sources()[0]
arrived = []
class Event(threading.Event):
def set(self):
if not arrived: # a request lands just as the first search completes
arrived.append(None)
t = threading.Thread(target=lambda: arrived.append(search._launch(mod, source, 'second', 50)))
t.start()
t.join(.3) # blocks on search._lock if completion is published atomically
super().set()
with patch.object(search, 'threading', types.SimpleNamespace(Event=Event, Thread=threading.Thread)):
search._launch(mod, source, 'first', 50)
for _ in range(200):
if len(arrived) == 2:
break
time.sleep(.01)
self.assertTrue(arrived[1]['event'].wait(2))
self.assertEqual(arrived[1]['query'], ('second', 50))
def test_set_enabled_does_not_hold_search_lock_in_source(self): def test_set_enabled_does_not_hold_search_lock_in_source(self):
free = [] free = []
def set_enabled(source_id, enabled): def set_enabled(source_id, enabled):
+1 -1
View File
@@ -249,7 +249,7 @@ def _start(key, task):
task['error'] = str(e) task['error'] = str(e)
task['limited'] = isinstance(e, SourceLimited) task['limited'] = isinstance(e, SourceLimited)
finally: finally:
with _lock: with _lock: # completion and queue handover are one step for _launch
queued = _pending.pop(key, None) queued = _pending.pop(key, None)
if queued: if queued:
_running[key] = queued _running[key] = queued