From 202b0f44da9d27dbd94d8016967e4652e2d4df8d Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 20 Aug 2026 12:17:20 +0100 Subject: [PATCH] chore: handle potential after-import exceptions We handle potential exceptions raised during the execution of the after-import module watchdog hook. --- ddtrace/internal/module.py | 7 +++++-- tests/internal/test_module.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/ddtrace/internal/module.py b/ddtrace/internal/module.py index 576987aa975..73cee306aab 100644 --- a/ddtrace/internal/module.py +++ b/ddtrace/internal/module.py @@ -230,8 +230,11 @@ def call_back(self, module: ModuleType) -> None: # loader type module.register_loader_type(_ImportHookChainedLoader, module.DefaultProvider) - for callback in self.callbacks.values(): - callback(module) + for key, callback in self.callbacks.items(): + try: + callback(module) + except Exception: + log.exception("Exception ignored in after_import hook %r for module %s", key, module.__name__) def load_module(self, fullname: str) -> t.Optional[ModuleType]: if self.loader is None: diff --git a/tests/internal/test_module.py b/tests/internal/test_module.py index d13fb95f8a1..d1478907127 100644 --- a/tests/internal/test_module.py +++ b/tests/internal/test_module.py @@ -358,6 +358,40 @@ class Bob(BaseCollector): Alice.uninstall() +@pytest.mark.subprocess(err=None) +def test_module_watchdog_after_import_hook_isolation(): + # A failing after_import hook on one watchdog subclass must not prevent + # another watchdog subclass's after_import hook from running for the + # same import. + from ddtrace.internal.module import ModuleWatchdog + + class Failing(ModuleWatchdog): + def after_import(self, module): + super(Failing, self).after_import(module) + raise ValueError("boom") + + class Collector(ModuleWatchdog): + def __init__(self): + self.__modules__ = set() + super(Collector, self).__init__() + + def after_import(self, module): + self.__modules__.add(module.__name__) + return super(Collector, self).after_import(module) + + Failing.install() + Collector.install() + + c = Collector._instance + + import tests.submod.stuff # noqa:F401 + + assert c.__modules__ >= {"tests.submod.stuff"}, c.__modules__ + + Collector.uninstall() + Failing.uninstall() + + @pytest.mark.subprocess(out="ddtrace imported\naccessing lazy module\nlazy loaded\n") def test_module_watchdog_no_lazy_force_load(): """Test that the module watchdog does not force-load lazy modules.