mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-21 05:45:37 +08:00
lazy boot: only repopulate when the index references a missing source
_writable_store_index_needs_population compared the set of stored row hashes against the set of index-referenced hashes symmetrically. The store is append-only (only reconcile_index prunes), so rows orphaned by a content change, an ad-hoc self-heal superseded by conf context, or an uninstall accumulate legitimately - and one orphan made the comparison unequal forever, turning every boot and every post-install reload into a full inline repopulate and permanently defeating the freshness-token scan skip (observed: 5 inline populates across one shed-install integration test, 3 orphaned rows as the stable delta). Check the direction that is actually an invariant: every source_hash the index references must resolve to a stored row, or materialising that tool would fail. Orphaned rows are logged at debug as prune candidates. Measured on the same test: 5 inline populates -> 2 (cold boot + the legitimate freshness-token change from the install itself).
This commit is contained in:
@@ -826,6 +826,17 @@ class LazyToolBox(ToolBox):
|
||||
return False
|
||||
|
||||
def _writable_store_index_needs_population(self) -> bool:
|
||||
"""Return True when an index entry references a source the store lost.
|
||||
|
||||
The invariant that matters is one-directional: every ``source_hash``
|
||||
the index references must resolve to a stored row, or materialising
|
||||
that tool would fail. The reverse is not staleness — the store is
|
||||
append-only (only ``reconcile_index`` prunes), so rows orphaned by a
|
||||
content change, an ad-hoc self-heal superseded by conf context, or an
|
||||
uninstall accumulate legitimately. A symmetric comparison here turned
|
||||
one such orphan into a full inline repopulate on every boot and
|
||||
reload, permanently defeating the freshness-token scan skip.
|
||||
"""
|
||||
if self._store is None:
|
||||
return False
|
||||
stores = (
|
||||
@@ -841,15 +852,25 @@ class LazyToolBox(ToolBox):
|
||||
index_hashes.update(entry.source_hash for entry in index.entries.values() if entry.source_hash)
|
||||
for versions in index.entries_by_version.values():
|
||||
index_hashes.update(entry.source_hash for entry in versions.values() if entry.source_hash)
|
||||
if source_hashes != index_hashes:
|
||||
dangling = index_hashes - source_hashes
|
||||
if dangling:
|
||||
log.info(
|
||||
"Tool source store index/source mismatch for %s (%d indexed sources, %d stored sources); "
|
||||
"running populator",
|
||||
"Tool source store index for %s references %d source(s) missing from the store "
|
||||
"(%d indexed, %d stored); running populator",
|
||||
store_name,
|
||||
len(dangling),
|
||||
len(index_hashes),
|
||||
len(source_hashes),
|
||||
)
|
||||
return True
|
||||
orphaned = len(source_hashes - index_hashes)
|
||||
if orphaned:
|
||||
log.debug(
|
||||
"Tool source store %s holds %d source row(s) no index entry references "
|
||||
"(superseded content or ad-hoc rows; reconcile_index prunes them)",
|
||||
store_name,
|
||||
orphaned,
|
||||
)
|
||||
return False
|
||||
|
||||
def _run_inline_populator(self) -> None:
|
||||
|
||||
@@ -333,6 +333,27 @@ def test_resolve_index_entry_returns_none_when_nothing_matches():
|
||||
assert box._resolve_index_entry(None, None) is None
|
||||
|
||||
|
||||
def _reconcile_box(index_hashes, store_hashes):
|
||||
box = _seam_box()
|
||||
box._store.read_only = False
|
||||
box._store.list_all.return_value = list(store_hashes)
|
||||
index = ToolIndex()
|
||||
for position, source_hash in enumerate(index_hashes):
|
||||
index.add_entry(_entry(id=f"tool_{position}", source_hash=source_hash))
|
||||
box._store.load_index.return_value = index
|
||||
return box
|
||||
|
||||
|
||||
def test_index_reconcile_ignores_orphaned_store_rows():
|
||||
box = _reconcile_box(index_hashes=["a", "b"], store_hashes=["a", "b", "orphaned"])
|
||||
assert box._writable_store_index_needs_population() is False
|
||||
|
||||
|
||||
def test_index_reconcile_repopulates_on_dangling_index_reference():
|
||||
box = _reconcile_box(index_hashes=["a", "b"], store_hashes=["a"])
|
||||
assert box._writable_store_index_needs_population() is True
|
||||
|
||||
|
||||
def test_create_tool_populates_adhoc_for_existing_file(tmp_path, monkeypatch):
|
||||
# Shed installs load cloned tools during metadata generation, before
|
||||
# any conf is persisted — a miss for an on-disk file populates that
|
||||
|
||||
Reference in New Issue
Block a user