-
Notifications
You must be signed in to change notification settings - Fork 557
Description
Async Stack Traces
Async Stack Traces are a problem in many languages, as often the calling frames are gone by the time an async task/ Promise finishes or exceptions get lost entirely. This issue details the behaviour of async stack traces in Python and discusses open issues.
Behaviour in Python
In Python, when async tasks/coroutines are properly awaited, stack traces and exceptions usually get logged properly, as they are passed through the different await calls. This is illustrated by the following example:
import asyncio
def main():
asyncio.run(foo())
async def foo():
await asyncio.sleep(0.100)
await bar()
async def bar():
await asyncio.sleep(0.200)
await blow_up()
async def blow_up():
await asyncio.sleep(0.300)
raise RuntimeError("nested async panic!")
if __name__ == "__main__":
main()
which outputs a proper stacktrace like this:
> Traceback (most recent call last):
> File "/Users/simon/code/sentry-python/sentry_sdk/stacktracetest.py", line 19, in <module>
> main()
> ~~~~^^
> File "/Users/simon/code/sentry-python/sentry_sdk/stacktracetest.py", line 4, in main
> asyncio.run(foo())
> ~~~~~~~~~~~^^^^^^^
> File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 195, in run
> return runner.run(main)
> ~~~~~~~~~~^^^^^^
> File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 118, in run
> return self._loop.run_until_complete(task)
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
> File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
> return future.result()
> ~~~~~~~~~~~~~^^
> File "/Users/simon/code/sentry-python/sentry_sdk/stacktracetest.py", line 8, in foo
> await bar()
> File "/Users/simon/code/sentry-python/sentry_sdk/stacktracetest.py", line 12, in bar
> await blow_up()
> File "/Users/simon/code/sentry-python/sentry_sdk/stacktracetest.py", line 16, in blow_up
> raise RuntimeError("nested async panic!")
> RuntimeError: nested async panic!
This makes asynchronous stacktraces in Python much less of a problem than e.g. Javascript.
However, when tasks run detached (are created, but not awaited), the calling frames may disappear and the exception will get lost. The following example:
import asyncio
async def boom():
await asyncio.sleep(0.01)
raise RuntimeError("detached task blew up")
async def caller_detached():
asyncio.create_task(boom())
await asyncio.sleep(0.02)
asyncio.run(caller_detaches())
results in this stacktrace:
Task exception was never retrieved
future: <Task finished name='Task-2' coro=<boom() done, defined at /Users/simon/code/sentry-python/sentry_sdk/stacktracetest.py:3> exception=RuntimeError('detached task blew up')>
Traceback (most recent call last):
File "/Users/simon/code/sentry-python/sentry_sdk/stacktracetest.py", line 5, in boom
raise RuntimeError("detached task blew up")
RuntimeError: detached task blew up
Notice that the calling task caller_detached
does not show up in the stacktrace, and an error "Task exception was never retrieved" gets output. To get the stacktrace including the calling source, debug mode can be enabled or a task factory can be set that snapshots the current stacktrace before a task is launched. Both of these solutions however come with a big overhead, as they create a snapshot of the stacktrace in every case, not just when there is an exception. To log the exception, either a custom exception handler can be registered or a done callback wrapping task.result() in try/except can be attached.
Of note however is that this behaviour of simply creating tasks seems to be discouraged by core python developers as it can mess with program control flow and there seems to be an intention to move more towards structured concurrency for async applications, which is also supported by newer Python additions such as the TaskGroup, which was introduced in Python 3.11 and is receiving further improvements to better fit asyncio in Python 3.14.
Sources and interesting reads:
https://www.draconianoverlord.com/2025/04/17/fixing-async-stack-traces.html/
https://peps.python.org/pep-3156/#exceptions
https://docs.python.org/3/library/asyncio-dev.html
https://docs.python.org/3/library/asyncio-eventloop.html
https://discuss.python.org/t/asyncio-tasks-and-exception-handling-recommended-idioms/23806
https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
https://peps.python.org/pep-0789/
https://docs.python.org/3/library/asyncio-task.html