Skip to content

Commit ce95260

Browse files
committed
Add ability to list all pending asyncio tasks remotely
1 parent 05d0559 commit ce95260

File tree

2 files changed

+255
-0
lines changed

2 files changed

+255
-0
lines changed

Modules/_asynciomodule.c

+12
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,17 @@ typedef struct _Py_AsyncioModuleDebugOffsets {
105105
uint64_t task_is_task;
106106
uint64_t task_awaited_by_is_set;
107107
uint64_t task_coro;
108+
uint64_t task_node;
108109
} asyncio_task_object;
110+
struct _asyncio_interpreter_state {
111+
uint64_t size;
112+
uint64_t asyncio_tasks_head;
113+
} asyncio_interpreter_state;
109114
struct _asyncio_thread_state {
110115
uint64_t size;
111116
uint64_t asyncio_running_loop;
112117
uint64_t asyncio_running_task;
118+
uint64_t asyncio_tasks_head;
113119
} asyncio_thread_state;
114120
} Py_AsyncioModuleDebugOffsets;
115121

@@ -121,11 +127,17 @@ GENERATE_DEBUG_SECTION(AsyncioDebug, Py_AsyncioModuleDebugOffsets _AsyncioDebug)
121127
.task_is_task = offsetof(TaskObj, task_is_task),
122128
.task_awaited_by_is_set = offsetof(TaskObj, task_awaited_by_is_set),
123129
.task_coro = offsetof(TaskObj, task_coro),
130+
.task_node = offsetof(TaskObj, task_node),
131+
},
132+
.asyncio_interpreter_state = {
133+
.size = sizeof(PyInterpreterState),
134+
.asyncio_tasks_head = offsetof(PyInterpreterState, asyncio_tasks_head),
124135
},
125136
.asyncio_thread_state = {
126137
.size = sizeof(_PyThreadStateImpl),
127138
.asyncio_running_loop = offsetof(_PyThreadStateImpl, asyncio_running_loop),
128139
.asyncio_running_task = offsetof(_PyThreadStateImpl, asyncio_running_task),
140+
.asyncio_tasks_head = offsetof(_PyThreadStateImpl, asyncio_tasks_head),
129141
}};
130142

131143
/* State of the _asyncio module */

Modules/_testexternalinspection.c

+243
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
#include <internal/pycore_debug_offsets.h> // _Py_DebugOffsets
5555
#include <internal/pycore_frame.h> // FRAME_SUSPENDED_YIELD_FROM
5656
#include <internal/pycore_interpframe.h> // FRAME_OWNED_BY_CSTACK
57+
#include <internal/pycore_llist.h> // struct llist_node
5758
#include <internal/pycore_stackref.h> // Py_TAG_BITS
5859

5960
#ifndef HAVE_PROCESS_VM_READV
@@ -68,11 +69,17 @@ struct _Py_AsyncioModuleDebugOffsets {
6869
uint64_t task_is_task;
6970
uint64_t task_awaited_by_is_set;
7071
uint64_t task_coro;
72+
uint64_t task_node;
7173
} asyncio_task_object;
74+
struct _asyncio_interpreter_state {
75+
uint64_t size;
76+
uint64_t asyncio_tasks_head;
77+
} asyncio_interpreter_state;
7278
struct _asyncio_thread_state {
7379
uint64_t size;
7480
uint64_t asyncio_running_loop;
7581
uint64_t asyncio_running_task;
82+
uint64_t asyncio_tasks_head;
7683
} asyncio_thread_state;
7784
};
7885

@@ -1464,6 +1471,240 @@ find_running_task(
14641471
return 0;
14651472
}
14661473

