Skip to content

Asyncio integration only instruments one Asyncio event loop #2333

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

Closed
szokeasaurusrex opened this issue Aug 31, 2023 · 3 comments
Closed

Asyncio integration only instruments one Asyncio event loop #2333

szokeasaurusrex opened this issue Aug 31, 2023 · 3 comments
Labels
Integration: Asyncio Triaged Has been looked at recently during old issue triage

Comments

@szokeasaurusrex
Copy link
Member

szokeasaurusrex commented Aug 31, 2023

How do you use Sentry?

Sentry Saas (sentry.io)

Version

1.30.0

Steps to Reproduce

To observe this error, we can add a print statement within the inner _coro_creating_hub_and_span async function (defined within _sentry_task_factory which is itself defined within patch_asyncio) in the file sentry_sdk/integrations/asyncio.py. This inner function is supposed to wrap every async call when the AsyncioIntegration is enabled, and so adding the print statement allows us to verify that asyncio has been correctly instrumented.

Next, we run the following sample script:

import asyncio
import time

import sentry_sdk
from sentry_sdk.integrations.asyncio import AsyncioIntegration


async def fib(n):
    if n == 0:
        return 0

    if n == 1:
        return 1

    time.sleep(0.05)

    async with asyncio.TaskGroup() as tg:
        fib1 = tg.create_task(fib(n - 1))
        fib2 = tg.create_task(fib(n - 2))

    return fib1.result() + fib2.result()


async def main():
    sentry_sdk.init(
        dsn="<dsn here>",
        traces_sample_rate=1.0,
        integrations=(AsyncioIntegration(),),
    )

    await runner()  # This call is instrumented


async def runner():
    with sentry_sdk.start_transaction(op="test", name="Fibonacci"):
        result = await fib(5)

    print(result)


if __name__ == "__main__":
    print("initial call")
    asyncio.run(main())
    print("initial call has ended\n")

    print("second call")
    asyncio.run(runner())  # This call is NOT instrumented
    print("second call has ended\n")

Expected Result

Both calls to runner should be auto-instrumented by the SDK's asyncio integration.

Actual Result

The SDK only auto-instruments the first call to runner, which is made within the main function. The SDK fails to instrument the second call to runner, which is made outside the main function.

Likely Cause

This issue likely has the same cause as #2328; that is to say, this issue appears to be caused by the Sentry SDK only instrumenting the currently running asyncio event loop. asyncio.run() always creates a new event loop every time it is called, which is why the second call to runner fails.

Potential Solutions

  1. Don't fix, and close the issue
    Calling asyncio.run() multiple times, while technically possible, is likely not a common use case, and the Python docs recommend avoiding multiple calls to the function from the same thread. From the docs, "[asyncio.run()] should be used as a main entry point for asyncio programs, and should ideally only be called once." If we go this route, perhaps we should document only supporting a single asyncio.run() call as a known SDK limitation.
  2. Modify the SDK to instrument newly created event loops
    This solution would also likely solve the related issue SDK not instrumenting Asyncio calls when sentry_sdk.init called at app lifecycle's beginning, outside an async function #2328. And, even if calling asyncio.run() multiple times is not a common or even a recommended design pattern, it is possible to do and so we should support it.

If we go with second option, this issue could likely be resolved by instrumenting asyncio through the use of a custom event loop policy which instruments the SDK when a new event loop is created in addition to instrumenting the current event loop.

@dPeS
Copy link

dPeS commented Sep 26, 2023

+1 I'm eager to test sentry apm in ray/asyncio...

@antonpirker
Copy link
Member

A quick question @dPeS does ray with asyncion call asyncio.run() multiple times?

@antonpirker
Copy link
Member

Because having multiple event loops is not recommended (because after the first event loop is closed it could happen, that not everything is cleaned up and data is leaked to the second event loop)

We will not support this, so I am closing this issue.

@antonpirker antonpirker closed this as not planned Won't fix, can't repro, duplicate, stale Mar 4, 2025
antonpirker added a commit that referenced this issue Mar 12, 2025
Instrumenting asyncio projects can be confusing. Here are two
improvements:

