X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
1b01a91
Add `f_generator` property to Python frame objects
1st1 Sep 24, 2024
0fc5511
Working implementation of `asyncio.capture_call_stack()`
1st1 Sep 25, 2024
1d20a51
Address Guido's comments
1st1 Sep 26, 2024
c8be18e
Add a comment for capture_call_stack()
1st1 Sep 26, 2024
abf2cb9
Add a couple more tests
1st1 Sep 26, 2024
20ceab7
Remove setter for C impl of Task._awaited_by
1st1 Sep 26, 2024
72d9321
Intoduce cr_task
1st1 Sep 26, 2024
c9475f6
Unbreak shield() and gather()
1st1 Sep 26, 2024
e1099e9
Add convinience fields to C Task/Future for profilers
1st1 Sep 26, 2024
817f88b
Fix ups
1st1 Sep 27, 2024
54386ac
Add basic docs
1st1 Sep 27, 2024
98434f0
Implement, test, and document asyncio.print_call_stack()
1st1 Sep 28, 2024
485c166
Reorder a few things
1st1 Sep 28, 2024
8802be7
Add a test to exercise asyncio stack traces in out-of-process profilers
pablogsal Sep 29, 2024
1ddc9cf
Refactor the section-generated code into an evil macro
pablogsal Sep 30, 2024
bc9beb8
Rename "capture_call_stack()" et al to "capture_call_graph()"
1st1 Oct 2, 2024
fd141d4
Add NEWS
1st1 Oct 2, 2024
2d72f24
Per mpage's suggestion: use traceback style formatring for frames
1st1 Oct 2, 2024
391defa
mpage feedback: fix typo
1st1 Oct 2, 2024
d6357fd
Per mpage suggestion: get rid of CoroutineCallGraphEntry
1st1 Oct 2, 2024
bb3b6df
Strip sloppy white space!
1st1 Oct 2, 2024
54c99ec
Fix sloppy set iteration!
1st1 Oct 2, 2024
c1a4f09
Fix a sloppy test!
1st1 Oct 2, 2024
027d522
Run 'make regen-all'
1st1 Oct 2, 2024
c2d5ec6
Add a what's new entry
1st1 Oct 2, 2024
08d09eb
Fix (hopefully) a compiler warning
1st1 Oct 2, 2024
fe3113b
Fix sloppy what's new!
1st1 Oct 2, 2024
18ec26d
Fix CI complaining about suspicious global in C
1st1 Oct 2, 2024
e4cc462
Add a test for depth=2
1st1 Oct 2, 2024
d5cdc36
Add critical sections for free threaded builds
pablogsal Oct 2, 2024
83606f2
Add more slopy tests
pablogsal Oct 2, 2024
5edac41
Apply suggestions from code review
1st1 Oct 16, 2024
8dc6d34
Apply suggestions from code review
1st1 Oct 16, 2024
30884ea
Update Modules/_asynciomodule.c
1st1 Oct 16, 2024
1317658
Update Modules/_asynciomodule.c
1st1 Oct 16, 2024
81b0a31
Use dataclasses instead of tuples for asyncio.stack
1st1 Oct 16, 2024
258ce3d
Fix sloppy whitespace!
1st1 Oct 16, 2024
b9ecefb
Remove critical sections for now
pablogsal Oct 16, 2024
b77dcb0
Eplain the weird macro
1st1 Oct 16, 2024
8867946
Fix test
pablogsal Oct 16, 2024
87d2524
Merge remote-tracking branch 'upstream/main' into stack
pablogsal Oct 16, 2024
b47bef1
Add a docs clarification
1st1 Oct 16, 2024
230b7ec
Fix typing in asyncio.stack
ambv Oct 16, 2024
b1d6158
Fix memory leak
pablogsal Oct 17, 2024
ac51364
Address comments from Kumar's review
ambv Oct 17, 2024
c7e59eb
Fix rare Mach-O linker bug when .bss is uninitialized data
ambv Oct 22, 2024
9eba5e1
Merge branch 'main' into pr-124640
ambv Oct 22, 2024
59121f6
you saw nothing, okay
ambv Oct 22, 2024
f8f48f0
Update Lib/asyncio/futures.py
1st1 Oct 23, 2024
74c5ad1
Remove unnecessary imports from tests
ambv Oct 30, 2024
067c043
Remove cr_task/gi_task
ambv Oct 31, 2024
9f04911
Fix refleaks
ambv Nov 13, 2024
0774805
Rename "asyncio.stack" to "asyncio.graph"
ambv Nov 13, 2024
3048493
Allow returning a string with `format_call_graph`
ambv Nov 13, 2024
1f42873
Add test for eager task factory support
ambv Nov 13, 2024
7799391
Merge branch 'main' into pr-124640
ambv Nov 13, 2024
03ed5c1
Make _asyncio_awaited_by a frozenset in the Python version as well pe…
ambv Nov 13, 2024
21f9ea9
Address picnixz' feedback
1st1 Nov 23, 2024
8a43dfa
Blacken the C/Py test code by hand, by request from picnixz
1st1 Nov 23, 2024
b3fae68
More style fixes per picnixz' suggestions
1st1 Nov 23, 2024
d0aedf0
Address Kumar's latest comment
1st1 Nov 23, 2024
df0032a
diff cleanup
kumaraditya303 Nov 23, 2024
0ce241b
change dataclasses to use tuples
kumaraditya303 Nov 23, 2024
8f126f6
doc fixes
kumaraditya303 Nov 23, 2024
966d84e
remove reduntant headers include and add my name to whatsnew
kumaraditya303 Nov 23, 2024
f56468a
improve comment
kumaraditya303 Nov 23, 2024
404b88a
fix leave task
kumaraditya303 Nov 23, 2024
911fed8
fix external inspection on linux
kumaraditya303 Nov 23, 2024
ab511a4
minor format
kumaraditya303 Nov 23, 2024
c3c685a
try to fix docs build
kumaraditya303 Nov 23, 2024
785adeb
Update Doc/whatsnew/3.14.rst
kumaraditya303 Nov 23, 2024
a577328
Match indentation with _asynciomodule.c after ab511a4f67ffd88f7709c1b…
ambv Nov 23, 2024
064129a
Improve comments after f56468af8b3789d0e20be30715c20ac1bb367641
ambv Nov 23, 2024
ce332d9
Restore the Oxford comma
ambv Nov 23, 2024
d6d943f
Address remaining suggestions of Andrew Svetlov
ambv Nov 23, 2024
703ff46
Fix gather()
1st1 Nov 23, 2024
e867863
Replace lambda by closure
pablogsal Nov 26, 2024
9cb5b29
Let’s not emphasize *asyncio*
1st1 Nov 26, 2024
61b2b7b
Apply suggestions from Irit's review
ambv Nov 26, 2024
9533ab9
Address remaining review by Irit
ambv Nov 26, 2024
596191d
Test pure-Python and C-accelerated versions of future_add_to/future_d…
ambv Nov 26, 2024
ad9152e
fix blooper
ambv Nov 26, 2024
066bf21
Fix another blooper
ambv Nov 26, 2024
4caeec4
Fix crash
pablogsal Jan 21, 2025
38f061d
Merge remote-tracking branch 'upstream/main' into stack
pablogsal Jan 21, 2025
a8dd667
use private method for the policy
pablogsal Jan 22, 2025
cf8f5e5
Avoid importing typing
ambv Jan 22, 2025
eda9c7c
Remove debug printing from test_asyncio.test_graph
ambv Jan 22, 2025
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
Prev Previous commit
Next Next commit
Rename "capture_call_stack()" et al to "capture_call_graph()"
  • Loading branch information
1st1 committed Oct 2, 2024
commit bc9beb85aaa7c0631227a5dede062f6f5fdbfe12
30 changes: 15 additions & 15 deletions Doc/library/asyncio-stack.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ a suspended *future*.
.. versionadded:: 3.14


.. function:: print_call_stack(*, future=None, file=None)
.. function:: print_call_graph(*, future=None, file=None)

Print the async call stack for the current task or the provided
Print the async call graph for the current task or the provided
:class:`Task` or :class:`Future`.

The function recieves an optional keyword-only *future* argument.
Expand All @@ -38,7 +38,7 @@ a suspended *future*.
import asyncio

async def test():
asyncio.print_call_stack()
asyncio.print_call_graph()

async def main():
async with asyncio.TaskGroup() as g:
Expand All @@ -50,7 +50,7 @@ a suspended *future*.

* Task(name='Task-2', id=0x105038fe0)
+ Call stack:
| * print_call_stack()
| * print_call_graph()
| asyncio/stack.py:231
| * async test()
| test.py:4
Expand All @@ -72,37 +72,37 @@ a suspended *future*.
...

buf = io.StringIO()
asyncio.print_call_stack(file=buf)
asyncio.print_call_graph(file=buf)
output = buf.getvalue()


.. function:: capture_call_stack(*, future=None)
.. function:: capture_call_graph(*, future=None)

Capture the async call stack for the current task or the provided
Capture the async call graph for the current task or the provided
:class:`Task` or :class:`Future`.

The function recieves an optional keyword-only *future* argument.
If not passed, the current running task will be used. If there's no
current task, the function returns ``None``.

Returns a ``FutureCallStack`` named tuple:
Returns a ``FutureCallGraph`` named tuple:

* ``FutureCallStack(future, call_stack, awaited_by)``
* ``FutureCallGraph(future, call_graph, awaited_by)``

Where 'future' is a reference to a *Future* or a *Task*
(or their subclasses.)

``call_stack`` is a list of ``FrameCallStackEntry`` and
``CoroutineCallStackEntry`` objects (more on them below.)
``call_graph`` is a list of ``FrameCallGraphEntry`` and
``CoroutineCallGraphEntry`` objects (more on them below.)

``awaited_by`` is a list of ``FutureCallStack`` tuples.
``awaited_by`` is a list of ``FutureCallGraph`` tuples.

* ``FrameCallStackEntry(frame)``
* ``FrameCallGraphEntry(frame)``

Where ``frame`` is a frame object of a regular Python function
in the call stack.

* ``CoroutineCallStackEntry(coroutine)``
* ``CoroutineCallGraphEntry(coroutine)``

Where ``coroutine`` is a coroutine object of an awaiting coroutine
or asyncronous generator.
Expand All @@ -111,7 +111,7 @@ a suspended *future*.
Low level utility functions
===========================

To introspect an async call stack asyncio requires cooperation from
To introspect an async call graph asyncio requires cooperation from
control flow structures, such as :func:`shield` or :class:`TaskGroup`.
Any time an intermediate ``Future`` object with low-level APIs like
:meth:`Future.add_done_callback() <asyncio.Future.add_done_callback>` is
Expand Down
68 changes: 34 additions & 34 deletions Lib/asyncio/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
from . import tasks

__all__ = (
'capture_call_stack',
'print_call_stack',
'FrameCallStackEntry',
'CoroutineCallStackEntry',
'FutureCallStack',
'capture_call_graph',
'print_call_graph',
'FrameCallGraphEntry',
'CoroutineCallGraphEntry',
'FutureCallGraph',
)

# Sadly, we can't re-use the traceback's module datastructures as those
Expand All @@ -24,21 +24,21 @@
# top level asyncio namespace, and want to avoid future name clashes.


class FrameCallStackEntry(typing.NamedTuple):
class FrameCallGraphEntry(typing.NamedTuple):
frame: types.FrameType


class CoroutineCallStackEntry(typing.NamedTuple):
class CoroutineCallGraphEntry(typing.NamedTuple):
coroutine: types.CoroutineType


class FutureCallStack(typing.NamedTuple):
class FutureCallGraph(typing.NamedTuple):
future: futures.Future
call_stack: list[FrameCallStackEntry | CoroutineCallStackEntry]
awaited_by: list[FutureCallStack]
call_graph: list[FrameCallGraphEntry | CoroutineCallGraphEntry]
awaited_by: list[FutureCallGraph]


def _build_stack_for_future(future: any) -> FutureCallStack:
def _build_stack_for_future(future: any) -> FutureCallGraph:
if not isinstance(future, futures.Future):
raise TypeError(
f"{future!r} object does not appear to be compatible "
Expand All @@ -52,17 +52,17 @@ def _build_stack_for_future(future: any) -> FutureCallStack:
else:
coro = get_coro()

st: list[CoroutineCallStackEntry] = []
awaited_by: list[FutureCallStack] = []
st: list[CoroutineCallGraphEntry] = []
awaited_by: list[FutureCallGraph] = []

while coro is not None:
if hasattr(coro, 'cr_await'):
# A native coroutine or duck-type compatible iterator
st.append(CoroutineCallStackEntry(coro))
st.append(CoroutineCallGraphEntry(coro))
coro = coro.cr_await
elif hasattr(coro, 'ag_await'):
# A native async generator or duck-type compatible iterator
st.append(CoroutineCallStackEntry(coro))
st.append(CoroutineCallGraphEntry(coro))
coro = coro.ag_await
else:
break
Expand All @@ -72,30 +72,30 @@ def _build_stack_for_future(future: any) -> FutureCallStack:
awaited_by.append(_build_stack_for_future(parent))

st.reverse()
return FutureCallStack(future, st, awaited_by)
return FutureCallGraph(future, st, awaited_by)


def capture_call_stack(*, future: any = None) -> FutureCallStack | None:
def capture_call_graph(*, future: any = None) -> FutureCallGraph | None:
"""Capture async call stack for the current task or the provided Future.

The stack is represented with three data structures:

* FutureCallStack(future, call_stack, awaited_by)
* FutureCallGraph(future, call_graph, awaited_by)

Where 'future' is a reference to an asyncio.Future or asyncio.Task
(or their subclasses.)

'call_stack' is a list of FrameCallStackEntry and CoroutineCallStackEntry
'call_graph' is a list of FrameCallGraphEntry and CoroutineCallGraphEntry
objects (more on them below.)

'awaited_by' is a list of FutureCallStack objects.
'awaited_by' is a list of FutureCallGraph objects.

* FrameCallStackEntry(frame)
* FrameCallGraphEntry(frame)

Where 'frame' is a frame object of a regular Python function
in the call stack.

* CoroutineCallStackEntry(coroutine)
* CoroutineCallGraphEntry(coroutine)

Where 'coroutine' is a coroutine object of an awaiting coroutine
or asyncronous generator.
Expand All @@ -117,7 +117,7 @@ def capture_call_stack(*, future: any = None) -> FutureCallStack | None:
else:
if loop is None:
raise RuntimeError(
'capture_call_stack() is called outside of a running '
'capture_call_graph() is called outside of a running '
'event loop and no *future* to introspect was provided')
future = tasks.current_task(loop=loop)

Expand All @@ -133,21 +133,21 @@ def capture_call_stack(*, future: any = None) -> FutureCallStack | None:
f"with asyncio.Future"
)

call_stack: list[FrameCallStackEntry | CoroutineCallStackEntry] = []
call_graph: list[FrameCallGraphEntry | CoroutineCallGraphEntry] = []

f = sys._getframe(1)
try:
while f is not None:
is_async = f.f_generator is not None

if is_async:
call_stack.append(CoroutineCallStackEntry(f.f_generator))
call_graph.append(CoroutineCallGraphEntry(f.f_generator))
if f.f_back is not None and f.f_back.f_generator is None:
# We've reached the bottom of the coroutine stack, which
# must be the Task that runs it.
break
else:
call_stack.append(FrameCallStackEntry(f))
call_graph.append(FrameCallGraphEntry(f))

f = f.f_back
finally:
Expand All @@ -158,13 +158,13 @@ def capture_call_stack(*, future: any = None) -> FutureCallStack | None:
for parent in fut_waiters:
awaited_by.append(_build_stack_for_future(parent))

return FutureCallStack(future, call_stack, awaited_by)
return FutureCallGraph(future, call_graph, awaited_by)


def print_call_stack(*, future: any = None, file=None) -> None:
def print_call_graph(*, future: any = None, file=None) -> None:
"""Print async call stack for the current task or the provided Future."""

def render_level(st: FutureCallStack, buf: list[str], level: int):
def render_level(st: FutureCallGraph, buf: list[str], level: int):
def add_line(line: str):
buf.append(level * ' ' + line)

Expand All @@ -177,12 +177,12 @@ def add_line(line: str):
f'* Future(id=0x{id(st.future):x})'
)

if st.call_stack:
if st.call_graph:
add_line(
f' + Call stack:'
)
for ste in st.call_stack:
if isinstance(ste, FrameCallStackEntry):
for ste in st.call_graph:
if isinstance(ste, FrameCallGraphEntry):
f = ste.frame
add_line(
f' | * {f.f_code.co_qualname}()'
Expand All @@ -191,7 +191,7 @@ def add_line(line: str):
f' | {f.f_code.co_filename}:{f.f_lineno}'
)
else:
assert isinstance(ste, CoroutineCallStackEntry)
assert isinstance(ste, CoroutineCallGraphEntry)
c = ste.coroutine

try:
Expand Down Expand Up @@ -222,7 +222,7 @@ def add_line(line: str):
for fut in st.awaited_by:
render_level(fut, buf, level + 1)

stack = capture_call_stack(future=future)
stack = capture_call_graph(future=future)
if stack is None:
return

Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_asyncio/test_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ def walk(s):
[
(
f"s {entry.frame.f_code.co_name}"
if isinstance(entry, asyncio.FrameCallStackEntry) else
if isinstance(entry, asyncio.FrameCallGraphEntry) else
(
f"a {entry.coroutine.cr_code.co_name}"
if hasattr(entry.coroutine, 'cr_code') else
f"ag {entry.coroutine.ag_code.co_name}"
)
) for entry in s.call_stack
) for entry in s.call_graph
]
)

Expand All @@ -39,9 +39,9 @@ def walk(s):
return ret

buf = io.StringIO()
asyncio.print_call_stack(future=fut, file=buf)
asyncio.print_call_graph(future=fut, file=buf)

stack = asyncio.capture_call_stack(future=fut)
stack = asyncio.capture_call_graph(future=fut)
return walk(stack), buf.getvalue()


Expand Down
X Tutup