[Python-checkins] cpython: Issue #12303: Add sigwaitinfo() and sigtimedwait() to the signal module.

ross.lagerwall python-checkins at python.org
Sat Jun 25 12:15:08 CEST 2011


http://hg.python.org/cpython/rev/1137f7021b95
changeset:   70964:1137f7021b95
user:        Ross Lagerwall <rosslagerwall at gmail.com>
date:        Sat Jun 25 12:13:40 2011 +0200
summary:
  Issue #12303: Add sigwaitinfo() and sigtimedwait() to the signal module.

files:
  Doc/library/signal.rst  |   45 +++++++-
  Doc/whatsnew/3.3.rst    |    4 +
  Lib/test/test_signal.py |   99 +++++++++++++++--
  Misc/NEWS               |    2 +
  Modules/signalmodule.c  |  154 +++++++++++++++++++++++++++-
  configure               |    4 +-
  configure.in            |    4 +-
  pyconfig.h.in           |    6 +
  8 files changed, 295 insertions(+), 23 deletions(-)


diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst
--- a/Doc/library/signal.rst
+++ b/Doc/library/signal.rst
@@ -179,7 +179,8 @@
    will then be called.  Returns nothing.  Not on Windows. (See the Unix man page
    :manpage:`signal(2)`.)
 
-   See also :func:`sigwait` and :func:`sigpending`.
+   See also :func:`sigwait`, :func:`sigwaitinfo`, :func:`sigtimedwait` and
+   :func:`sigpending`.
 
 
 .. function:: pthread_kill(thread_id, signum)
@@ -334,7 +335,47 @@
    Availability: Unix (see the man page :manpage:`sigwait(3)` for further
    information).
 
-   See also :func:`pause`, :func:`pthread_sigmask` and :func:`sigpending`.
+   See also :func:`pause`, :func:`pthread_sigmask`, :func:`sigpending`,
+   :func:`sigwaitinfo` and :func:`sigtimedwait`.
+
+   .. versionadded:: 3.3
+
+
+.. function:: sigwaitinfo(sigset)
+
+   Suspend execution of the calling thread until the delivery of one of the
+   signals specified in the signal set *sigset*.  The function accepts the
+   signal and removes it from the pending list of signals. If one of the
+   signals in *sigset* is already pending for the calling thread, the function
+   will return immediately with information about that signal. The signal
+   handler is not called for the delivered signal. The function raises an
+   :exc:`OSError` with error number set to :const:`errno.EINTR` if it is
+   interrupted by a signal that is not in *sigset*.
+
+   The return value is an object representing the data contained in the
+   :c:type:`siginfo_t` structure, namely: :attr:`si_signo`, :attr:`si_code`,
+   :attr:`si_errno`, :attr:`si_pid`, :attr:`si_uid`, :attr:`si_status`,
+   :attr:`si_band`.
+
+   Availability: Unix (see the man page :manpage:`sigwaitinfo(2)` for further
+   information).
+
+   See also :func:`pause`, :func:`sigwait` and :func:`sigtimedwait`.
+
+   .. versionadded:: 3.3
+
+
+.. function:: sigtimedwait(sigset, (timeout_sec, timeout_nsec))
+
+   Like :func:`sigtimedwait`, but takes a tuple of ``(seconds, nanoseconds)``
+   as an additional argument specifying a timeout. If both *timeout_sec* and
+   *timeout_nsec* are specified as :const:`0`, a poll is performed. Returns
+   :const:`None` if a timeout occurs.
+
+   Availability: Unix (see the man page :manpage:`sigtimedwait(2)` for further
+   information).
+
+   See also :func:`pause`, :func:`sigwait` and :func:`sigwaitinfo`.
 
    .. versionadded:: 3.3
 
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
@@ -169,6 +169,10 @@
   * :func:`~signal.pthread_kill`: send a signal to a thread ;
   * :func:`~signal.sigpending`: examine pending functions ;
   * :func:`~signal.sigwait`: wait a signal.
