Repository navigation
gh-158880: Fix asyncio ps showing tasks from only one interpreter #158900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Fix ``python -m asyncio ps`` and ``pstree`` showing tasks from only one | ||
| interpreter. Patch by Timofei Ivankov. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -958,7 +958,9 @@ _remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *s | |||||||||||||||||
| if (ensure_async_debug_offsets(self) < 0) { | ||||||||||||||||||
| return NULL; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (refresh_generation_caches_for_interpreter(self, self->interpreter_addr) < 0) { | ||||||||||||||||||
| PyObject *seen = PySet_New(NULL); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just a list: cpython/Modules/_remote_debugging/module.c Lines 719 to 720 in 9028df3
cpython/Modules/_remote_debugging/module.c Lines 884 to 885 in 9028df3
cpython/Modules/_remote_debugging/frames.c Lines 459 to 462 in 182f323
Is there a reproducible race scenario where we'd see a cycle? (It was quiet easy to reproduce issues like ABA.)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I couldn't reproduce a cycle. I thought the interpreter walk could loop |
||||||||||||||||||
| if (seen == NULL) { | ||||||||||||||||||
| set_exception_cause(self, PyExc_MemoryError, "Failed to create interpreter set"); | ||||||||||||||||||
| return NULL; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -968,30 +970,65 @@ _remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *s | |||||||||||||||||
| goto result_err; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Process all threads | ||||||||||||||||||
| if (iterate_threads(self, process_thread_for_awaited_by, result) < 0) { | ||||||||||||||||||
| goto result_err; | ||||||||||||||||||
| } | ||||||||||||||||||
| // gh-158880: Tasks live in every interpreter, not only the one at the list head | ||||||||||||||||||
| for (uintptr_t interp = self->interpreter_addr; interp != 0; ) { | ||||||||||||||||||
| PyObject *addr = PyLong_FromUnsignedLongLong(interp); | ||||||||||||||||||
| if (addr == NULL) { | ||||||||||||||||||
| set_exception_cause(self, PyExc_MemoryError, "Failed to create interpreter address"); | ||||||||||||||||||
| goto result_err; | ||||||||||||||||||
| } | ||||||||||||||||||
| Py_ssize_t seen_count = PySet_GET_SIZE(seen); | ||||||||||||||||||
| int marked = PySet_Add(seen, addr); | ||||||||||||||||||
| Py_DECREF(addr); | ||||||||||||||||||
| if (marked < 0) { | ||||||||||||||||||
| set_exception_cause(self, PyExc_RuntimeError, "Failed to mark interpreter as seen"); | ||||||||||||||||||
| goto result_err; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (PySet_GET_SIZE(seen) == seen_count) { | ||||||||||||||||||
| // already walked | ||||||||||||||||||
| break; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| uintptr_t head_addr = self->interpreter_addr | ||||||||||||||||||
| + (uintptr_t)self->async_debug_offsets.asyncio_interpreter_state.asyncio_tasks_head; | ||||||||||||||||||
| if (refresh_generation_caches_for_interpreter(self, interp) < 0) { | ||||||||||||||||||
| goto result_err; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // On top of a per-thread task lists used by default by asyncio to avoid | ||||||||||||||||||
| // contention, there is also a fallback per-interpreter list of tasks; | ||||||||||||||||||
| // any tasks still pending when a thread is destroyed will be moved to the | ||||||||||||||||||
| // per-interpreter task list. It's unlikely we'll find anything here, but | ||||||||||||||||||
| // interesting for debugging. | ||||||||||||||||||
| if (append_awaited_by(self, 0, head_addr, result)) | ||||||||||||||||||
| { | ||||||||||||||||||
| set_exception_cause(self, PyExc_RuntimeError, "Failed to append interpreter awaited_by in get_all_awaited_by"); | ||||||||||||||||||
| goto result_err; | ||||||||||||||||||
| // Process all threads | ||||||||||||||||||
| if (iterate_threads(self, interp, process_thread_for_awaited_by, result) < 0) { | ||||||||||||||||||
| goto result_err; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| uintptr_t head_addr = interp | ||||||||||||||||||
| + (uintptr_t)self->async_debug_offsets.asyncio_interpreter_state.asyncio_tasks_head; | ||||||||||||||||||
|
|
||||||||||||||||||
| // On top of a per-thread task lists used by default by asyncio to avoid | ||||||||||||||||||
| // contention, there is also a fallback per-interpreter list of tasks; | ||||||||||||||||||
| // any tasks still pending when a thread is destroyed will be moved to | ||||||||||||||||||
| // the per-interpreter task list. It's unlikely we'll find anything | ||||||||||||||||||
| // here, but interesting for debugging. | ||||||||||||||||||
| if (append_awaited_by(self, 0, head_addr, result)) | ||||||||||||||||||
| { | ||||||||||||||||||
| set_exception_cause(self, PyExc_RuntimeError, "Failed to append interpreter awaited_by in get_all_awaited_by"); | ||||||||||||||||||
| goto result_err; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (_Py_RemoteDebug_PagedReadRemoteMemory( | ||||||||||||||||||
| &self->handle, | ||||||||||||||||||
| interp + (uintptr_t)self->debug_offsets.interpreter_state.next, | ||||||||||||||||||
| sizeof(void*), | ||||||||||||||||||
| &interp) < 0) { | ||||||||||||||||||
| set_exception_cause(self, PyExc_RuntimeError, "Failed to read next interpreter address"); | ||||||||||||||||||
| goto result_err; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| _Py_RemoteDebug_ClearCache(&self->handle); | ||||||||||||||||||
| Py_DECREF(seen); | ||||||||||||||||||
| return result; | ||||||||||||||||||
|
|
||||||||||||||||||
| result_err: | ||||||||||||||||||
| _Py_RemoteDebug_ClearCache(&self->handle); | ||||||||||||||||||
| Py_DECREF(seen); | ||||||||||||||||||
| Py_XDECREF(result); | ||||||||||||||||||
| return NULL; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -1064,7 +1101,8 @@ _remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject | |||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Process all threads | ||||||||||||||||||
| if (iterate_threads(self, process_thread_for_async_stack_trace, result) < 0) { | ||||||||||||||||||
| if (iterate_threads(self, self->interpreter_addr, | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless I'm missing something,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it has the problem, i planned to open a separate issue for this to avoid "big-bang" pr |
||||||||||||||||||
| process_thread_for_async_stack_trace, result) < 0) { | ||||||||||||||||||
| goto result_err; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One way to silence https://github.com/python/cpython/actions/runs/37499870518/job/112393935495#step:6:1023 is by wrapping this in
except TRANSIENT_ERRORS:Perhaps a better approach, similar to #158801
cpython/Lib/test/test_external_inspection.py
Lines 2440 to 2460 in 0e75a05
cpython/Lib/test/test_external_inspection.py
Lines 2469 to 2482 in 0e75a05