From f807e31c928dcd4354865d8a3965e0985d5c7bcb Mon Sep 17 00:00:00 2001 From: Arjun Sharma Date: Tue, 13 Oct 2026 19:29:40 +0000 Subject: [PATCH] lightspeed survey: copy sys.modules before the extension scan Reading a module attribute in the loop can import another module, which raised 'dictionary changed size during iteration' and left image builds with no benchmarks. --- asv/contrib/lightspeed/survey.py | 2 +- test/test_lightspeed_survey.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/asv/contrib/lightspeed/survey.py b/asv/contrib/lightspeed/survey.py index b4cfda2..db6ec81 100644 --- a/asv/contrib/lightspeed/survey.py +++ b/asv/contrib/lightspeed/survey.py @@ -74,7 +74,7 @@ def _add_extension_deps( """ # Collect packages that have extension modules, deduplicate scan dirs seen_dirs: Set[str] = set() - for mod in sys.modules.values(): + for mod in list(sys.modules.values()): if mod is None: continue mod_file = getattr(mod, "__file__", None) or "" diff --git a/test/test_lightspeed_survey.py b/test/test_lightspeed_survey.py index a7efad1..8912fa8 100644 --- a/test/test_lightspeed_survey.py +++ b/test/test_lightspeed_survey.py @@ -54,3 +54,18 @@ def time_child_import(): assert ok, reason assert "child.py" in {p.split("/pkg/")[-1] for p in deps} assert dict(os.environ) == env_before + + +def test_extension_scan_survives_import_during_scan(tmp_path, monkeypatch): + from types import ModuleType + + from asv.contrib.lightspeed.survey import _add_extension_deps + + class Lazy(ModuleType): + def __getattr__(self, name): + sys.modules.setdefault("lazy_loaded_child", ModuleType("lazy_loaded_child")) + raise AttributeError(name) + + monkeypatch.setitem(sys.modules, "lazy_parent", Lazy("lazy_parent")) + monkeypatch.delitem(sys.modules, "lazy_loaded_child", raising=False) + _add_extension_deps({}, str(tmp_path))