Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 49 additions & 0 deletions Lib/test/test_external_inspection.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import collections.abc
import unittest
import os
import textwrap
Expand Down Expand Up @@ -614,6 +615,54 @@ async def main():
stacks = self._extract_coroutine_funcnames(asyncio.run(main()))
self.assertEqual(stacks["consumer"], ["consumer"])

@skip_if_not_supported
def test_running_task_stack_between_steps(self):
# gh-159645
traces = []

class Loop(asyncio.SelectorEventLoop):
def call_soon(self, *args, **kwargs):
if asyncio.current_task(self) is not None:
unwinder = RemoteUnwinder(os.getpid())
traces.append(unwinder.get_async_stack_trace())
return super().call_soon(*args, **kwargs)

async def child():
await asyncio.sleep(0)

async def main():
await asyncio.create_task(child(), name="child")

asyncio.run(main(), loop_factory=Loop)
stacks = [list(self._extract_coroutine_funcnames(trace).values())
for trace in traces]
self.assertIn([["sleep", "child"], ["main"]], stacks)
self.assertIn([[], ["main"]], stacks)

@skip_if_not_supported
def test_running_task_with_non_native_coroutine(self):
# gh-159645
class Wrapper(collections.abc.Coroutine):
def __init__(self, coro):
self._coro = coro
def send(self, value):
return self._coro.send(value)
def throw(self, *args):
return self._coro.throw(*args)
def close(self):
return self._coro.close()
def __await__(self):
return self._coro.__await__()

async def inner():
return RemoteUnwinder(os.getpid()).get_async_stack_trace()

async def main():
return await asyncio.create_task(Wrapper(inner()), name="wrapper")

stacks = self._extract_coroutine_funcnames(asyncio.run(main()))
self.assertEqual(stacks["wrapper"], [])

@skip_if_not_supported
@unittest.skipIf(
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :mod:`profiling.sampling` ``--async-aware`` profiles showing event loop
frames under tasks. Patch by Timofei Ivankov.
23 changes: 23 additions & 0 deletions Modules/_remote_debugging/asyncio.c
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,24 @@ get_task_frame(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *f
if (get_coro_like_address(unwinder, running_coro_addr, &running_coro_addr) < 0) {
return -1;
}
if (running_coro_addr == 0) {
*frame_addr = 0;
return 0;
}

int8_t frame_state;
if (_Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
running_coro_addr + (uintptr_t)unwinder->debug_offsets.gen_object.gi_frame_state,
sizeof(frame_state),
&frame_state) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Running task coro frame state read failed");
return -1;
}
if (frame_state != FRAME_EXECUTING) {
*frame_addr = 0;
return 0;
}

*frame_addr = running_coro_addr + (uintptr_t)unwinder->debug_offsets.gen_object.gi_iframe;
return 0;
Expand Down Expand Up @@ -972,6 +990,11 @@ process_running_task_chain(
return -1;
}

// gh-159645: the coroutine isn't running, so keep its await chain.
if (running_task_frame == 0) {
return process_task_waiters(unwinder, result);
}

// Get the chain from the current frame to this task
PyObject *coro_chain = PyStructSequence_GET_ITEM(task_info, 2);
assert(coro_chain != NULL);
Expand Down
Loading