From e38edd65cdf1c2118d8916068f6b099fff8e9d67 Mon Sep 17 00:00:00 2001 From: Matt Wozniski Date: Wed, 26 Feb 2025 18:14:13 -0500 Subject: [PATCH] fixup! fixup! fixup! fixup! add paragraph about multiple threads --- peps/pep-0768.rst | 55 +++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/peps/pep-0768.rst b/peps/pep-0768.rst index 10c91f75093..a6c3037a2df 100644 --- a/peps/pep-0768.rst +++ b/peps/pep-0768.rst @@ -110,8 +110,8 @@ expressions, and step through code dynamically. This approach would align Python's debugging capabilities with those of other major programming languages and debugging tools that support this mode. -/Specification -============== +Specification +============= This proposal introduces a safe debugging mechanism that allows external @@ -206,7 +206,7 @@ When a debugger wants to attach to a Python process, it follows these steps: standard practice for tools like GDB, which use SIGSTOP or ptrace to pause the process. This approach prevents races when writing to process memory. Profilers and other tools that don't wish to stop the process can still use this interface, but they need to - handle possible races, which is a normal consideration for profilers in general. + handle possible races. This is a normal consideration for profilers. - Write a file path to a Python source file (.py) into the ``debugger_script_path`` field in ``_PyRemoteDebuggerSupport``. @@ -332,35 +332,34 @@ their script run. Multi-threading Considerations ------------------------------ -Debugging code injected through this interface executes opportunistically in the -thread where the debugging information has been written first encounters a safe -evaluation point after the request is made. This behavior is different on how -Python handles signals in the sense that signal handlers can only run in the -main thread. For developers needing to target any thread, the debug script can -be installed on all threads. - -The Global Interpreter Lock (GIL) continues to govern execution as normal when -debug code runs. This means if a target thread is currently executing a C -extension that holds the GIL without releasing it, the debug code will wait -until that operation completes and the GIL becomes available. However, the -interface introduces no additional GIL contention beyond what the debugging -code itself requires. Importantly, the interface remains fully compatible with -Python's free-threaded mode, where the GIL is not held, allowing debugger code -to execute in any available thread. - -In situations where all threads in the target process are blocked—waiting on I/O -operations, sleep states, or external resources—the debugging code might not -execute immediately. In these cases, debuggers can send a pre-registered signal -to the process, which will interrupt the sleep state and force thread scheduling, -creating an opportunity for the debug code to run or leverage any other mechanism -that can force the target process to resume execution. - -The execution pattern closely resembles how Python handles signals internally. -The interpreter guarantees that debug code only runs at safe points, never +The overall execution pattern resembles how Python handles signals internally. +The interpreter guarantees that injected code only runs at safe points, never interrupting atomic operations within the interpreter itself. This approach ensures that debugging operations cannot corrupt the interpreter state while still providing timely execution in most real-world scenarios. +However, debugging code injected through this interface can execute in any +thread. This behavior is different than how Python handles signals, since +signal handlers can only run in the main thread. If a debugger wants to inject +code into every running thread, it must inject it into every ``PyThreadState``. +If a debugger wants to run code in the first available thread, it needs to +inject it into every ``PyThreadState``, and that injected code must check +whether it has already been run by another thread (likely by setting some flag +in the globals of some module). + +Note that the Global Interpreter Lock (GIL) continues to govern execution as +normal when the injected code runs. This means if a target thread is currently +executing a C extension that holds the GIL continuously, the injected code +won't be able to run until that operation completes and the GIL becomes +available. However, the interface introduces no additional GIL contention +beyond what the injected code itself requires. Importantly, the interface +remains fully compatible with Python's free-threaded mode. + +It may be useful for a debugger that injected some code to be run to follow +that up by sending some pre-registered signal to the process, which can +iterrupt any blocking I/O or sleep states waiting for external resources, and +allow a safe opportunity to run the injected code. + Backwards Compatibility =======================