fix(scan): give the PARTITIONED graph a single-attempt path too

run_batch_once was wired on the disposable and sharded paths but not the partitioned one -- and a
multi-language repository, which every WordPress plugin is, builds through the partitioned graph. So
the portioner fell back to run_batch there, whose internal split-retry folded into the time it
measured: a portion read 956 s and collapsed the sizer, exactly as before the single-attempt fix.
The fix had no effect on the one subject it was written for.

PartitionGraph now has run_batch_once, dispatching to each sub-graph the same way run_batch does --
the routing is factored into one _dispatch method that takes the per-graph method name. Sub-graphs
are built through the disposable path and already carry run_batch_once.

The lesson, again: a capability added at one construction site is not added at the others. There are
three that build a queryable graph -- disposable, sharded, partitioned -- and the one that mattered
for the measurement was the one missed.

1,424 tests pass, nineteen skipped.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-22 23:33:23 +02:00
co-authored by Claude Opus 4.8
parent 0fef399869
commit 03011b8fe6
+14 -1
View File
@@ -91,6 +91,19 @@ class PartitionGraph:
return self.run_batch(kind, {"one": params}).get("one")
def run_batch(self, kind: str, requests: Mapping[str, Mapping[str, object]]) -> dict[str, list[object]]:
return self._dispatch(kind, requests, "run_batch")
def run_batch_once(self, kind: str, requests: Mapping[str, Mapping[str, object]]) -> dict[str, list[object]]:
"""A single attempt per partition, for the scan-level portioner that retries itself.
Without this a multi-language repository -- every WordPress plugin -- had no single-attempt path, so
the portioner fell back to `run_batch`, whose internal split-retry folded into the time it measured:
one portion read 956 s and collapsed the sizer. The routing is identical; only the per-graph method
called differs.
"""
return self._dispatch(kind, requests, "run_batch_once")
def _dispatch(self, kind: str, requests: Mapping[str, Mapping[str, object]], method: str) -> dict[str, list[object]]:
answers: dict[str, list[object]] = {}
languages = dict.fromkeys(self.path_languages.get(str(params.get("file", "")), "") for params in requests.values())
for language in languages:
@@ -102,7 +115,7 @@ class PartitionGraph:
selected = {rid: params for rid, params in requests.items() if self.path_languages.get(str(params.get("file", ""))) == language}
if not selected:
continue
batch = getattr(graph, "run_batch", None)
batch = getattr(graph, method, None) or getattr(graph, "run_batch", None)
raw = batch(kind, selected) if callable(batch) else {rid: graph.run(kind, params) for rid, params in selected.items()}
if not isinstance(raw, Mapping):
continue