Skip to content

Commit 3f23308

Browse files
committed
Merge branch 'main' of https://github.com/python/cpython into fix-asyncio-task-unregister-race
2 parents 58c83fc + 2ef3728 commit 3f23308

6 files changed

Lines changed: 146 additions & 51 deletions

File tree

‎Lib/asyncio/base_tasks.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ def _task_get_stack(task, limit):
4242
elif hasattr(task._coro, 'ag_frame'):
4343
# case 3: async generators
4444
f = task._coro.ag_frame
45+
elif not task.done() and hasattr(task._coro, 'aga_gen'):
46+
# case 4: async generator's asend/athrow/aclose
47+
f = task._coro.aga_gen.ag_frame
4548
else:
46-
# case 4: unknown objects
49+
# case 5: unknown objects
4750
f = None
4851
if f is not None:
4952
while f is not None:

‎Lib/test/test_asyncio/test_tasks.py‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,40 @@ async def runner():
335335

336336
self.loop.run_until_complete(runner())
337337

338+
def test_get_stack_async_generator(self):
339+
# gh-161178
340+
async def gen():
341+
try:
342+
yield
343+
except ValueError:
344+
pass
345+
finally:
346+
await asyncio.sleep(0)
347+
yield
348+
349+
async def runner(method, args):
350+
agen = gen()
351+
await anext(agen)
352+
task = self.new_task(self.loop, getattr(agen, method)(*args))
353+
await asyncio.sleep(0)
354+
frame = agen.ag_frame
355+
stack = task.get_stack()
356+
await task
357+
try:
358+
self.assertEqual(stack, [frame])
359+
self.assertEqual(task.get_stack(), [])
360+
finally:
361+
await agen.aclose()
362+
363+
for method, args in (
364+
('__anext__', ()),
365+
('asend', (None,)),
366+
('athrow', (ValueError(),)),
367+
('aclose', ()),
368+
):
369+
with self.subTest(method=method):
370+
self.loop.run_until_complete(runner(method, args))
371+
338372
def test_task_repr(self):
339373
self.loop.set_debug(False)
340374

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :meth:`asyncio.Task.print_stack` to include frames from async generator
2+
operations. Patch by Timofei Ivankov

‎Modules/_remote_debugging/clinic/module.c.h‎

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

‎Modules/_remote_debugging/module.c‎

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,44 +1010,70 @@ For each thread with a running task, returns the running task plus
10101010
all tasks that transitively depend on it (tasks waiting for the
10111011
running task, tasks waiting for those tasks, etc.).
10121012
1013-
Returns a list of per-thread results, where each thread result
1014-
contains:
1015-
- Thread ID
1016-
- List of task information for the running task and all its waiters
1013+
Returns:
1014+
A list of AwaitedInfo objects, where each object contains:
10171015
1018-
Each task info contains:
1019-
- Task ID (memory address)
1020-
- Task name
1021-
- Call stack frames: List of (func_name, filename, lineno)
1022-
- List of tasks waiting for this task (recursive structure)
1016+
- thread_id (int): Identifier of the thread.
1017+
- awaited_by (list[TaskInfo]): The running task and all its
1018+
transitive waiters.
1019+
1020+
Each TaskInfo contains:
1021+
- task_id (int): Identifier of the task.
1022+
- task_name (str): Name of the task.
1023+
- coroutine_stack (list[CoroInfo]): Stack of coroutine frames.
1024+
- awaited_by (list[CoroInfo]): Coroutine information for
1025+
tasks or futures awaiting this task.
1026+
1027+
Each CoroInfo contains:
1028+
- call_stack (list[FrameInfo]): Frames for the coroutine.
1029+
- task_name (int): Identifier of the task or future.
1030+
1031+
Each FrameInfo contains:
1032+
- filename (str): Source code filename.
1033+
- location (LocationInfo | None): Source location, if available.
1034+
- funcname (str): Function name.
1035+
- opcode (int | None): Opcode, if available.
1036+
1037+
Each LocationInfo contains:
1038+
- lineno (int): Starting line number.
1039+
- end_lineno (int): Ending line number.
1040+
- col_offset (int): Starting column offset.
1041+
- end_col_offset (int): Ending column offset.
10231042
10241043
Raises:
10251044
RuntimeError: If AsyncioDebug section is not available in the
10261045
target process
10271046
MemoryError: If memory allocation fails
10281047
OSError: If reading from the remote process fails
10291048
1030-
Example output (similar structure to get_all_awaited_by but only for
1031-
running tasks):
1032-
[
1033-
# Thread 140234 results
1034-
(140234, [
1035-
# Running task and its complete waiter dependency graph
1036-
(4345585712, 'main_task',
1037-
[("run_server", "server.py", 127), ("main", "app.py", 23)],
1038-
[
1039-
# Tasks waiting for main_task
1040-
(4345585800, 'worker_1', [...], [...]),
1041-
(4345585900, 'worker_2', [...], [...])
1042-
])
1043-
])
1044-
]
1049+
Example output:
1050+
1051+
[
1052+
AwaitedInfo(thread_id=140234, awaited_by=[
1053+
TaskInfo(
1054+
task_id=1,
1055+
task_name="worker",
1056+
coroutine_stack=[
1057+
CoroInfo(call_stack=[...], task_name=1)
1058+
],
1059+
awaited_by=[CoroInfo(call_stack=[...], task_name=2)]
1060+
),
1061+
TaskInfo(
1062+
task_id=2,
1063+
task_name="main",
1064+
coroutine_stack=[
1065+
CoroInfo(call_stack=[...], task_name=2)
1066+
],
1067+
awaited_by=[]
1068+
)
1069+
])
1070+
]
10451071
10461072
[clinic start generated code]*/
10471073

10481074
static PyObject *
10491075
_remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject *self)
1050-
/*[clinic end generated code: output=6433d52b55e87bbe input=bc802e4221c99399]*/
1076+
/*[clinic end generated code: output=6433d52b55e87bbe input=0532031a7c495491]*/
10511077
{
10521078
if (ensure_async_debug_offsets(self) < 0) {
10531079
return NULL;

‎Python/marshal.c‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -737,9 +737,10 @@ w_tie_set_items(w_set_item *items, Py_ssize_t count, size_t first, WFILE *p)
737737
remembered it, and it cannot be written again. */
738738
continue;
739739
}
740-
uintptr_t w = (uintptr_t)entry->value;
741-
assert(!(w & (W_ENTRY_ACTIVE | W_ENTRY_TIED)));
742-
assert((w >> W_ENTRY_SHIFT) - first < (size_t)count);
740+
assert(!((uintptr_t)entry->value & (W_ENTRY_ACTIVE | W_ENTRY_TIED)));
741+
assert(
742+
((uintptr_t)entry->value >> W_ENTRY_SHIFT) - first < (size_t)count
743+
);
743744
entry->value = (void *)value;
744745
}
745746
}

0 commit comments

Comments
 (0)