diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -119,6 +119,16 @@ The variables defined in the :mod:`signa in user and kernel space. SIGPROF is delivered upon expiration. +.. data:: SIG_BLOCK +.. data:: SIG_UNBLOCK +.. data:: SIG_SETMASK + + Constants for :func:`pthread_sigmask` to decide how to change the signal + mask of the current thread. + + .. versionadded:: 3.3 + + The :mod:`signal` module defines one exception: .. exception:: ItimerError @@ -161,6 +171,27 @@ The :mod:`signal` module defines the fol :manpage:`signal(2)`.) +.. function:: pthread_sigmask(how, mask) + + Fetch and/or change the signal mask of the calling thread. The signal mask + is the set of signals whose delivery is currently blocked for the caller. + + The behavior of the call is dependent on the value of *how*, as follows. + + * :data:`SIG_BLOCK`: The set of blocked signals is the union of the current + set and the *mask* argument. + * :data:`SIG_UNBLOCK`: The signals in *mask* are removed from the current + set of blocked signals. It is permissible to attempt to unblock a + signal which is not blocked. + * :data:`SIG_SETMASK`: The set of blocked signals is set to the *mask* + argument. + + Availability: Unix (see the man page :manpage:`pthread_sigmask(3)` for + further information). + + .. versionadded:: 3.3 + + .. function:: setitimer(which, seconds[, interval]) Sets given interval timer (one of :const:`signal.ITIMER_REAL`, diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -118,7 +118,16 @@ threading * The :mod:`threading` module has a new :func:`~threading._info` function which provides informations about the thread implementation. - (:issue:`11223`) +(:issue:`11223`) + +signal +------ + +* The :mod:`signal` module has a new :func:`~signal.pthread_sigmask` function + to fetch and/or change the signal mask of the calling thread. + +(Contributed by Jean-Paul Calderone in :issue:`8407`) + Optimizations ============= diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2627,6 +2627,8 @@ class SignalsTest(unittest.TestCase): in the latter.""" read_results = [] def _read(): + if hasattr(signal, 'pthread_sigmask'): + signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGALRM]) s = os.read(r, 1) read_results.append(s) t = threading.Thread(target=_read) diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -208,6 +208,57 @@ class BasicSignalTests(unittest.TestCase signal.signal(signal.SIGHUP, hup) self.assertEqual(signal.getsignal(signal.SIGHUP), hup) + @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'), + 'need signal.pthread_sigmask()') + def test_pthread_signal(self): + pid = os.getpid() + signum = signal.SIGUSR1 + + global tripped + tripped = False + + def handler(signum, frame): + global tripped + tripped = True + + def read_sigmask(): + return signal.pthread_sigmask(signal.SIG_BLOCK, []) + + old_handler = signal.signal(signum, handler) + try: + old_mask = signal.pthread_sigmask(signal.SIG_UNBLOCK, [signum]) + try: + # check our signal handler + os.kill(pid, signum) + self.assertTrue(tripped) + + # block SIGUSR1 + tripped = False + signal.pthread_sigmask(signal.SIG_BLOCK, [signum]) + os.kill(pid, signum) + self.assertFalse(tripped) + + # check the mask + blocked = read_sigmask() + self.assertIn(signum, blocked) + self.assertEqual(set(old_mask) ^ set(blocked), {signum}) + + # unblock SIGUSR1 + signal.pthread_sigmask(signal.SIG_UNBLOCK, [signum]) + os.kill(pid, signum) + self.assertTrue(tripped) + + # check the mask + unblocked = read_sigmask() + self.assertNotIn(signum, unblocked) + self.assertEqual(set(blocked) ^ set(unblocked), {signum}) + self.assertSequenceEqual(old_mask, unblocked) + finally: + signal.pthread_sigmask(signal.SIG_SETMASK, old_mask) + finally: + signal.signal(signum, old_handler) + del tripped + @unittest.skipUnless(sys.platform == "win32", "Windows specific") class WindowsSignalTests(unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -110,6 +110,9 @@ Core and Builtins Library ------- +- Issue #8407: Add signal.pthread_sigmask() function to fetch and/or change the + signal mask of the calling thread. + - Issue #11223: Add threading._info() function providing informations about the thread implementation. @@ -490,6 +493,10 @@ Extensions Tests ----- +- Issue #8407, #11859: Fix tests of test_io using threads and an alarm: use + pthread_sigmask() to ensure that only the main thread receives the SIGALRM + signal. + - Issue #11223: Skip test_lock_acquire_interruption() and test_rlock_acquire_interruption() of test_threadsignals if a thread lock is implemented using a POSIX mutex and a POSIX condition variable. A POSIX diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -22,6 +22,14 @@ #include #endif +#if defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK) +# define USE_PTHREAD_SIGMASK +#endif + +#if defined(USE_PTHREAD_SIGMASK) && defined(HAVE_PTHREAD_H) +# include +#endif + #ifndef SIG_ERR #define SIG_ERR ((PyOS_sighandler_t)(-1)) #endif @@ -495,6 +503,107 @@ PyDoc_STRVAR(getitimer_doc, Returns current value of given itimer."); #endif +#ifdef USE_PTHREAD_SIGMASK +/* Convert an iterable to a sigset. + Return 0 on success, return -1 and raise an exception on error. */ + +static int +iterable_to_sigset(PyObject *iterable, sigset_t *mask) +{ + int result = -1; + PyObject *iterator, *item; + long signum; + int err; + + sigemptyset(mask); + + iterator = PyObject_GetIter(iterable); + if (iterator == NULL) + goto error; + + while (1) + { + item = PyIter_Next(iterator); + if (item == NULL) { + if (PyErr_Occurred()) + goto error; + else + break; + } + + signum = PyLong_AsLong(item); + Py_DECREF(item); + if (signum == -1 && PyErr_Occurred()) + goto error; + if (0 < signum && signum < NSIG) + err = sigaddset(mask, (int)signum); + else + err = 1; + if (err) { + PyErr_Format(PyExc_ValueError, + "signal number %ld out of range", signum); + goto error; + } + } + result = 0; + +error: + Py_XDECREF(iterator); + return result; +} + +static PyObject * +signal_pthread_sigmask(PyObject *self, PyObject *args) +{ + int how, sig; + PyObject *signals, *result, *signum; + sigset_t mask, previous; + + if (!PyArg_ParseTuple(args, "iO:pthread_sigmask", &how, &signals)) + return NULL; + + if (iterable_to_sigset(signals, &mask)) + return NULL; + + if (pthread_sigmask(how, &mask, &previous) != 0) { + PyErr_Format(PyExc_ValueError, "invalid how value"); + return NULL; + } + + result = PyList_New(0); + if (result == NULL) + return NULL; + + for (sig = 1; sig < NSIG; sig++) { + if (sigismember(&previous, sig) != 1) + continue; + + /* Handle the case where it is a member by adding the signal to + the result list. Ignore the other cases because they mean the + signal isn't a member of the mask or the signal was invalid, + and an invalid signal must have been our fault in constructing + the loop boundaries. */ + signum = PyLong_FromLong(sig); + if (signum == NULL) { + Py_DECREF(result); + return NULL; + } + if (PyList_Append(result, signum) == -1) { + Py_DECREF(signum); + Py_DECREF(result); + return NULL; + } + Py_DECREF(signum); + } + return result; +} + +PyDoc_STRVAR(signal_pthread_sigmask_doc, +"pthread_sigmask(how, mask) -> old mask\n\ +\n\ +Fetch and/or change the signal mask of the calling thread."); +#endif /* #ifdef USE_PTHREAD_SIGMASK */ + /* List of functions defined in the module */ static PyMethodDef signal_methods[] = { @@ -515,10 +624,14 @@ static PyMethodDef signal_methods[] = { #endif #ifdef HAVE_PAUSE {"pause", (PyCFunction)signal_pause, - METH_NOARGS,pause_doc}, + METH_NOARGS, pause_doc}, #endif {"default_int_handler", signal_default_int_handler, METH_VARARGS, default_int_handler_doc}, +#ifdef USE_PTHREAD_SIGMASK + {"pthread_sigmask", (PyCFunction)signal_pthread_sigmask, + METH_VARARGS, signal_pthread_sigmask_doc}, +#endif {NULL, NULL} /* sentinel */ }; @@ -603,6 +716,27 @@ PyInit_signal(void) goto finally; Py_DECREF(x); +#ifdef SIG_BLOCK + x = PyLong_FromLong(SIG_BLOCK); + if (!x || PyDict_SetItemString(d, "SIG_BLOCK", x) < 0) + goto finally; + Py_DECREF(x); +#endif + +#ifdef SIG_UNBLOCK + x = PyLong_FromLong(SIG_UNBLOCK); + if (!x || PyDict_SetItemString(d, "SIG_UNBLOCK", x) < 0) + goto finally; + Py_DECREF(x); +#endif + +#ifdef SIG_SETMASK + x = PyLong_FromLong(SIG_SETMASK); + if (!x || PyDict_SetItemString(d, "SIG_SETMASK", x) < 0) + goto finally; + Py_DECREF(x); +#endif + x = IntHandler = PyDict_GetItemString(d, "default_int_handler"); if (!x) goto finally;