Fix/sessions migration sweep race (#195)

* fix(sessions): ensure migration sweep runs before yielding checkpointer to prevent race conditions

* fix(sessions): enhance migration sweep with progress indication and ETA estimation
This commit is contained in:
Xi Zhang
2026-04-29 03:06:25 +02:00
committed by GitHub
parent 50719ef256
commit 7cfec02416
2 changed files with 180 additions and 50 deletions
+41
View File
@@ -1073,6 +1073,47 @@ class TestMigrationSweep(unittest.TestCase):
assert pairs == 1
assert self._row_count("tw", "") == 2
def test_get_checkpointer_blocks_on_sweep_then_idempotent(self):
"""End-to-end: ``get_checkpointer()`` must run the sweep BEFORE
yielding the saver so a concurrent ``aput()`` can't race the
DELETEs. After the first call sets ``user_version=1``, subsequent
calls must skip the sweep entirely.
"""
from EvoScientist import sessions as sessions_module
from EvoScientist.sessions import (
_MIGRATION_VERSION,
get_checkpointer,
)
self._seed([("ge", "", 6)])
# Force the sweep to be needed regardless of file size.
with patch.object(sessions_module, "_MIGRATION_THRESHOLD_BYTES", 1):
# First entry: sweep should run, prune to keep=10 (default), and
# set user_version. With only 6 rows in one (thread, ns) pair,
# the prune is a no-op but user_version is still bumped.
async def _first():
async with get_checkpointer() as saver:
return saver is not None
assert _run(_first())
assert self._user_version() == _MIGRATION_VERSION
# Second entry: sweep must be skipped — patch _run_migration_sweep
# to raise so any accidental re-invocation fails the test loudly.
async def _exploding_sweep(*_args, **_kwargs):
raise AssertionError("sweep must not re-run after marker is set")
with patch.object(
sessions_module, "_run_migration_sweep", _exploding_sweep
):
async def _second():
async with get_checkpointer() as saver:
return saver is not None
assert _run(_second())
class TestDbStats(unittest.TestCase):
"""Tests for the read-only ``db_stats`` diagnostic helper."""