Issue #8407, issue #11859: Add signal.pthread_sigmask() function to fetch

and/or change the signal mask of the calling thread.

Fix also tests of test_io using threads and an alarm: use pthread_sigmask() to
ensure that the SIGALRM signal is received by the main thread.

Original patch written by Jean-Paul Calderone.
This commit is contained in:
Victor Stinner 2011-04-30 15:21:58 +02:00
parent d5c355ccc7
commit a929335961
6 changed files with 262 additions and 6 deletions

View File

@ -13,9 +13,6 @@ rules for working with signals and their handlers:
underlying implementation), with the exception of the handler for
:const:`SIGCHLD`, which follows the underlying implementation.
* There is no way to "block" signals temporarily from critical sections (since
this is not supported by all Unix flavors).
* Although Python signal handlers are called asynchronously as far as the Python
user is concerned, they can only occur between the "atomic" instructions of the
Python interpreter. This means that signals arriving during long calculations
@ -119,6 +116,28 @@ The variables defined in the :mod:`signal` module are:
in user and kernel space. SIGPROF is delivered upon expiration.
.. data:: SIG_BLOCK
A possible value for the *how* parameter to :func:`pthread_sigmask`
indicating that signals are to be blocked.
.. versionadded:: 3.3
.. data:: SIG_UNBLOCK
A possible value for the *how* parameter to :func:`pthread_sigmask`
indicating that signals are to be unblocked.
.. versionadded:: 3.3
.. data:: SIG_SETMASK
A possible value for the *how* parameter to :func:`pthread_sigmask`
indicating that the signal mask is to be replaced.
.. versionadded:: 3.3
The :mod:`signal` module defines one exception:
.. exception:: ItimerError
@ -161,6 +180,34 @@ The :mod:`signal` module defines the following functions:
: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 old signal mask is returned.
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.
*mask* is a list of signal numbers (e.g. [:const:`signal.SIGINT`,
:const:`signal.SIGTERM`]).
For example, ``signal.pthread_sigmask(signal.SIG_BLOCK, [])`` reads the
signal mask of the calling thread.
Availability: Unix. See the man page :manpage:`sigprocmask(3)` and
: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`,

View File

@ -118,7 +118,16 @@ sys
* The :mod:`sys` module has a new :func:`~sys.thread_info` :term:`struct
sequence` holding 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
=============

View File

@ -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)

View File

@ -483,11 +483,65 @@ class ItimerTest(unittest.TestCase):
# and the handler should have been called
self.assertEqual(self.hndl_called, True)
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
'need signal.pthread_sigmask()')
class PthreadSigmaskTests(unittest.TestCase):
def test_arguments(self):
self.assertRaises(TypeError, signal.pthread_sigmask)
self.assertRaises(TypeError, signal.pthread_sigmask, 1)
self.assertRaises(TypeError, signal.pthread_sigmask, 1, 2, 3)
self.assertRaises(RuntimeError, signal.pthread_sigmask, 1700, [])
def test_block_unlock(self):
pid = os.getpid()
signum = signal.SIGUSR1
def handler(signum, frame):
handler.tripped = True
handler.tripped = False
def read_sigmask():
return signal.pthread_sigmask(signal.SIG_BLOCK, [])
old_handler = signal.signal(signum, handler)
self.addCleanup(signal.signal, signum, old_handler)
# unblock SIGUSR1, copy the old mask and test our signal handler
old_mask = signal.pthread_sigmask(signal.SIG_UNBLOCK, [signum])
self.addCleanup(signal.pthread_sigmask, signal.SIG_SETMASK, old_mask)
os.kill(pid, signum)
self.assertTrue(handler.tripped)
# block SIGUSR1
handler.tripped = False
signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
os.kill(pid, signum)
self.assertFalse(handler.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(handler.tripped)
# check the mask
unblocked = read_sigmask()
self.assertNotIn(signum, unblocked)
self.assertEqual(set(blocked) ^ set(unblocked), {signum})
self.assertSequenceEqual(old_mask, unblocked)
def test_main():
try:
support.run_unittest(BasicSignalTests, InterProcessSignalTests,
WakeupSignalTests, SiginterruptTest,
ItimerTest, WindowsSignalTests)
ItimerTest, WindowsSignalTests,
PthreadSigmaskTests)
finally:
support.reap_children()

View File

@ -127,6 +127,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 #11858: configparser.ExtendedInterpolation expected lower-case section
names.
@ -531,6 +534,10 @@ Extensions
Tests
-----
- Issue #8407, #11859: Fix tests of test_io using threads and an alarm: use
pthread_sigmask() to ensure that the SIGALRM signal is received by the main
thread.
- Issue #11811: Factor out detection of IPv6 support on the current host
and make it available as ``test.support.IPV6_ENABLED``. Patch by
Charles-François Natali.

View File

@ -22,6 +22,14 @@
#include <sys/time.h>
#endif
#if defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK)
# define PYPTHREAD_SIGMASK
#endif
#if defined(PYPTHREAD_SIGMASK) && defined(HAVE_PTHREAD_H)
# include <pthread.h>
#endif
#ifndef SIG_ERR
#define SIG_ERR ((PyOS_sighandler_t)(-1))
#endif
@ -495,6 +503,110 @@ PyDoc_STRVAR(getitimer_doc,
Returns current value of given itimer.");
#endif
#ifdef PYPTHREAD_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;
int err;
if (!PyArg_ParseTuple(args, "iO:pthread_sigmask", &how, &signals))
return NULL;
if (iterable_to_sigset(signals, &mask))
return NULL;
err = pthread_sigmask(how, &mask, &previous);
if (err != 0) {
errno = err;
PyErr_SetFromErrno(PyExc_RuntimeError);
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 PYPTHREAD_SIGMASK */
/* List of functions defined in the module */
static PyMethodDef signal_methods[] = {
@ -515,10 +627,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 PYPTHREAD_SIGMASK
{"pthread_sigmask", (PyCFunction)signal_pthread_sigmask,
METH_VARARGS, signal_pthread_sigmask_doc},
#endif
{NULL, NULL} /* sentinel */
};
@ -603,6 +719,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;