]> granicus.if.org Git - python/commitdiff
Issue #8407, issue #11859: Add signal.pthread_sigmask() function to fetch
authorVictor Stinner <victor.stinner@haypocalc.com>
Sat, 30 Apr 2011 13:21:58 +0000 (15:21 +0200)
committerVictor Stinner <victor.stinner@haypocalc.com>
Sat, 30 Apr 2011 13:21:58 +0000 (15:21 +0200)
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.

Doc/library/signal.rst
Doc/whatsnew/3.3.rst
Lib/test/test_io.py
Lib/test/test_signal.py
Misc/NEWS
Modules/signalmodule.c

index 698b1e74f495a00bbda23a40e62a6860a2a7337a..ffe7f09d489860ed1fcd166e937aaef072f0a836 100644 (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`,
index 93da9d8555a6c1bde299f37d07d34ecaeeef06d8..7b4988673c0c5eca566708369911118f210acdff 100644 (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
 =============
index 18214924f59738036a27c6dceb194312ba4116e5..8d293d0407225dbdb69e9a8a01188ae29daccf70 100644 (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)
index f64bd4c99ad6442486d0c6c69fbfb26640dc9010..809da3a8b65dc0614eeb4b1eb40c6ceb4bffa67a 100644 (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()
 
index 37ac3e75b9d18387a018684c9e8377336d43defc..1230a680c3c41a44e14e36d477e50baa403a8859 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.
index 00a83b497cd7ed26b693b71f778074e88e7a403c..305261cb01ebe4dcbba0673ab5090a5d10ccd82a 100644 (file)
 #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;