diff --git a/constitution.py b/constitution.py index 7b7a61de87f7872ff9c66755200cf5103920eec6..09cee4db64af5a0677ae9baf11fd553f80db97b6 100644 --- a/constitution.py +++ b/constitution.py @@ -1264,7 +1264,7 @@ async def pairwise_rank(n: int, compare_fn, progress_fn=None) -> list: # There is exactly one logical pass; step is how far down the ranking we # had to scan to find the first uncovered adjacent pair (1 = top of zip). while True: - scores = rank_centrality(pairs) + scores = await asyncio.to_thread(rank_centrality, pairs) ranking = sorted(range(n), key=lambda idx: scores[idx], reverse=True) target = None @@ -2462,7 +2462,7 @@ async def rank_commits(commits: list[dict], *, epoch: int = -1): if not pairs: commit_score_list = [Decimal("1")] else: - scores = rank_centrality(pairs) + scores = await asyncio.to_thread(rank_centrality, pairs) commit_score_list = [Decimal(str(scores[i])) for i in range(len(ordered))] commit_ranking = { diff --git a/tests/test_pair_selection.py b/tests/test_pair_selection.py index 8a5614da2ba6062af961f2bb942af70fd7c69813..49ddca99fdf1c5fa7a5ca2c9cca600cefbef214d 100644 --- a/tests/test_pair_selection.py +++ b/tests/test_pair_selection.py @@ -14,7 +14,9 @@ progress_fn(event: dict) -> None """ import asyncio +import time import pytest +import constitution as c from constitution import UnionFind, pairwise_rank, rank_centrality @@ -353,6 +355,37 @@ def test_cached_comparisons_yield_to_other_event_loop_work(): assert run(scenario()) >= 19 +def test_rank_centrality_solve_runs_off_the_event_loop(monkeypatch): + original = c.rank_centrality + + def slow_rank_centrality(pairs): + time.sleep(0.05) + return original(pairs) + + monkeypatch.setattr(c, "rank_centrality", slow_rank_centrality) + + async def scenario(): + heartbeat_ticks = 0 + ranking_done = False + + async def cached_compare(i, j): + return [(min(i, j), max(i, j), 2.0, 1.0)] + + async def heartbeat(): + nonlocal heartbeat_ticks + while not ranking_done: + heartbeat_ticks += 1 + await asyncio.sleep(0.001) + + heartbeat_task = asyncio.create_task(heartbeat()) + await c.pairwise_rank(3, cached_compare) + ranking_done = True + await heartbeat_task + return heartbeat_ticks + + assert run(scenario()) >= 5 + + # --------------------------------------------------------------------------- # rank_centrality accumulates (no overwrite bug) # ---------------------------------------------------------------------------