From: Pablo Galindo Salgado Date: Sat, 3 Jan 2026 14:37:16 +0000 (+0000) Subject: gh-138122: Skip threads on EPERM in blocking mode profiler (GH-143368) X-Git-Tag: v3.15.0a5~11^2~136 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=27434c68f8c9d8f5e3e1708948a339999172a4bc;p=thirdparty%2FPython%2Fcpython.git gh-138122: Skip threads on EPERM in blocking mode profiler (GH-143368) When using blocking mode in the remote debugging profiler, ptrace calls to seize threads can fail with EPERM if the thread has exited between listing and attaching, is in a special kernel state, or is already being traced. Previously this raised a RuntimeError that was caught by the Python sampling loop,and retried indefinitely since EPERM is a persistent condition that will not resolve on its own. Treat EPERM the same as ESRCH by returning 1 (skip this thread) instead of -1 (fatal error). This allows profiling to continue with the threads that can be traced rather than entering an endless retry loop printing the same error message repeatedly. --- diff --git a/Misc/NEWS.d/next/Library/2026-01-02-17-26-33.gh-issue-143368.m3EF9E.rst b/Misc/NEWS.d/next/Library/2026-01-02-17-26-33.gh-issue-143368.m3EF9E.rst new file mode 100644 index 000000000000..4c07e4569acc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-02-17-26-33.gh-issue-143368.m3EF9E.rst @@ -0,0 +1,3 @@ +Fix endless retry loop in :mod:`profiling.sampling` blocking mode when +threads cannot be seized due to ``EPERM``. Such threads are now skipped +instead of causing repeated error messages. Patch by Pablo Galindo. diff --git a/Modules/_remote_debugging/threads.c b/Modules/_remote_debugging/threads.c index d40031abbdc0..3100b83c8f48 100644 --- a/Modules/_remote_debugging/threads.c +++ b/Modules/_remote_debugging/threads.c @@ -632,6 +632,12 @@ seize_thread(pid_t tid) if (errno == ESRCH) { return 1; // Thread gone, skip } + if (errno == EPERM) { + // Thread may have exited, be in a special state, or already be traced. + // Skip rather than fail - this avoids endless retry loops when + // threads transiently become inaccessible. + return 1; + } if (errno == EINVAL || errno == EIO) { // Fallback for older kernels if (ptrace(PTRACE_ATTACH, tid, NULL, NULL) == 0) { @@ -639,8 +645,8 @@ seize_thread(pid_t tid) waitpid(tid, &status, __WALL); return 0; } - if (errno == ESRCH) { - return 1; // Thread gone + if (errno == ESRCH || errno == EPERM) { + return 1; // Thread gone or inaccessible } } return -1; // Real error