Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP (don't merge!): bpo-21895: Experiment signal handlers in any thread #768

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 8 additions & 13 deletions Doc/library/signal.rst
Expand Up @@ -46,18 +46,17 @@ This has consequences:
signal handlers will be called when the calculation finishes.


.. _signals-and-threads:


Signals and threads
^^^^^^^^^^^^^^^^^^^

Python signal handlers are always executed in the main Python thread,
even if the signal was received in another thread. This means that signals
can't be used as a means of inter-thread communication. You can use
the synchronization primitives from the :mod:`threading` module instead.
Only the main thread is allowed to set a new signal handler.

You can use the synchronization primitives from the :mod:`threading` module
instead.

Besides, only the main thread is allowed to set a new signal handler.
.. versionchanged:: 3.7
Python signal handlers can now be executed in threads other than the main
Python thread.


Module contents
Expand Down Expand Up @@ -221,11 +220,7 @@ The :mod:`signal` module defines the following functions:

Send the signal *signalnum* to the thread *thread_id*, another thread in the
same process as the caller. The target thread can be executing any code
(Python or not). However, if the target thread is executing the Python
interpreter, the Python signal handlers will be :ref:`executed by the main
thread <signals-and-threads>`. Therefore, the only point of sending a
signal to a particular Python thread would be to force a running system call
to fail with :exc:`InterruptedError`.
(Python or not).

Use :func:`threading.get_ident()` or the :attr:`~threading.Thread.ident`
attribute of :class:`threading.Thread` objects to get a suitable value
Expand Down
6 changes: 3 additions & 3 deletions Modules/_winapi.c
Expand Up @@ -1350,9 +1350,9 @@ _winapi_WaitForMultipleObjects_impl(PyObject *module, PyObject *handle_seq,
handles[i] = h;
Py_DECREF(v);
}
/* If this is the main thread then make the wait interruptible
by Ctrl-C unless we are waiting for *all* handles */
if (!wait_flag && _PyOS_IsMainThread()) {
/* Make the wait interruptible by Ctrl-C unless
we are waiting for *all* handles */
if (!wait_flag) {
sigint_event = _PyOS_SigintEvent();
assert(sigint_event != NULL);
handles[nhandles++] = sigint_event;
Expand Down
32 changes: 11 additions & 21 deletions Modules/signalmodule.c
Expand Up @@ -417,13 +417,6 @@ signal_signal_impl(PyObject *module, int signalnum, PyObject *handler)
PyErr_SetString(PyExc_ValueError, "invalid signal value");
return NULL;
}
#endif
#ifdef WITH_THREAD
if (PyThread_get_thread_ident() != main_thread) {
PyErr_SetString(PyExc_ValueError,
"signal only works in main thread");
return NULL;
}
#endif
if (signalnum < 1 || signalnum >= NSIG) {
PyErr_SetString(PyExc_ValueError,
Expand All @@ -436,7 +429,8 @@ signal_signal_impl(PyObject *module, int signalnum, PyObject *handler)
func = SIG_DFL;
else if (!PyCallable_Check(handler)) {
PyErr_SetString(PyExc_TypeError,
"signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object");
"signal handler must be signal.SIG_IGN, "
"signal.SIG_DFL, or a callable object");
return NULL;
}
else
Expand Down Expand Up @@ -550,14 +544,6 @@ signal_set_wakeup_fd(PyObject *self, PyObject *args)
return NULL;
#endif

#ifdef WITH_THREAD
if (PyThread_get_thread_ident() != main_thread) {
PyErr_SetString(PyExc_ValueError,
"set_wakeup_fd only works in main thread");
return NULL;
}
#endif

#ifdef MS_WINDOWS
is_socket = 0;
if (sockfd != INVALID_FD) {
Expand Down Expand Up @@ -1501,11 +1487,6 @@ PyErr_CheckSignals(void)
if (!is_tripped)
return 0;

#ifdef WITH_THREAD
if (PyThread_get_thread_ident() != main_thread)
return 0;
#endif

/*
* The is_tripped variable is meant to speed up the calls to
* PyErr_CheckSignals (both directly or via pending calls) when no
Expand Down Expand Up @@ -1553,7 +1534,16 @@ PyErr_CheckSignals(void)
void
PyErr_SetInterrupt(void)
{
#if defined(HAVE_PTHREAD_KILL) && defined(WITH_THREAD)
if (PyThread_get_thread_ident() == main_thread) {
trip_signal(SIGINT);
}
else {
pthread_kill(main_thread, SIGINT);
}
#else
trip_signal(SIGINT);
#endif
}

void
Expand Down
6 changes: 2 additions & 4 deletions Modules/timemodule.c
Expand Up @@ -1457,11 +1457,9 @@ pysleep(_PyTime_t secs)
return -1;
}

/* Allow sleep(0) to maintain win32 semantics, and as decreed
* by Guido, only the main thread can be interrupted.
*/
/* Allow sleep(0) to maintain win32 semantics. */
ul_millis = (unsigned long)millisecs;
if (ul_millis == 0 || !_PyOS_IsMainThread()) {
if (ul_millis == 0) {
Py_BEGIN_ALLOW_THREADS
Sleep(ul_millis);
Py_END_ALLOW_THREADS
Expand Down
2 changes: 2 additions & 0 deletions Python/ceval.c
Expand Up @@ -355,6 +355,8 @@ PyEval_RestoreThread(PyThreadState *tstate)
callback.
*/

/* FIXME: move this array into PyThreadState to avoid race conditions between
threads when Py_AddPendingCall() is called by signal handlers. */
#define NPENDINGCALLS 32
static struct {
int (*func)(void *);
Expand Down