Also introduce a new C API ``PyErr_SetInterruptEx(int signum)``.
single: SIGINT
single: KeyboardInterrupt (built-in exception)
- This function interacts with Python's signal handling. It checks whether a
- signal has been sent to the processes and if so, invokes the corresponding
- signal handler. If the :mod:`signal` module is supported, this can invoke a
- signal handler written in Python. In all cases, the default effect for
- :const:`SIGINT` is to raise the :exc:`KeyboardInterrupt` exception. If an
- exception is raised the error indicator is set and the function returns ``-1``;
- otherwise the function returns ``0``. The error indicator may or may not be
- cleared if it was previously set.
+ This function interacts with Python's signal handling.
+
+ If the function is called from the main thread and under the main Python
+ interpreter, it checks whether a signal has been sent to the processes
+ and if so, invokes the corresponding signal handler. If the :mod:`signal`
+ module is supported, this can invoke a signal handler written in Python.
+
+ The function attemps to handle all pending signals, and then returns ``0``.
+ However, if a Python signal handler raises an exception, the error
+ indicator is set and the function returns ``-1`` immediately (such that
+ other pending signals may not have been handled yet: they will be on the
+ next :c:func:`PyErr_CheckSignals()` invocation).
+
+ If the function is called from a non-main thread, or under a non-main
+ Python interpreter, it does nothing and returns ``0``.
+
+ This function can be called by long-running C code that wants to
+ be interruptible by user requests (such as by pressing Ctrl-C).
+
+ .. note::
+ The default Python signal handler for :const:`SIGINT` raises the
+ :exc:`KeyboardInterrupt` exception.
.. c:function:: void PyErr_SetInterrupt()
.. index::
+ module: signal
single: SIGINT
single: KeyboardInterrupt (built-in exception)
- Simulate the effect of a :const:`SIGINT` signal arriving. The next time
+ Simulate the effect of a :const:`SIGINT` signal arriving.
+ This is equivalent to ``PyErr_SetInterruptEx(SIGINT)``.
+
+ .. note::
+ This function is async-signal-safe. It can be called without
+ the :term:`GIL` and from a C signal handler.
+
+
+.. c:function:: int PyErr_SetInterruptEx(int signum)
+
+ .. index::
+ module: signal
+ single: KeyboardInterrupt (built-in exception)
+
+ Simulate the effect of a signal arriving. The next time
:c:func:`PyErr_CheckSignals` is called, the Python signal handler for
- :const:`SIGINT` will be called.
+ the given signal number will be called.
+
+ This function can be called by C code that sets up its own signal handling
+ and wants Python signal handlers to be invoked as expected when an
+ interruption is requested (for example when the user presses Ctrl-C
+ to interrupt an operation).
+
+ If the given signal isn't handled by Python (it was set to
+ :data:`signal.SIG_DFL` or :data:`signal.SIG_IGN`), it will be ignored.
+
+ If *signum* is outside of the allowed range of signal numbers, ``-1``
+ is returned. Otherwise, ``0`` is returned. The error indicator is
+ never changed by this function.
+
+ .. note::
+ This function is async-signal-safe. It can be called without
+ the :term:`GIL` and from a C signal handler.
+
+ .. versionadded:: 3.10
- If :const:`SIGINT` isn't handled by Python (it was set to
- :data:`signal.SIG_DFL` or :data:`signal.SIG_IGN`), this function does
- nothing.
.. c:function:: int PySignal_SetWakeupFd(int fd)
PyCFunction_GetFlags
PyCFunction_GetFunction
PyCFunction_GetSelf
+PyCFunction_New
PyCFunction_NewEx
PyCFunction_Type
PyCMethod_New
PyErr_SetImportError
PyErr_SetImportErrorSubclass
PyErr_SetInterrupt
+PyErr_SetInterruptEx
PyErr_SetNone
PyErr_SetObject
PyErr_SetString
:func:`sys.unraisablehook` is now used to handle unhandled exceptions.
-.. function:: interrupt_main()
+.. function:: interrupt_main(signum=signal.SIGINT, /)
- Simulate the effect of a :data:`signal.SIGINT` signal arriving in the main
- thread. A thread can use this function to interrupt the main thread.
+ Simulate the effect of a signal arriving in the main thread.
+ A thread can use this function to interrupt the main thread, though
+ there is no guarantee that the interruption will happen immediately.
- If :data:`signal.SIGINT` isn't handled by Python (it was set to
+ If given, *signum* is the number of the signal to simulate.
+ If *signum* is not given, :data:`signal.SIGINT` is simulated.
+
+ If the given signal isn't handled by Python (it was set to
:data:`signal.SIG_DFL` or :data:`signal.SIG_IGN`), this function does
nothing.
+ .. versionchanged:: 3.10
+ The *signum* argument is added to customize the signal number.
+
+ .. note::
+ This does not emit the corresponding signal but schedules a call to
+ the associated handler (if it exists).
+ If you want to truly emit the signal, use :func:`signal.raise_signal`.
+
.. function:: exit()
module names.
(Contributed by Victor Stinner in :issue:`42955`.)
+_thread
+-------
+
+:func:`_thread.interrupt_main` now takes an optional signal number to
+simulate (the default is still :data:`signal.SIGINT`).
+(Contributed by Antoine Pitrou in :issue:`43356`.)
+
threading
---------
object is an instance of :class:`set` but not an instance of a subtype.
(Contributed by Pablo Galindo in :issue:`43277`.)
+* Added :c:func:`PyErr_SetInterruptEx` which allows passing a signal number
+ to simulate.
+ (Contributed by Antoine Pitrou in :issue:`43356`.)
+
+
Porting to Python 3.10
----------------------
# error "this header requires Py_BUILD_CORE define"
#endif
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+
#include "pycore_runtime.h" // _PyRuntimeState
+#ifndef NSIG
+# if defined(_NSIG)
+# define NSIG _NSIG /* For BSD/SysV */
+# elif defined(_SIGMAX)
+# define NSIG (_SIGMAX + 1) /* For QNX */
+# elif defined(SIGMAX)
+# define NSIG (SIGMAX + 1) /* For djgpp */
+# else
+# define NSIG 64 /* Use a reasonable default value */
+# endif
+#endif
+
/* Forward declarations */
struct _PyArgv;
struct pyruntimestate;
/* In signalmodule.c */
PyAPI_FUNC(int) PyErr_CheckSignals(void);
PyAPI_FUNC(void) PyErr_SetInterrupt(void);
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
+PyAPI_FUNC(int) PyErr_SetInterruptEx(int signum);
+#endif
/* Support for adding program text to SyntaxErrors */
PyAPI_FUNC(void) PyErr_SyntaxLocation(
class InterruptMainTests(unittest.TestCase):
+ def check_interrupt_main_with_signal_handler(self, signum):
+ def handler(signum, frame):
+ 1/0
+
+ old_handler = signal.signal(signum, handler)
+ self.addCleanup(signal.signal, signum, old_handler)
+
+ with self.assertRaises(ZeroDivisionError):
+ _thread.interrupt_main()
+
+ def check_interrupt_main_noerror(self, signum):
+ handler = signal.getsignal(signum)
+ try:
+ # No exception should arise.
+ signal.signal(signum, signal.SIG_IGN)
+ _thread.interrupt_main(signum)
+
+ signal.signal(signum, signal.SIG_DFL)
+ _thread.interrupt_main(signum)
+ finally:
+ # Restore original handler
+ signal.signal(signum, handler)
+
def test_interrupt_main_subthread(self):
# Calling start_new_thread with a function that executes interrupt_main
# should raise KeyboardInterrupt upon completion.
with self.assertRaises(KeyboardInterrupt):
_thread.interrupt_main()
+ def test_interrupt_main_with_signal_handler(self):
+ self.check_interrupt_main_with_signal_handler(signal.SIGINT)
+ self.check_interrupt_main_with_signal_handler(signal.SIGTERM)
+
def test_interrupt_main_noerror(self):
- handler = signal.getsignal(signal.SIGINT)
- try:
- # No exception should arise.
- signal.signal(signal.SIGINT, signal.SIG_IGN)
- _thread.interrupt_main()
+ self.check_interrupt_main_noerror(signal.SIGINT)
+ self.check_interrupt_main_noerror(signal.SIGTERM)
- signal.signal(signal.SIGINT, signal.SIG_DFL)
- _thread.interrupt_main()
- finally:
- # Restore original handler
- signal.signal(signal.SIGINT, handler)
+ def test_interrupt_main_invalid_signal(self):
+ self.assertRaises(ValueError, _thread.interrupt_main, -1)
+ self.assertRaises(ValueError, _thread.interrupt_main, signal.NSIG)
+ self.assertRaises(ValueError, _thread.interrupt_main, 1000000)
class AtexitTests(unittest.TestCase):
--- /dev/null
+Allow passing a signal number to ``_thread.interrupt_main()``.
#include <stddef.h> // offsetof()
#include "structmember.h" // PyMemberDef
+#ifdef HAVE_SIGNAL_H
+# include <signal.h> // SIGINT
+#endif
+
// ThreadError is just an alias to PyExc_RuntimeError
#define ThreadError PyExc_RuntimeError
thread to exit silently unless the exception is caught.");
static PyObject *
-thread_PyThread_interrupt_main(PyObject * self, PyObject *Py_UNUSED(ignored))
+thread_PyThread_interrupt_main(PyObject *self, PyObject *args)
{
- PyErr_SetInterrupt();
+ int signum = SIGINT;
+ if (!PyArg_ParseTuple(args, "|i:signum", &signum)) {
+ return NULL;
+ }
+
+ if (PyErr_SetInterruptEx(signum)) {
+ PyErr_SetString(PyExc_ValueError, "signal number out of range");
+ return NULL;
+ }
Py_RETURN_NONE;
}
PyDoc_STRVAR(interrupt_doc,
-"interrupt_main()\n\
+"interrupt_main(signum=signal.SIGINT, /)\n\
+\n\
+Simulate the arrival of the given signal in the main thread,\n\
+where the corresponding signal handler will be executed.\n\
+If *signum* is omitted, SIGINT is assumed.\n\
+A subthread can use this function to interrupt the main thread.\n\
\n\
-Raise a KeyboardInterrupt in the main thread.\n\
-A subthread can use this function to interrupt the main thread."
+Note: the default signal hander for SIGINT raises ``KeyboardInterrupt``."
);
static lockobject *newlockobject(PyObject *module);
METH_NOARGS, exit_doc},
{"exit", thread_PyThread_exit_thread,
METH_NOARGS, exit_doc},
- {"interrupt_main", thread_PyThread_interrupt_main,
- METH_NOARGS, interrupt_doc},
+ {"interrupt_main", (PyCFunction)thread_PyThread_interrupt_main,
+ METH_VARARGS, interrupt_doc},
{"get_ident", thread_get_ident,
METH_NOARGS, get_ident_doc},
#ifdef PY_HAVE_THREAD_NATIVE_ID
#include "pycore_call.h"
#include "pycore_ceval.h"
#include "pycore_pyerrors.h"
+#include "pycore_pylifecycle.h"
#include "pycore_pystate.h" // _PyThreadState_GET()
#ifndef MS_WINDOWS
#define SIG_ERR ((PyOS_sighandler_t)(-1))
#endif
-#ifndef NSIG
-# if defined(_NSIG)
-# define NSIG _NSIG /* For BSD/SysV */
-# elif defined(_SIGMAX)
-# define NSIG (_SIGMAX + 1) /* For QNX */
-# elif defined(SIGMAX)
-# define NSIG (SIGMAX + 1) /* For djgpp */
-# else
-# define NSIG 64 /* Use a reasonable default value */
-# endif
-#endif
-
#include "clinic/signalmodule.c.h"
/*[clinic input]
static volatile struct {
_Py_atomic_int tripped;
- PyObject *func;
+ /* func is atomic to ensure that PyErr_SetInterrupt is async-signal-safe
+ * (even though it would probably be otherwise, anyway).
+ */
+ _Py_atomic_address func;
} Handlers[NSIG];
#ifdef MS_WINDOWS
static PyObject *ItimerError;
#endif
+Py_LOCAL_INLINE(PyObject *)
+get_handler(int i) {
+ return (PyObject *)_Py_atomic_load(&Handlers[i].func);
+}
+
+Py_LOCAL_INLINE(void)
+SetHandler(int i, PyObject* func) {
+ _Py_atomic_store(&Handlers[i].func, (uintptr_t)func);
+}
+
#ifdef HAVE_GETITIMER
/* auxiliary functions for setitimer */
static int
return NULL;
}
- old_handler = Handlers[signalnum].func;
- Handlers[signalnum].func = Py_NewRef(handler);
+ old_handler = get_handler(signalnum);
+ SetHandler(signalnum, Py_NewRef(handler));
if (old_handler != NULL) {
return old_handler;
"signal number out of range");
return NULL;
}
- old_handler = Handlers[signalnum].func;
+ old_handler = get_handler(signalnum);
if (old_handler != NULL) {
return Py_NewRef(old_handler);
}
}
// If signal_module_exec() is called more than one, we must
// clear the strong reference to the previous function.
- Py_XSETREF(Handlers[signum].func, Py_NewRef(func));
+ PyObject* old_func = get_handler(signum);
+ SetHandler(signum, Py_NewRef(func));
+ Py_XDECREF(old_func);
}
// Instal Python SIGINT handler which raises KeyboardInterrupt
- if (Handlers[SIGINT].func == DefaultHandler) {
+ PyObject* sigint_func = get_handler(SIGINT);
+ if (sigint_func == DefaultHandler) {
PyObject *int_handler = PyMapping_GetItemString(d, "default_int_handler");
if (!int_handler) {
return -1;
}
- Py_SETREF(Handlers[SIGINT].func, int_handler);
+ SetHandler(SIGINT, int_handler);
+ Py_DECREF(sigint_func);
PyOS_setsig(SIGINT, signal_handler);
}
{
// Restore default signals and clear handlers
for (int signum = 1; signum < NSIG; signum++) {
- PyObject *func = Handlers[signum].func;
+ PyObject *func = get_handler(signum);
_Py_atomic_store_relaxed(&Handlers[signum].tripped, 0);
- Handlers[signum].func = NULL;
+ SetHandler(signum, NULL);
if (func != NULL
&& func != Py_None
&& func != DefaultHandler
* signal handler for it by the time PyErr_CheckSignals() is called
* (see bpo-43406).
*/
- PyObject *func = Handlers[i].func;
+ PyObject *func = get_handler(i);
if (func == NULL || func == Py_None || func == IgnoreHandler ||
func == DefaultHandler) {
/* No Python signal handler due to aforementioned race condition.
}
-/* Simulate the effect of a signal.SIGINT signal arriving. The next time
- PyErr_CheckSignals is called, the Python SIGINT signal handler will be
- raised.
+/* Simulate the effect of a signal arriving. The next time PyErr_CheckSignals
+ is called, the corresponding Python signal handler will be raised.
+
+ Missing signal handler for the given signal number is silently ignored. */
+int
+PyErr_SetInterruptEx(int signum)
+{
+ if (signum < 1 || signum >= NSIG) {
+ return -1;
+ }
+ PyObject* func = get_handler(signum);
+ if (func != IgnoreHandler && func != DefaultHandler) {
+ trip_signal(signum);
+ }
+ return 0;
+}
- Missing signal handler for the SIGINT signal is silently ignored. */
void
PyErr_SetInterrupt(void)
{
- if ((Handlers[SIGINT].func != IgnoreHandler) &&
- (Handlers[SIGINT].func != DefaultHandler)) {
- trip_signal(SIGINT);
- }
+ (void) PyErr_SetInterruptEx(SIGINT);
}
static int
EXPORT_FUNC(PyErr_SetImportError)
EXPORT_FUNC(PyErr_SetImportErrorSubclass)
EXPORT_FUNC(PyErr_SetInterrupt)
+EXPORT_FUNC(PyErr_SetInterruptEx)
EXPORT_FUNC(PyErr_SetNone)
EXPORT_FUNC(PyErr_SetObject)
EXPORT_FUNC(PyErr_SetString)