Skip to content

Commit 0fa2067

Browse files
committed
Add basic docs
1 parent 51a8733 commit 0fa2067

File tree

7 files changed

+108
-69
lines changed

7 files changed

+108
-69
lines changed

Doc/library/asyncio-future.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ Future Functions
6565
and *loop* is not specified and there is no running event loop.
6666

6767

68-
.. function:: wrap_future(future, *, loop=None)
68+
.. function:: wrap_future(future, /, *, loop=None)
6969

7070
Wrap a :class:`concurrent.futures.Future` object in a
7171
:class:`asyncio.Future` object.

Doc/library/asyncio-stack.rst

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
.. currentmodule:: asyncio
2+
3+
4+
.. _asyncio-stack:
5+
6+
===================
7+
Stack Introspection
8+
===================
9+
10+
**Source code:** :source:`Lib/asyncio/stack.py`
11+
12+
-------------------------------------
13+
14+
asyncio has powerful runtime call stack introspection utilities
15+
to trace the entire call graph of a running coroutine or task, or
16+
a suspended *future*.
17+
18+
.. versionadded:: 3.14
19+
20+
21+
.. function:: capture_call_stack(*, future=None)
22+
23+
Capture the async call stack for the current task or the provided
24+
:class:`Task` or :class:`Future`.
25+
26+
The function receives an optional keyword-only *future* argument.
27+
If not passed, the current task will be used. If there's no current task,
28+
the function returns ``None``.
29+
30+
Returns a ``FutureCallStack`` named tuple:
31+
32+
* ``FutureCallStack(future, call_stack, awaited_by)``
33+
34+
Where 'future' is a reference to a *Future* or a *Task*
35+
(or their subclasses.)
36+
37+
``call_stack`` is a list of ``FrameCallStackEntry`` and
38+
``CoroutineCallStackEntry`` objects (more on them below.)
39+
40+
``awaited_by`` is a list of ``FutureCallStack`` tuples.
41+
42+
* ``FrameCallStackEntry(frame)``
43+
44+
Where ``frame`` is a frame object of a regular Python function
45+
in the call stack.
46+
47+
* ``CoroutineCallStackEntry(coroutine)``
48+
49+
Where ``coroutine`` is a coroutine object of an awaiting coroutine
50+
or asyncronous generator.
51+
52+
53+
Low level utility functions
54+
===========================
55+
56+
To introspect an async call stack asyncio requires cooperation from
57+
control flow structures, such as :func:`shield` or :class:`TaskGroup`.
58+
Any time an intermediate ``Future`` object with low-level APIs like
59+
:meth:`Future.add_done_callback() <asyncio.Future.add_done_callback>` is
60+
involved, the following two functions should be used to inform *asyncio*
61+
about how exactly such intermediate future objects are connected with
62+
the tasks they wrap or control.
63+
64+
65+
.. function:: future_add_to_awaited_by(future, waiter, /)
66+
67+
Record that *future* is awaited on by *waiter*.
68+
69+
Both *future* and *waiter* must be instances of
70+
:class:`asyncio.Future <Future>` or :class:`asyncio.Task <Task>` or
71+
their subclasses, otherwise the call would have no effect.
72+
73+
74+
.. function:: future_discard_from_awaited_by(future, waiter, /)
75+
76+
Record that *future* is no longer awaited on by *waiter*.
77+
78+
Both *future* and *waiter* must be instances of
79+
:class:`asyncio.Future <Future>` or :class:`asyncio.Task <Task>` or
80+
their subclasses, otherwise the call would have no effect.

Doc/library/asyncio.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ You can experiment with an ``asyncio`` concurrent context in the :term:`REPL`:
9999
asyncio-subprocess.rst
100100
asyncio-queue.rst
101101
asyncio-exceptions.rst
102+
asyncio-stack.rst
102103

103104
.. toctree::
104105
:caption: Low-level APIs

Lib/asyncio/futures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ def wrap_future(future, *, loop=None):
423423
return new_future
424424

425425

426-
def future_add_to_awaited_by(fut, waiter):
426+
def future_add_to_awaited_by(fut, waiter, /):
427427
"""Record that `fut` is awaited on by `waiter`."""
428428
# For the sake of keeping the implementation minimal and assuming
429429
# that 99.9% of asyncio users use the built-in Futures and Tasks
@@ -451,7 +451,7 @@ def future_add_to_awaited_by(fut, waiter):
451451
fut._asyncio_awaited_by.add(waiter)
452452

453453

454-
def future_discard_from_awaited_by(fut, waiter):
454+
def future_discard_from_awaited_by(fut, waiter, /):
455455
"""Record that `fut` is no longer awaited on by `waiter`."""
456456
# See the comment in "future_add_to_awaited_by()" body for
457457
# details on implemntation.

Lib/asyncio/stack.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import types
55
import typing
66

7+
from . import events
78
from . import futures
89
from . import tasks
910

@@ -103,11 +104,20 @@ def capture_call_stack(*, future: any = None) -> FutureCallStack | None:
103104
returns None.
104105
"""
105106

107+
loop = events._get_running_loop()
108+
106109
if future is not None:
107-
if future is not tasks.current_task():
110+
# Check if we're in a context of a running event loop;
111+
# if yes - check if the passed future is the currently
112+
# running task or not.
113+
if loop is None or future is not tasks.current_task():
108114
return _build_stack_for_future(future)
109115
# else: future is the current task, move on.
110116
else:
117+
if loop is None:
118+
raise RuntimeError(
119+
'capture_call_stack() is called outside of a running '
120+
'event loop and no *future* to introspect was provided')
111121
future = tasks.current_task()
112122

113123
if future is None:

Modules/_asynciomodule.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3839,6 +3839,7 @@ _asyncio.future_add_to_awaited_by
38393839
38403840
fut: object
38413841
waiter: object
3842+
/
38423843
38433844
Record that `fut` is awaited on by `waiter`.
38443845
@@ -3847,7 +3848,7 @@ Record that `fut` is awaited on by `waiter`.
38473848
static PyObject *
38483849
_asyncio_future_add_to_awaited_by_impl(PyObject *module, PyObject *fut,
38493850
PyObject *waiter)
3850-
/*[clinic end generated code: output=0ab9a1a63389e4df input=29259cdbafe9e7bf]*/
3851+
/*[clinic end generated code: output=0ab9a1a63389e4df input=06e6eaac51f532b9]*/
38513852
{
38523853
asyncio_state *state = get_asyncio_state(module);
38533854
if (future_awaited_by_add(state, fut, waiter)) {
@@ -3861,6 +3862,7 @@ _asyncio.future_discard_from_awaited_by
38613862
38623863
fut: object
38633864
waiter: object
3865+
/
38643866
38653867
Record that `fut` is no longer awaited on by `waiter`.
38663868
@@ -3869,7 +3871,7 @@ Record that `fut` is no longer awaited on by `waiter`.
38693871
static PyObject *
38703872
_asyncio_future_discard_from_awaited_by_impl(PyObject *module, PyObject *fut,
38713873
PyObject *waiter)
3872-
/*[clinic end generated code: output=a03b0b4323b779de input=5d67a3edc79b6094]*/
3874+
/*[clinic end generated code: output=a03b0b4323b779de input=b5f7a39ccd36b5db]*/
38733875
{
38743876
asyncio_state *state = get_asyncio_state(module);
38753877
if (future_awaited_by_discard(state, fut, waiter)) {

Modules/clinic/_asynciomodule.c.h

Lines changed: 9 additions & 63 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)