gh-131591: Check for remote debug in PyErr_CheckSignals (#132853)

For the same reasons as running the GC, this will allow sections that
run in native code for long periods without executing bytecode to also
run the remote debugger protocol without having to wait until bytecode
is executed

Signed-off-by: Pablo Galindo <pablogsal@gmail.com>
This commit is contained in:
Pablo Galindo Salgado 2025-04-23 20:59:41 +01:00 committed by GitHub
parent 4b4b9fbb06
commit 99b13775da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 26 deletions

View file

@ -373,6 +373,10 @@ PyAPI_FUNC(_PyStackRef) _PyFloat_FromDouble_ConsumeInputs(_PyStackRef left, _PyS
#endif
#endif
#if defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG)
extern int _PyRunRemoteDebugger(PyThreadState *tstate);
#endif
#ifdef __cplusplus
}
#endif

View file

@ -1777,6 +1777,10 @@ PyErr_CheckSignals(void)
_Py_RunGC(tstate);
}
#if defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG)
_PyRunRemoteDebugger(tstate);
#endif
if (!_Py_ThreadCanHandleSignals(tstate->interp)) {
return 0;
}

View file

@ -1242,6 +1242,38 @@ static inline void run_remote_debugger_script(const char *path)
PyErr_FormatUnraisable("Error executing debugger script %s", path);
}
}
int _PyRunRemoteDebugger(PyThreadState *tstate)
{
const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp);
if (config->remote_debug == 1
&& tstate->remote_debugger_support.debugger_pending_call == 1)
{
tstate->remote_debugger_support.debugger_pending_call = 0;
// Immediately make a copy in case of a race with another debugger
// process that's trying to write to the buffer. At least this way
// we'll be internally consistent: what we audit is what we run.
const size_t pathsz
= sizeof(tstate->remote_debugger_support.debugger_script_path);
char *path = PyMem_Malloc(pathsz);
if (path) {
// And don't assume the debugger correctly null terminated it.
memcpy(
path,
tstate->remote_debugger_support.debugger_script_path,
pathsz);
path[pathsz - 1] = '\0';
if (*path) {
run_remote_debugger_script(path);
}
PyMem_Free(path);
}
}
return 0;
}
#endif
/* Do periodic things, like check for signals and async I/0.
@ -1372,32 +1404,7 @@ _Py_HandlePending(PyThreadState *tstate)
}
#if defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG)
const PyConfig *config = _PyInterpreterState_GetConfig(tstate->interp);
if (config->remote_debug == 1
&& tstate->remote_debugger_support.debugger_pending_call == 1)
{
tstate->remote_debugger_support.debugger_pending_call = 0;
// Immediately make a copy in case of a race with another debugger
// process that's trying to write to the buffer. At least this way
// we'll be internally consistent: what we audit is what we run.
const size_t pathsz
= sizeof(tstate->remote_debugger_support.debugger_script_path);
char *path = PyMem_Malloc(pathsz);
if (path) {
// And don't assume the debugger correctly null terminated it.
memcpy(
path,
tstate->remote_debugger_support.debugger_script_path,
pathsz);
path[pathsz - 1] = '\0';
if (*path) {
run_remote_debugger_script(path);
}
PyMem_Free(path);
}
}
_PyRunRemoteDebugger(tstate);
#endif
return 0;