Skip to content

Improve asyncio integration error handling. #4129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 12, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 53 additions & 16 deletions sentry_sdk/integrations/asyncio.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import sys
import signal

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
from asyncio.tasks import Task
except ImportError:
raise DidNotEnable("asyncio not available")

from typing import TYPE_CHECKING
from typing import cast, TYPE_CHECKING

if TYPE_CHECKING:
from typing import Any
Expand All @@ -36,10 +37,26 @@ 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():
# 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 "
"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

Expand All @@ -56,27 +73,47 @@ 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:
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

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():
Expand Down
Loading