Skip to content

Commit cfa97d3

Browse files
committed
Reorder a few things
1 parent 90a54d9 commit cfa97d3

File tree

2 files changed

+72
-22
lines changed

2 files changed

+72
-22
lines changed

Doc/library/asyncio-stack.rst

+58-11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,64 @@ a suspended *future*.
1818
.. versionadded:: 3.14
1919

2020

21+
.. function:: print_call_stack(*, future=None, file=None)
22+
23+
Print the async call stack for the current task or the provided
24+
:class:`Task` or :class:`Future`.
25+
26+
The function recieves an optional keyword-only *future* argument.
27+
If not passed, the current running task will be used. If there's no
28+
current task, the function returns ``None``.
29+
30+
If *file* is not specified the function will print to :data:`sys.stdout`.
31+
32+
**Example:**
33+
34+
The following Python code:
35+
36+
.. code-block:: python
37+
38+
import asyncio
39+
40+
async def test():
41+
asyncio.print_call_stack()
42+
43+
async def main():
44+
async with asyncio.TaskGroup() as g:
45+
g.create_task(test())
46+
47+
asyncio.run(main())
48+
49+
will print::
50+
51+
* Task(name='Task-2', id=0x105038fe0)
52+
+ Call stack:
53+
| * print_call_stack()
54+
| asyncio/stack.py:231
55+
| * async test()
56+
| test.py:4
57+
+ Awaited by:
58+
* Task(name='Task-1', id=0x1050a6060)
59+
+ Call stack:
60+
| * async TaskGroup.__aexit__()
61+
| asyncio/taskgroups.py:107
62+
| * async main()
63+
| test.py:7
64+
65+
For rendering the call stack to a string the following pattern
66+
should be used:
67+
68+
.. code-block:: python
69+
70+
import io
71+
72+
...
73+
74+
buf = io.StringIO()
75+
asyncio.print_call_stack(file=buf)
76+
output = buf.getvalue()
77+
78+
2179
.. function:: capture_call_stack(*, future=None)
2280

2381
Capture the async call stack for the current task or the provided
@@ -49,17 +107,6 @@ a suspended *future*.
49107
Where ``coroutine`` is a coroutine object of an awaiting coroutine
50108
or asyncronous generator.
51109

52-
.. function:: print_call_stack(*, future=None, file=None)
53-
54-
Print the async call stack for the current task or the provided
55-
:class:`Task` or :class:`Future`.
56-
57-
The function recieves an optional keyword-only *future* argument.
58-
If not passed, the current running task will be used. If there's no
59-
current task, the function returns ``None``.
60-
61-
If *file* is not specified the function will print to :data:`sys.stdout`.
62-
63110

64111
Low level utility functions
65112
===========================

Lib/asyncio/stack.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,7 @@ def capture_call_stack(*, future: any = None) -> FutureCallStack | None:
164164
def print_call_stack(*, future: any = None, file=None) -> None:
165165
"""Print async call stack for the current task or the provided Future."""
166166

167-
stack = capture_call_stack(future=future)
168-
if stack is None:
169-
return
170-
171-
buf = []
172-
173-
def render_level(st: FutureCallStack, level: int = 0):
167+
def render_level(st: FutureCallStack, buf: list[str], level: int):
174168
def add_line(line: str):
175169
buf.append(level * ' ' + line)
176170

@@ -226,9 +220,18 @@ def add_line(line: str):
226220
f' + Awaited by:'
227221
)
228222
for fut in st.awaited_by:
229-
render_level(fut, level + 1)
223+
render_level(fut, buf, level + 1)
230224

231-
render_level(stack)
232-
rendered = '\n'.join(buf)
225+
stack = capture_call_stack(future=future)
226+
if stack is None:
227+
return
233228

234-
print(rendered, file=file)
229+
try:
230+
buf = []
231+
render_level(stack, buf, 0)
232+
rendered = '\n'.join(buf)
233+
print(rendered, file=file)
234+
finally:
235+
# 'stack' has references to frames so we should
236+
# make sure it's GC'ed as soon as we don't need it.
237+
del stack

0 commit comments

Comments
 (0)