[Python-checkins] cpython: Issue #8407, issue #11859: Add signal.pthread_sigmask() function to fetch

victor.stinner python-checkins at python.org
Sat Apr 30 15:22:07 CEST 2011


http://hg.python.org/cpython/rev/28b9702a83d1
changeset:   69711:28b9702a83d1
user:        Victor Stinner <victor.stinner at haypocalc.com>
date:        Sat Apr 30 15:21:58 2011 +0200
summary:
  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.

files:
  Doc/library/signal.rst  |   53 ++++++++++-
  Doc/whatsnew/3.3.rst    |   11 ++-
  Lib/test/test_io.py     |    2 +
  Lib/test/test_signal.py |   56 +++++++++++-
  Misc/NEWS               |    7 +
  Modules/signalmodule.c  |  139 +++++++++++++++++++++++++++-
  6 files changed, 262 insertions(+), 6 deletions(-)


diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst
--- a/Doc/library/signal.rst
+++ b/Doc/library/signal.rst
@@ -13,9 +13,6 @@
   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 @@
    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 @@
    :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`,
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 @@
 * 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
 =============
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 @@
         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
@@ -483,11 +483,65 @@
         # and the handler should have been called
         self.assertEqual(self.hndl_called, True)
 
+
+ at 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()
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -127,6 +127,9 @@
 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 @@
 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.
diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c
--- a/Modules/signalmodule.c
+++ b/Modules/signalmodule.c
@@ -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 @@
 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 @@
 #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 @@
         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;

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list