- If users try to init the Sentry SDK outside of an async loop, a
warning message will now printed instructing them how to correctly call
init() in async envrionments. Including a link to the docs.

- During shutdown of Python unfinished async tasks emit an error `Task
was destroyed but it is pending!`. This happens if you use Sentry or
not. The error message is confusing and led people to believe the Sentry
instrumentation caused this problem. This is now remediated by
- The tasks is wrapped by Sentry, but we now **set the name of the
wrapped task to include the original** and (and a hint that is has been
wrapped by Sentry) to show that the original task is failing, not just
some Sentry task unknown to the user.
- When shutting down a **info message** is printed, informing that there
could be `Task was destroyed but it is pending!` but that those are OK
and not a problem with the users code or Sentry.


Before this PR the users saw this during shutdown:

```
Exception ignored in: <coroutine object patch_asyncio.<locals>._sentry_task_factory.<locals>._coro_creating_hub_and_span at 0x103ae84f0>
Traceback (most recent call last):
  File "/Users/antonpirker/code/sentry-python/sentry_sdk/integrations/asyncio.py", line 46, in _coro_creating_hub_and_span
    with sentry_sdk.isolation_scope():
  File "/Users/antonpirker/.pyenv/versions/3.12.3/lib/python3.12/contextlib.py", line 158, in __exit__
    self.gen.throw(value)
  File "/Users/antonpirker/code/sentry-python/sentry_sdk/scope.py", line 1732, in isolation_scope
    _current_scope.reset(current_token)
ValueError: <Token var=<ContextVar name='current_scope' default=None at 0x102a87f60> at 0x103b1cfc0> was created in a different Context
Task was destroyed but it is pending!
task: <Task cancelling name='Task-2' coro=<patch_asyncio.<locals>._sentry_task_factory.<locals>._coro_creating_hub_and_span() done, defined at /Users/antonpirker/code/sentry-python/sentry_sdk/integrations/asyncio.py:42> wait_for=<Future pending cb=[Task.task_wakeup()]> cb=[gather.<locals>._done_callback() at /Users/antonpirker/.pyenv/versions/3.12.3/lib/python3.12/asyncio/tasks.py:767]>
```

With this PR the users will see this during shutdown:
Note the INFO message on top and also the task name on the bottom.
```
[sentry] 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.
Exception ignored in: <coroutine object patch_asyncio.<locals>._sentry_task_factory.<locals>._task_with_sentry_span_creation at 0x1028fc4f0>
Traceback (most recent call last):
  File "/Users/antonpirker/code/sentry-python/sentry_sdk/integrations/asyncio.py", line 62, in _task_with_sentry_span_creation
    with sentry_sdk.isolation_scope():
  File "/Users/antonpirker/.pyenv/versions/3.12.3/lib/python3.12/contextlib.py", line 158, in __exit__
    self.gen.throw(value)
  File "/Users/antonpirker/code/sentry-python/sentry_sdk/scope.py", line 1732, in isolation_scope
    _current_scope.reset(current_token)
ValueError: <Token var=<ContextVar name='current_scope' default=None at 0x10193ff10> at 0x1029710c0> was created in a different Context
Task was destroyed but it is pending!
task: <Task cancelling name='long_running_task (Sentry-wrapped)' coro=<patch_asyncio.<locals>._sentry_task_factory.<locals>._task_with_sentry_span_creation() done, defined at /Users/antonpirker/code/sentry-python/sentry_sdk/integrations/asyncio.py:58> wait_for=<Future pending cb=[Task.task_wakeup()]> cb=[gather.<locals>._done_callback() at /Users/antonpirker/.pyenv/versions/3.12.3/lib/python3.12/asyncio/tasks.py:767]>
```

Fixes #2908

Improves #2333
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Integration: Asyncio Triaged Has been looked at recently during old issue triage
Projects
Archived in project
Development

No branches or pull requests

4 participants