+  * :func:`~signal.sigwaitinfo`: wait for a signal, returning detailed
+    information about it.
+  * :func:`~signal.sigtimedwait`: like :func:`~signal.sigwaitinfo` but with a
+    timeout.
 
 * The signal handler writes the signal number as a single byte instead of
   a nul byte into the wakeup file descriptor. So it is possible to wait more
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
@@ -520,6 +520,7 @@
     functions.
     """
     def setUp(self):
+        self.hndl_called = False
         self.has_pthread_kill = hasattr(signal, 'pthread_kill')
 
     def handler(self, signum, frame):
@@ -607,45 +608,35 @@
         with self.assertRaises(ZeroDivisionError):
             signal.pthread_kill(current, signum)
 
-    @unittest.skipUnless(hasattr(signal, 'sigwait'),
-                         'need signal.sigwait()')
     @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
                          'need signal.pthread_sigmask()')
     @unittest.skipUnless(hasattr(os, 'fork'), 'need os.fork()')
-    def test_sigwait(self):
-        def test(signum):
-            signal.alarm(1)
-            received = signal.sigwait([signum])
-            if received != signum:
-                print("sigwait() received %s, not %s"
-                      % (received, signum),
-                      file=sys.stderr)
-                os._exit(1)
-
+    def wait_helper(self, test, handler, blocked=signal.SIGALRM):
         signum = signal.SIGALRM
 
-        # sigwait must be called with the signal blocked: since the current
+        # sig*wait* must be called with the signal blocked: since the current
         # process might have several threads running, we fork() a child process
         # to have a single thread.
         pid = os.fork()
         if pid == 0:
             # child: block and wait the signal
             try:
-                signal.signal(signum, self.handler)
-                signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
+                signal.signal(signum, handler)
+                signal.pthread_sigmask(signal.SIG_BLOCK, [blocked])
 
                 # Do the tests
                 test(signum)
 
                 # The handler must not be called on unblock
                 try:
-                    signal.pthread_sigmask(signal.SIG_UNBLOCK, [signum])
+                    signal.pthread_sigmask(signal.SIG_UNBLOCK, [blocked])
                 except ZeroDivisionError:
                     print("the signal handler has been called",
                           file=sys.stderr)
                     os._exit(1)
             except BaseException as err:
                 print("error: {}".format(err), file=sys.stderr)
+                sys.stderr.flush()
                 os._exit(1)
             else:
                 os._exit(0)
@@ -655,6 +646,82 @@
 
     @unittest.skipUnless(hasattr(signal, 'sigwait'),
                          'need signal.sigwait()')
+    def test_sigwait(self):
+        def test(signum):
+            signal.alarm(1)
+            self.assertEqual(signum, signal.sigwait([signum]))
+
+        self.wait_helper(test, self.handler)
+
+    @unittest.skipUnless(hasattr(signal, 'sigwaitinfo'),
+                         'need signal.sigwaitinfo()')
+    def test_sigwaitinfo(self):
+        def test(signum):
+            signal.alarm(1)
+            info = signal.sigwaitinfo([signum])
+            self.assertEqual(signum, info.si_signo)
+
+        self.wait_helper(test, self.handler)
+
+    @unittest.skipUnless(hasattr(signal, 'sigtimedwait'),
+                         'need signal.sigtimedwait()')
+    def test_sigtimedwait(self):
+        def test(signum):
+            signal.alarm(1)
+            info = signal.sigtimedwait([signum], (10, 1000))
+            self.assertEqual(signum, info.si_signo)
+
+        self.wait_helper(test, self.handler)
+
+    # check that polling with sigtimedwait works
+    @unittest.skipUnless(hasattr(signal, 'sigtimedwait'),
+                         'need signal.sigtimedwait()')
+    def test_sigtimedwait_poll(self):
+        def test(signum):
+            self.kill(signum)
+            info = signal.sigtimedwait([signum], (0, 0))
+            self.assertEqual(signum, info.si_signo)
+
+        self.wait_helper(test, self.handler)
+
+    @unittest.skipUnless(hasattr(signal, 'sigtimedwait'),
+                         'need signal.sigtimedwait()')
+    def test_sigtimedwait_timeout(self):
+        def test(signum):
+            self.assertEqual(None, signal.sigtimedwait([signum], (1, 35500)))
+
+        self.wait_helper(test, self.handler)
+
+    @unittest.skipUnless(hasattr(signal, 'sigtimedwait'),
+                         'need signal.sigtimedwait()')
+    def test_sigtimedwait_negative_timeout(self):
+        signum = signal.SIGALRM
+        self.assertRaises(ValueError, signal.sigtimedwait, [signum], (-1, -1))
+        self.assertRaises(ValueError, signal.sigtimedwait, [signum], (0, -1))
+        self.assertRaises(ValueError, signal.sigtimedwait, [signum], (-1, 0))
+
+    def alarm_handler(self, signum, frame):
+        self.hndl_called = True
+
+    @unittest.skipUnless(hasattr(signal, 'sigwaitinfo'),
+                         'need signal.sigwaitinfo()')
+    def test_sigwaitinfo_interrupted(self):
+        def test(signum):
+            signal.alarm(1)
+            try:
+                signal.sigwaitinfo([signal.SIGUSR1])
+            except OSError as e:
+                if e.errno == errno.EINTR:
+                    self.assertTrue(self.hndl_called)
+                else:
+                    self.fail("Expected EINTR to be raised by sigwaitinfo")
+            else:
+                self.fail("Expected EINTR to be raised by sigwaitinfo")
+
+        self.wait_helper(test, self.alarm_handler, signal.SIGUSR1)
+
+    @unittest.skipUnless(hasattr(signal, 'sigwait'),
+                         'need signal.sigwait()')
     @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
                          'need signal.pthread_sigmask()')
     @unittest.skipIf(threading is None, "test needs threading module")
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -200,6 +200,8 @@
 Library
 -------
 
+- Issue #12303: Add sigwaitinfo() and sigtimedwait() to the signal module.
+
 - Issue #12404: Remove C89 incompatible code from mmap module. Patch by Akira
   Kitada.
 
diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c
--- a/Modules/signalmodule.c
+++ b/Modules/signalmodule.c
@@ -507,7 +507,8 @@
 Returns current value of given itimer.");
 #endif
 
-#if defined(PYPTHREAD_SIGMASK) || defined(HAVE_SIGWAIT)
+#if defined(PYPTHREAD_SIGMASK) || defined(HAVE_SIGWAIT) || \
+        defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT)
 /* Convert an iterable to a sigset.
    Return 0 on success, return -1 and raise an exception on error. */
 
@@ -679,6 +680,140 @@
 Wait a signal.");
 #endif   /* #ifdef HAVE_SIGPENDING */
 
+#if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT)
+static int initialized;
+static PyStructSequence_Field struct_siginfo_fields[] = {
+    {"si_signo",        "signal number"},
+    {"si_code",         "signal code"},
+    {"si_errno",        "errno associated with this signal"},
+    {"si_pid",          "sending process ID"},
+    {"si_uid",          "real user ID of sending process"},
+    {"si_status",       "exit value or signal"},
+    {"si_band",         "band event for SIGPOLL"},
+    {0}
+};
+
+PyDoc_STRVAR(struct_siginfo__doc__,
+"struct_siginfo: Result from sigwaitinfo or sigtimedwait.\n\n\
+This object may be accessed either as a tuple of\n\
+(si_signo, si_code, si_errno, si_pid, si_uid, si_status, si_band),\n\
+or via the attributes si_signo, si_code, and so on.");
+
+static PyStructSequence_Desc struct_siginfo_desc = {
+    "signal.struct_siginfo",           /* name */
+    struct_siginfo__doc__,       /* doc */
+    struct_siginfo_fields,       /* fields */
+    7          /* n_in_sequence */
+};
+
+static PyTypeObject SiginfoType;
+
+static PyObject *
+fill_siginfo(siginfo_t *si)
+{
+    PyObject *result = PyStructSequence_New(&SiginfoType);
+    if (!result)
+        return NULL;
+
+    PyStructSequence_SET_ITEM(result, 0, PyLong_FromLong((long)(si->si_signo)));
+    PyStructSequence_SET_ITEM(result, 1, PyLong_FromLong((long)(si->si_code)));
+    PyStructSequence_SET_ITEM(result, 2, PyLong_FromLong((long)(si->si_errno)));
+    PyStructSequence_SET_ITEM(result, 3, PyLong_FromPid(si->si_pid));
+    PyStructSequence_SET_ITEM(result, 4, PyLong_FromLong((long)(si->si_uid)));
+    PyStructSequence_SET_ITEM(result, 5,
+                                PyLong_FromLong((long)(si->si_status)));
+    PyStructSequence_SET_ITEM(result, 6, PyLong_FromLong(si->si_band));
+    if (PyErr_Occurred()) {
+        Py_DECREF(result);
+        return NULL;
+    }
+
+    return result;
+}
+#endif
+
+#ifdef HAVE_SIGWAITINFO
+static PyObject *
+signal_sigwaitinfo(PyObject *self, PyObject *args)
+{
+    PyObject *signals;
+    sigset_t set;
+    siginfo_t si;
+    int err;
+
+    if (!PyArg_ParseTuple(args, "O:sigwaitinfo", &signals))
+        return NULL;
+
+    if (iterable_to_sigset(signals, &set))
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
+    err = sigwaitinfo(&set, &si);
+    Py_END_ALLOW_THREADS
+    if (err == -1)
+        return PyErr_SetFromErrno(PyExc_OSError);
+
+    return fill_siginfo(&si);
+}
+
+PyDoc_STRVAR(signal_sigwaitinfo_doc,
+"sigwaitinfo(sigset) -> struct_siginfo\n\
+\n\
+Wait synchronously for a signal until one of the signals in *sigset* is\n\
+delivered.\n\
+Returns a struct_siginfo containing information about the signal.");
+#endif   /* #ifdef HAVE_SIGWAITINFO */
+
+#ifdef HAVE_SIGTIMEDWAIT
+static PyObject *
+signal_sigtimedwait(PyObject *self, PyObject *args)
+{
+    PyObject *signals, *timeout;
+    struct timespec buf;
+    sigset_t set;
+    siginfo_t si;
+    int err;
+
+    if (!PyArg_ParseTuple(args, "OO:sigtimedwait", &signals, &timeout))
+        return NULL;
+
+    if (!PyTuple_Check(timeout) || PyTuple_Size(timeout) != 2) {
+        PyErr_SetString(PyExc_TypeError,
+            "sigtimedwait() arg 2 must be a tuple "
+            "(timeout_sec, timeout_nsec)");
+        return NULL;
+    } else if (!PyArg_ParseTuple(timeout, "ll:sigtimedwait",
+                    &(buf.tv_sec), &(buf.tv_nsec)))
+        return NULL;
+
+    if (buf.tv_sec < 0 || buf.tv_nsec < 0) {
+        PyErr_SetString(PyExc_ValueError, "timeout must be non-negative");
+        return NULL;
+    }
+
+    if (iterable_to_sigset(signals, &set))
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
+    err = sigtimedwait(&set, &si, &buf);
+    Py_END_ALLOW_THREADS
+    if (err == -1) {
+        if (errno == EAGAIN)
+            Py_RETURN_NONE;
+        else
+            return PyErr_SetFromErrno(PyExc_OSError);
+    }
+
+    return fill_siginfo(&si);
+}
+
+PyDoc_STRVAR(signal_sigtimedwait_doc,
+"sigtimedwait(sigset, (timeout_sec, timeout_nsec)) -> struct_siginfo\n\
+\n\
+Like sigwaitinfo(), but with a timeout specified as a tuple of (seconds,\n\
+nanoseconds).");
+#endif   /* #ifdef HAVE_SIGTIMEDWAIT */
+
 
 #if defined(HAVE_PTHREAD_KILL) && defined(WITH_THREAD)
 static PyObject *
@@ -752,6 +887,14 @@
     {"sigwait",                 (PyCFunction)signal_sigwait,
      METH_VARARGS, signal_sigwait_doc},
 #endif
+#ifdef HAVE_SIGWAITINFO
+    {"sigwaitinfo",             (PyCFunction)signal_sigwaitinfo,
+     METH_VARARGS, signal_sigwaitinfo_doc},
+#endif
+#ifdef HAVE_SIGTIMEDWAIT
+    {"sigtimedwait",            (PyCFunction)signal_sigtimedwait,
+     METH_VARARGS, signal_sigtimedwait_doc},
+#endif
     {NULL,                      NULL}           /* sentinel */
 };
 
@@ -820,6 +963,15 @@
     if (m == NULL)
         return NULL;
 
+#if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT)
+    if (!initialized)
+        PyStructSequence_InitType(&SiginfoType, &struct_siginfo_desc);
+
+    Py_INCREF((PyObject*) &SiginfoType);
+    PyModule_AddObject(m, "struct_siginfo", (PyObject*) &SiginfoType);
+    initialized = 1;
+#endif
+
     /* Add some symbolic constants to the module */
     d = PyModule_GetDict(m);
 
diff --git a/configure b/configure
--- a/configure
+++ b/configure
@@ -9367,8 +9367,8 @@
  select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
  setgid sethostname \
  setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
- sigaction sigaltstack siginterrupt sigpending \
- sigrelse sigwait snprintf strftime strlcpy symlinkat sync \
+ sigaction sigaltstack siginterrupt sigpending sigrelse \
+ sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy symlinkat sync \
  sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
  truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
  wcscoll wcsftime wcsxfrm writev _getpty
diff --git a/configure.in b/configure.in
--- a/configure.in
+++ b/configure.in
@@ -2552,8 +2552,8 @@
  select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
  setgid sethostname \
  setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
- sigaction sigaltstack siginterrupt sigpending \
- sigrelse sigwait snprintf strftime strlcpy symlinkat sync \
+ sigaction sigaltstack siginterrupt sigpending sigrelse \
+ sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy symlinkat sync \
  sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
  truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
  wcscoll wcsftime wcsxfrm writev _getpty)
diff --git a/pyconfig.h.in b/pyconfig.h.in
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -743,9 +743,15 @@
 /* Define to 1 if you have the `sigrelse' function. */
 #undef HAVE_SIGRELSE
 
+/* Define to 1 if you have the `sigtimedwait' function. */
+#undef HAVE_SIGTIMEDWAIT
+
 /* Define to 1 if you have the `sigwait' function. */
 #undef HAVE_SIGWAIT
 
+/* Define to 1 if you have the `sigwaitinfo' function. */
+#undef HAVE_SIGWAITINFO
+
 /* Define to 1 if you have the `snprintf' function. */
 #undef HAVE_SNPRINTF
 

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


More information about the Python-checkins mailing list