1474+
static int
1475+
append_awaited_by_for_thread(
1476+
int pid,
1477+
uintptr_t head_addr,
1478+
struct _Py_DebugOffsets *debug_offsets,
1479+
struct _Py_AsyncioModuleDebugOffsets *async_offsets,
1480+
PyObject *result
1481+
) {
1482+
struct llist_node task_node;
1483+
1484+
if (0 > read_memory(
1485+
pid,
1486+
head_addr,
1487+
sizeof(task_node),
1488+
&task_node))
1489+
{
1490+
return -1;
1491+
}
1492+
1493+
while ((uintptr_t)task_node.next != head_addr) {
1494+
uintptr_t task_addr = (uintptr_t)task_node.next
1495+
- async_offsets->asyncio_task_object.task_node;
1496+
1497+
PyObject *tn = parse_task_name(
1498+
pid,
1499+
debug_offsets,
1500+
async_offsets,
1501+
task_addr);
1502+
if (tn == NULL) {
1503+
return -1;
1504+
}
1505+
1506+
PyObject *current_awaited_by = PyList_New(0);
1507+
if (current_awaited_by == NULL) {
1508+
Py_DECREF(tn);
1509+
return -1;
1510+
}
1511+
1512+
PyObject *result_item = PyTuple_New(2);
1513+
if (result_item == NULL) {
1514+
Py_DECREF(tn);
1515+
Py_DECREF(current_awaited_by);
1516+
return -1;
1517+
}
1518+
1519+
PyTuple_SET_ITEM(result_item, 0, tn); // steals ref
1520+
PyTuple_SET_ITEM(result_item, 1, current_awaited_by); // steals ref
1521+
if (PyList_Append(result, result_item)) {
1522+
Py_DECREF(result_item);
1523+
return -1;
1524+
}
1525+
Py_DECREF(result_item);
1526+
1527+
if (parse_task_awaited_by(pid, debug_offsets, async_offsets,
1528+
task_addr, current_awaited_by))
1529+
{
1530+
return -1;
1531+
}
1532+
1533+
// onto the next one...
1534+
if (0 > read_memory(
1535+
pid,
1536+
(uintptr_t)task_node.next,
1537+
sizeof(task_node),
1538+
&task_node))
1539+
{
1540+
return -1;
1541+
}
1542+
}
1543+
1544+
return 0;
1545+
}
1546+
1547+
static int
1548+
append_awaited_by(
1549+
int pid,
1550+
unsigned long tid,
1551+
uintptr_t head_addr,
1552+
struct _Py_DebugOffsets *debug_offsets,
1553+
struct _Py_AsyncioModuleDebugOffsets *async_offsets,
1554+
PyObject *result)
1555+
{
1556+
PyObject *tid_py = PyLong_FromUnsignedLong(tid);
1557+
if (tid_py == NULL) {
1558+
return -1;
1559+
}
1560+
1561+
PyObject *result_item = PyTuple_New(2);
1562+
if (result_item == NULL) {
1563+
Py_DECREF(tid_py);
1564+
return -1;
1565+
}
1566+
1567+
PyObject* awaited_by_for_thread = PyList_New(0);
1568+
if (awaited_by_for_thread == NULL) {
1569+
Py_DECREF(tid_py);
1570+
Py_DECREF(result_item);
1571+
return -1;
1572+
}
1573+
1574+
PyTuple_SET_ITEM(result_item, 0, tid_py); // steals ref
1575+
PyTuple_SET_ITEM(result_item, 1, awaited_by_for_thread); // steals ref
1576+
if (PyList_Append(result, result_item)) {
1577+
Py_DECREF(result_item);
1578+
return -1;
1579+
}
1580+
Py_DECREF(result_item);
1581+
1582+
if (append_awaited_by_for_thread(
1583+
pid,
1584+
head_addr,
1585+
debug_offsets,
1586+
async_offsets,
1587+
awaited_by_for_thread))
1588+
{
1589+
return -1;
1590+
}
1591+
1592+
return 0;
1593+
}
1594+
1595+
static PyObject*
1596+
get_all_awaited_by(PyObject* self, PyObject* args)
1597+
{
1598+
#if (!defined(__linux__) && !defined(__APPLE__)) || \
1599+
(defined(__linux__) && !HAVE_PROCESS_VM_READV)
1600+
PyErr_SetString(
1601+
PyExc_RuntimeError,
1602+
"get_all_awaited_by is not implemented on this platform");
1603+
return NULL;
1604+
#endif
1605+
1606+
int pid;
1607+
1608+
if (!PyArg_ParseTuple(args, "i", &pid)) {
1609+
return NULL;
1610+
}
1611+
1612+
uintptr_t runtime_start_addr = get_py_runtime(pid);
1613+
if (runtime_start_addr == 0) {
1614+
if (!PyErr_Occurred()) {
1615+
PyErr_SetString(
1616+
PyExc_RuntimeError, "Failed to get .PyRuntime address");
1617+
}
1618+
return NULL;
1619+
}
1620+
struct _Py_DebugOffsets local_debug_offsets;
1621+
1622+
if (read_offsets(pid, &runtime_start_addr, &local_debug_offsets)) {
1623+
return NULL;
1624+
}
1625+
1626+
struct _Py_AsyncioModuleDebugOffsets local_async_debug;
1627+
if (read_async_debug(pid, &local_async_debug)) {
1628+
return NULL;
1629+
}
1630+
1631+
PyObject *result = PyList_New(0);
1632+
if (result == NULL) {
1633+
return NULL;
1634+
}
1635+
1636+
off_t interpreter_state_list_head =
1637+
local_debug_offsets.runtime_state.interpreters_head;
1638+
1639+
uintptr_t interpreter_state_addr;
1640+
if (0 > read_memory(
1641+
pid,
1642+
runtime_start_addr + interpreter_state_list_head,
1643+
sizeof(void*),
1644+
&interpreter_state_addr))
1645+
{
1646+
goto result_err;
1647+
}
1648+
1649+
uintptr_t thread_state_addr;
1650+
unsigned long tid = 0;
1651+
if (0 > read_memory(
1652+
pid,
1653+
interpreter_state_addr
1654+
+ local_debug_offsets.interpreter_state.threads_head,
1655+
sizeof(void*),
1656+
&thread_state_addr))
1657+
{
1658+
goto result_err;
1659+
}
1660+
1661+
uintptr_t head_addr;
1662+
while (thread_state_addr != 0) {
1663+
if (0 > read_memory(
1664+
pid,
1665+
thread_state_addr
1666+
+ local_debug_offsets.thread_state.native_thread_id,
1667+
sizeof(tid),
1668+
&tid))
1669+
{
1670+
goto result_err;
1671+
}
1672+
1673+
head_addr = thread_state_addr
1674+
+ local_async_debug.asyncio_thread_state.asyncio_tasks_head;
1675+
1676+
if (append_awaited_by(pid, tid, head_addr, &local_debug_offsets,
1677+
&local_async_debug, result))
1678+
{
1679+
goto result_err;
1680+
}
1681+
1682+
if (0 > read_memory(
1683+
pid,
1684+
thread_state_addr + local_debug_offsets.thread_state.next,
1685+
sizeof(void*),
1686+
&thread_state_addr))
1687+
{
1688+
goto result_err;
1689+
}
1690+
}
1691+
1692+
head_addr = interpreter_state_addr
1693+
+ local_async_debug.asyncio_interpreter_state.asyncio_tasks_head;
1694+
1695+
if (append_awaited_by(pid, 0, head_addr, &local_debug_offsets,
1696+
&local_async_debug, result))
1697+
{
1698+
goto result_err;
1699+
}
1700+
1701+
return result;
1702+
1703+
result_err:
1704+
Py_DECREF(result);
1705+
return NULL;
1706+
}
1707+
14671708
static PyObject*
14681709
get_stack_trace(PyObject* self, PyObject* args)
14691710
{
@@ -1686,6 +1927,8 @@ static PyMethodDef methods[] = {
16861927
"Get the Python stack from a given PID"},
16871928
{"get_async_stack_trace", get_async_stack_trace, METH_VARARGS,
16881929
"Get the asyncio stack from a given PID"},
1930+
{"get_all_awaited_by", get_all_awaited_by, METH_VARARGS,
1931+
"Get all tasks and their awaited_by from a given PID"},
16891932
{NULL, NULL, 0, NULL},
16901933
};
16911934

0 commit comments

Comments
 (0)