Skip to content

Commit

Permalink
fixup! fixup! fixup! fixup! add paragraph about multiple threads
Browse files Browse the repository at this point in the history
  • Loading branch information
godlygeek committed Feb 26, 2025
1 parent bcae670 commit e38edd6
Showing 1 changed file with 27 additions and 28 deletions.
55 changes: 27 additions & 28 deletions peps/pep-0768.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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``.
Expand Down Expand Up @@ -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
=======================

Expand Down

0 comments on commit e38edd6

Please sign in to comment.