From 8a15acc7e65dfa650c2586c9cdf371bac1d40c66 Mon Sep 17 00:00:00 2001 From: agu2347 <94227848+agu2347@users.noreply.github.com> Date: Sat, 15 Aug 2026 12:31:24 +0530 Subject: [PATCH] fix(utils): handle non-ImportError exceptions when checking eventlet contextvars _is_contextvars_broken() only caught ImportError when trying to import greenlet/eventlet to detect monkeypatching. Depending on which combination of eventlet, greenlet, and other monkeypatched modules (e.g. dnspython, httpcore) happen to be installed, importing these can raise other exceptions (e.g. AttributeError), crashing the SDK on import. Also clean up any partially-imported modules left behind in sys.modules when such an import fails, so a subsequent import of the same module doesn't silently reuse the broken cached module. Fixes #7202 --- sentry_sdk/utils.py | 13 +++++++++++ tests/utils/test_contextvars.py | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 45e5376d1c..4d83397dbf 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -1348,6 +1348,7 @@ def _is_contextvars_broken() -> bool: except ImportError: pass + modules_before_import = set(sys.modules.keys()) try: import greenlet from eventlet.patcher import is_monkey_patched # type: ignore @@ -1364,6 +1365,18 @@ def _is_contextvars_broken() -> bool: return True except ImportError: pass + except Exception: + # Importing eventlet/greenlet can fail in unexpected ways depending on + # which combination of eventlet, greenlet, and other monkeypatched + # modules (e.g. dnspython, httpcore) happen to be installed. When that + # happens, the partially-imported module can be left behind in + # sys.modules, which would make subsequent imports of it silently + # reuse the broken module instead of retrying the import. Clean up + # any modules that got added during the failed import attempt. + # See https://github.com/getsentry/sentry-python/issues/7202. + modules_after_import = set(sys.modules.keys()) + for module_name in modules_after_import - modules_before_import: + del sys.modules[module_name] return False diff --git a/tests/utils/test_contextvars.py b/tests/utils/test_contextvars.py index 50881314c1..f18f915311 100644 --- a/tests/utils/test_contextvars.py +++ b/tests/utils/test_contextvars.py @@ -1,5 +1,8 @@ +import builtins import random +import sys import time +import types from unittest import mock import pytest @@ -50,3 +53,38 @@ def test_leaks(maybe_monkeypatched_threading): @mock.patch("sentry_sdk.utils._is_contextvars_broken", return_value=True) def test_leaks_when_is_contextvars_broken_is_false(maybe_monkeypatched_threading): _run_contextvar_threaded_test() + + +def test_is_contextvars_broken_survives_eventlet_attributeerror(monkeypatch): + """ + Regression test for https://github.com/getsentry/sentry-python/issues/7202 + + Importing eventlet/greenlet can raise errors other than ImportError + depending on which combination of eventlet, greenlet, and other + monkeypatched modules (e.g. dnspython, httpcore) happen to be installed. + _is_contextvars_broken() should not crash in that case, and it should not + leave a broken partially-imported module behind in sys.modules. + """ + from sentry_sdk import utils + + monkeypatch.delitem(sys.modules, "greenlet", raising=False) + monkeypatch.delitem(sys.modules, "eventlet", raising=False) + monkeypatch.delitem(sys.modules, "eventlet.patcher", raising=False) + + real_import = builtins.__import__ + + def fake_import(name, *args, **kwargs): + if name == "greenlet": + sys.modules["greenlet"] = types.ModuleType("greenlet") + raise AttributeError("module 'dns.rdtypes' has no attribute 'ANY'") + return real_import(name, *args, **kwargs) + + with mock.patch("builtins.__import__", side_effect=fake_import): + with mock.patch( + "gevent.monkey.is_object_patched", return_value=False, create=True + ): + with mock.patch.dict(sys.modules, {"gevent": None}, clear=False): + result = utils._is_contextvars_broken() + + assert result is False + assert "greenlet" not in sys.modules