From 7b005e1be3de087199d87b3ac6ce7e021f33d301 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 12 Mar 2025 10:15:34 +0100 Subject: [PATCH 1/5] Emit warning when AsyncioIntegration is setup incorrectly --- sentry_sdk/integrations/asyncio.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py index 7021d7fceb..ee95abeed1 100644 --- a/sentry_sdk/integrations/asyncio.py +++ b/sentry_sdk/integrations/asyncio.py @@ -3,7 +3,7 @@ import sentry_sdk from sentry_sdk.consts import OP from sentry_sdk.integrations import Integration, DidNotEnable -from sentry_sdk.utils import event_from_exception, reraise +from sentry_sdk.utils import event_from_exception, logger, reraise try: import asyncio @@ -74,9 +74,15 @@ async def _coro_creating_hub_and_span(): return task loop.set_task_factory(_sentry_task_factory) # type: ignore + except RuntimeError: # When there is no running loop, we have nothing to patch. - pass + logger.warning( + "There is no running asyncio loop so there is nothing Sentry can patch. " + "Please make sure you call sentry_sdk.init() within a running " + "asyncio loop for the AsyncioIntegration to work. " + "See https://docs.sentry.io/platforms/python/integrations/asyncio/" + ) def _capture_exception(): From ef0c412d9a0fe2f096d378035067d4be312d3695 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 12 Mar 2025 11:38:12 +0100 Subject: [PATCH 2/5] Inform users about what is hapenning during shutdown. --- sentry_sdk/integrations/asyncio.py | 54 +++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py index ee95abeed1..fcbf40abc1 100644 --- a/sentry_sdk/integrations/asyncio.py +++ b/sentry_sdk/integrations/asyncio.py @@ -1,4 +1,5 @@ import sys +import signal import sentry_sdk from sentry_sdk.consts import OP @@ -36,10 +37,25 @@ def patch_asyncio(): loop = asyncio.get_running_loop() orig_task_factory = loop.get_task_factory() + # Add a shutdown handler to log a helpful message + def shutdown_handler(): + logger.info( + "AsyncIO is shutting down. If you see 'Task was destroyed but it is pending!' " + "errors with '_task_with_sentry_span_creation', these are normal during shutdown " + "and not a problem with your code or Sentry." + ) + + try: + loop.add_signal_handler(signal.SIGINT, shutdown_handler) + loop.add_signal_handler(signal.SIGTERM, shutdown_handler) + except (NotImplementedError, AttributeError): + # Signal handlers might not be supported on all platforms + pass + def _sentry_task_factory(loop, coro, **kwargs): # type: (asyncio.AbstractEventLoop, Coroutine[Any, Any, Any], Any) -> asyncio.Future[Any] - async def _coro_creating_hub_and_span(): + async def _task_with_sentry_span_creation(): # type: () -> Any result = None @@ -56,20 +72,32 @@ async def _coro_creating_hub_and_span(): return result + task = None + # Trying to use user set task factory (if there is one) if orig_task_factory: - return orig_task_factory(loop, _coro_creating_hub_and_span(), **kwargs) - - # The default task factory in `asyncio` does not have its own function - # but is just a couple of lines in `asyncio.base_events.create_task()` - # Those lines are copied here. - - # WARNING: - # If the default behavior of the task creation in asyncio changes, - # this will break! - task = Task(_coro_creating_hub_and_span(), loop=loop, **kwargs) - if task._source_traceback: # type: ignore - del task._source_traceback[-1] # type: ignore + task = orig_task_factory( + loop, _task_with_sentry_span_creation(), **kwargs + ) + + if task is None: + # The default task factory in `asyncio` does not have its own function + # but is just a couple of lines in `asyncio.base_events.create_task()` + # Those lines are copied here. + + # WARNING: + # If the default behavior of the task creation in asyncio changes, + # this will break! + task = Task(_task_with_sentry_span_creation(), loop=loop, **kwargs) + if task._source_traceback: # type: ignore + del task._source_traceback[-1] # type: ignore + + # Set the task name to include the original coroutine's name + try: + task.set_name(f"{get_name(coro)} (Sentry-wrapped)") + except AttributeError: + # set_name might not be available in all Python versions + pass return task From 36f94c2943e26c2f504a3826a319e083685e76a1 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 12 Mar 2025 12:49:02 +0100 Subject: [PATCH 3/5] make mypy happy --- sentry_sdk/integrations/asyncio.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py index fcbf40abc1..a9f76d3cdc 100644 --- a/sentry_sdk/integrations/asyncio.py +++ b/sentry_sdk/integrations/asyncio.py @@ -12,7 +12,7 @@ except ImportError: raise DidNotEnable("asyncio not available") -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast if TYPE_CHECKING: from typing import Any @@ -39,6 +39,7 @@ def patch_asyncio(): # Add a shutdown handler to log a helpful message def shutdown_handler(): + # type: () -> None logger.info( "AsyncIO is shutting down. If you see 'Task was destroyed but it is pending!' " "errors with '_task_with_sentry_span_creation', these are normal during shutdown " @@ -94,7 +95,9 @@ async def _task_with_sentry_span_creation(): # Set the task name to include the original coroutine's name try: - task.set_name(f"{get_name(coro)} (Sentry-wrapped)") + cast(asyncio.Task[Any], task).set_name( + f"{get_name(coro)} (Sentry-wrapped)" + ) except AttributeError: # set_name might not be available in all Python versions pass From bc389c14d53ebd7ee638aa4d7d5193eef9d663fe Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 12 Mar 2025 13:02:44 +0100 Subject: [PATCH 4/5] linting --- sentry_sdk/integrations/asyncio.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py index a9f76d3cdc..349dc12a84 100644 --- a/sentry_sdk/integrations/asyncio.py +++ b/sentry_sdk/integrations/asyncio.py @@ -12,10 +12,9 @@ except ImportError: raise DidNotEnable("asyncio not available") -from typing import TYPE_CHECKING, cast +from typing import Any, cast, TYPE_CHECKING if TYPE_CHECKING: - from typing import Any from collections.abc import Coroutine from sentry_sdk._types import ExcInfo From 4fe2bcdf2db83f2a4505419c1e2895ca961ac335 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 12 Mar 2025 13:23:30 +0100 Subject: [PATCH 5/5] linting --- sentry_sdk/integrations/asyncio.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py index 349dc12a84..9326c16e9a 100644 --- a/sentry_sdk/integrations/asyncio.py +++ b/sentry_sdk/integrations/asyncio.py @@ -12,9 +12,10 @@ except ImportError: raise DidNotEnable("asyncio not available") -from typing import Any, cast, TYPE_CHECKING +from typing import cast, TYPE_CHECKING if TYPE_CHECKING: + from typing import Any from collections.abc import Coroutine from sentry_sdk._types import ExcInfo @@ -94,7 +95,7 @@ async def _task_with_sentry_span_creation(): # Set the task name to include the original coroutine's name try: - cast(asyncio.Task[Any], task).set_name( + cast("asyncio.Task[Any]", task).set_name( f"{get_name(coro)} (Sentry-wrapped)" ) except AttributeError: