|
| 1 | +.. currentmodule:: asyncio |
| 2 | + |
| 3 | + |
| 4 | +.. _asyncio-graph: |
| 5 | + |
| 6 | +======================== |
| 7 | +Call Graph Introspection |
| 8 | +======================== |
| 9 | + |
| 10 | +**Source code:** :source:`Lib/asyncio/graph.py` |
| 11 | + |
| 12 | +------------------------------------- |
| 13 | + |
| 14 | +asyncio has powerful runtime call graph introspection utilities |
| 15 | +to trace the entire call graph of a running *coroutine* or *task*, or |
| 16 | +a suspended *future*. These utilities and the underlying machinery |
| 17 | +can be used from within a Python program or by external profilers |
| 18 | +and debuggers. |
| 19 | + |
| 20 | +.. versionadded:: next |
| 21 | + |
| 22 | + |
| 23 | +.. function:: print_call_graph(future=None, /, *, file=None, depth=1, limit=None) |
| 24 | + |
| 25 | + Print the async call graph for the current task or the provided |
| 26 | + :class:`Task` or :class:`Future`. |
| 27 | + |
| 28 | + This function prints entries starting from the top frame and going |
| 29 | + down towards the invocation point. |
| 30 | + |
| 31 | + The function receives an optional *future* argument. |
| 32 | + If not passed, the current running task will be used. |
| 33 | + |
| 34 | + If the function is called on *the current task*, the optional |
| 35 | + keyword-only *depth* argument can be used to skip the specified |
| 36 | + number of frames from top of the stack. |
| 37 | + |
| 38 | + If the optional keyword-only *limit* argument is provided, each call stack |
| 39 | + in the resulting graph is truncated to include at most ``abs(limit)`` |
| 40 | + entries. If *limit* is positive, the entries left are the closest to |
| 41 | + the invocation point. If *limit* is negative, the topmost entries are |
| 42 | + left. If *limit* is omitted or ``None``, all entries are present. |
| 43 | + If *limit* is ``0``, the call stack is not printed at all, only |
| 44 | + "awaited by" information is printed. |
| 45 | + |
| 46 | + If *file* is omitted or ``None``, the function will print |
| 47 | + to :data:`sys.stdout`. |
| 48 | + |
| 49 | + **Example:** |
| 50 | + |
| 51 | + The following Python code: |
| 52 | + |
| 53 | + .. code-block:: python |
| 54 | +
|
| 55 | + import asyncio |
| 56 | +
|
| 57 | + async def test(): |
| 58 | + asyncio.print_call_graph() |
| 59 | +
|
| 60 | + async def main(): |
| 61 | + async with asyncio.TaskGroup() as g: |
| 62 | + g.create_task(test()) |
| 63 | +
|
| 64 | + asyncio.run(main()) |
| 65 | +
|
| 66 | + will print:: |
| 67 | + |
| 68 | + * Task(name='Task-2', id=0x1039f0fe0) |
| 69 | + + Call stack: |
| 70 | + | File 't2.py', line 4, in async test() |
| 71 | + + Awaited by: |
| 72 | + * Task(name='Task-1', id=0x103a5e060) |
| 73 | + + Call stack: |
| 74 | + | File 'taskgroups.py', line 107, in async TaskGroup.__aexit__() |
| 75 | + | File 't2.py', line 7, in async main() |
| 76 | + |
| 77 | +.. function:: format_call_graph(future=None, /, *, depth=1, limit=None) |
| 78 | + |
| 79 | + Like :func:`print_call_graph`, but returns a string. |
| 80 | + If *future* is ``None`` and there's no current task, |
| 81 | + the function returns an empty string. |
| 82 | + |
| 83 | + |
| 84 | +.. function:: capture_call_graph(future=None, /, *, depth=1, limit=None) |
| 85 | + |
| 86 | + Capture the async call graph for the current task or the provided |
| 87 | + :class:`Task` or :class:`Future`. |
| 88 | + |
| 89 | + The function receives an optional *future* argument. |
| 90 | + If not passed, the current running task will be used. If there's no |
| 91 | + current task, the function returns ``None``. |
| 92 | + |
| 93 | + If the function is called on *the current task*, the optional |
| 94 | + keyword-only *depth* argument can be used to skip the specified |
| 95 | + number of frames from top of the stack. |
| 96 | + |
| 97 | + Returns a ``FutureCallGraph`` data class object: |
| 98 | + |
| 99 | + * ``FutureCallGraph(future, call_stack, awaited_by)`` |
| 100 | + |
| 101 | + Where *future* is a reference to a :class:`Future` or |
| 102 | + a :class:`Task` (or their subclasses.) |
| 103 | + |
| 104 | + ``call_stack`` is a tuple of ``FrameCallGraphEntry`` objects. |
| 105 | + |
| 106 | + ``awaited_by`` is a tuple of ``FutureCallGraph`` objects. |
| 107 | + |
| 108 | + * ``FrameCallGraphEntry(frame)`` |
| 109 | + |
| 110 | + Where *frame* is a frame object of a regular Python function |
| 111 | + in the call stack. |
| 112 | + |
| 113 | + |
| 114 | +Low level utility functions |
| 115 | +=========================== |
| 116 | + |
| 117 | +To introspect an async call graph asyncio requires cooperation from |
| 118 | +control flow structures, such as :func:`shield` or :class:`TaskGroup`. |
| 119 | +Any time an intermediate :class:`Future` object with low-level APIs like |
| 120 | +:meth:`Future.add_done_callback() <asyncio.Future.add_done_callback>` is |
| 121 | +involved, the following two functions should be used to inform asyncio |
| 122 | +about how exactly such intermediate future objects are connected with |
| 123 | +the tasks they wrap or control. |
| 124 | + |
| 125 | + |
| 126 | +.. function:: future_add_to_awaited_by(future, waiter, /) |
| 127 | + |
| 128 | + Record that *future* is awaited on by *waiter*. |
| 129 | + |
| 130 | + Both *future* and *waiter* must be instances of |
| 131 | + :class:`Future` or :class:`Task` or their subclasses, |
| 132 | + otherwise the call would have no effect. |
| 133 | + |
| 134 | + A call to ``future_add_to_awaited_by()`` must be followed by an |
| 135 | + eventual call to the :func:`future_discard_from_awaited_by` function |
| 136 | + with the same arguments. |
| 137 | + |
| 138 | + |
| 139 | +.. function:: future_discard_from_awaited_by(future, waiter, /) |
| 140 | + |
| 141 | + Record that *future* is no longer awaited on by *waiter*. |
| 142 | + |
| 143 | + Both *future* and *waiter* must be instances of |
| 144 | + :class:`Future` or :class:`Task` or their subclasses, otherwise |
| 145 | + the call would have no effect. |
0 commit comments