]> granicus.if.org Git - python/commitdiff
Issue #8407: Add pthread_kill(), sigpending() and sigwait() functions to the
authorVictor Stinner <victor.stinner@haypocalc.com>
Sat, 7 May 2011 23:46:11 +0000 (01:46 +0200)
committerVictor Stinner <victor.stinner@haypocalc.com>
Sat, 7 May 2011 23:46:11 +0000 (01:46 +0200)
signal module.

Doc/library/os.rst
Doc/library/signal.rst
Doc/whatsnew/3.3.rst
Lib/test/test_signal.py
Misc/NEWS
Modules/signalmodule.c
configure
configure.in
pyconfig.h.in

index eca51dcb8a69241029d5b8faef3b71484236b9ac..76095807dc23c71ea07718e0bb14be1616585d1e 100644 (file)
@@ -2284,6 +2284,8 @@ written in Python, such as a mail server's external command delivery program.
    will be set to *sig*. The Windows version of :func:`kill` additionally takes
    process handles to be killed.
 
+   See also :func:`signal.pthread_kill`.
+
    .. versionadded:: 3.2
       Windows support.
 
index f318cfa22729f19931349f6105eb3e852820e447..473eda280761df2ffabeb247de7ba1ff2b51168e 100644 (file)
@@ -179,6 +179,29 @@ The :mod:`signal` module defines the following functions:
    will then be called.  Returns nothing.  Not on Windows. (See the Unix man page
    :manpage:`signal(2)`.)
 
+   See also :func:`sigwait` and :func:`sigpending`.
+
+
+.. function:: pthread_kill(thread_id, signum)
+
+   Send the signal *signum* to the thread *thread_id*, another thread in the same
+   process as the caller.  The signal is asynchronously directed to thread.
+
+   *thread_id* can be read from the :attr:`~threading.Thread.ident` attribute
+   of :attr:`threading.Thread`.  For example,
+   ``threading.current_thread().ident`` gives the identifier of the current
+   thread.
+
+   If *signum* is 0, then no signal is sent, but error checking is still
+   performed; this can be used to check if a thread is still running.
+
+   Availability: Unix (see the man page :manpage:`pthread_kill(3)` for further
+   information).
+
+   See also :func:`os.kill`.
+
+   .. versionadded:: 3.3
+
 
 .. function:: pthread_sigmask(how, mask)
 
@@ -206,6 +229,8 @@ The :mod:`signal` module defines the following functions:
    Availability: Unix. See the man page :manpage:`sigprocmask(3)` and
    :manpage:`pthread_sigmask(3)` for further information.
 
+   See also :func:`pause`, :func:`sigpending` and :func:`sigwait`.
+
    .. versionadded:: 3.3
 
 
@@ -283,6 +308,34 @@ The :mod:`signal` module defines the following functions:
    :const:`SIGTERM`. A :exc:`ValueError` will be raised in any other case.
 
 
+.. function:: sigpending()
+
+   Examine the set of signals that are pending for delivery to the calling
+   thread (i.e., the signals which have been raised while blocked).  Return the
+   set of the pending signals.
+
+   Availability: Unix (see the man page :manpage:`sigpending(2)` for further
+   information).
+
+   See also :func:`pause`, :func:`pthread_sigmask` and :func:`sigwait`.
+
+   .. versionadded:: 3.3
+
+
+.. function:: sigwait(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
+   (removes it from the pending list of signals), and returns the signal number.
+
+   Availability: Unix (see the man page :manpage:`sigwait(3)` for further
+   information).
+
+   See also :func:`pause`, :func:`pthread_sigmask` and :func:`sigpending`.
+
+   .. versionadded:: 3.3
+
+
 .. _signal-example:
 
 Example
index 0ab4fc86bec668cdbcb4825637b5a02a33bd1d53..14f06af802c70ee227088adbbb2c14b5e174803c 100644 (file)
@@ -123,10 +123,13 @@ sys
 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.
+* The :mod:`signal` module has a new functions:
 
-  (Contributed by Jean-Paul Calderone in :issue:`8407`)
+  * :func:`~signal.pthread_sigmask`: fetch and/or change the signal mask of the
+    calling thread (Contributed by Jean-Paul Calderone in :issue:`8407`) ;
+  * :func:`~signal.pthread_kill`: send a signal to a thread ;
+  * :func:`~signal.sigpending`: examine pending functions ;
+  * :func:`~signal.sigwait`: wait a signal.
 
 
 Optimizations
index c74f001b1a20d1f3e3d74a37ccc89619635aff4e..c1054ed834bf317361d7fe7ed17f3c5506b16e6f 100644 (file)
@@ -8,6 +8,10 @@ import signal
 import subprocess
 import traceback
 import sys, os, time, errno
+try:
+    import threading
+except ImportError:
+    threading = None
 
 if sys.platform in ('os2', 'riscos'):
     raise unittest.SkipTest("Can't test signal on %s" % sys.platform)
@@ -187,7 +191,7 @@ class InterProcessSignalTests(unittest.TestCase):
 
 
 @unittest.skipIf(sys.platform == "win32", "Not valid on Windows")
-class BasicSignalTests(unittest.TestCase):
+class PosixTests(unittest.TestCase):
     def trivial_signal_handler(self, *args):
         pass
 
@@ -484,50 +488,121 @@ class ItimerTest(unittest.TestCase):
         self.assertEqual(self.hndl_called, True)
 
 
-@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
-                     'need signal.pthread_sigmask()')
 class PendingSignalsTests(unittest.TestCase):
     """
-    Tests for the pthread_sigmask() function.
+    Test pthread_sigmask(), pthread_kill(), sigpending() and sigwait()
+    functions.
     """
+    def setUp(self):
+        self.has_pthread_kill = hasattr(signal, 'pthread_kill')
+
     def handler(self, signum, frame):
         1/0
 
     def read_sigmask(self):
         return signal.pthread_sigmask(signal.SIG_BLOCK, [])
 
-    def test_pthread_sigmask_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 can_test_blocked_signals(self, skip):
+        """
+        Check if a blocked signal can be raised to the main thread without
+        calling its signal handler. We need pthread_kill() or exactly one
+        thread (the main thread).
 
-    def test_pthread_sigmask(self):
-        import faulthandler
-        pid = os.getpid()
-        signum = signal.SIGUSR1
+        Return True if it's possible. Otherwise, return False and print a
+        warning if skip is False, or raise a SkipTest exception if skip is
+        True.
+        """
+        if self.has_pthread_kill:
+            return True
 
         # The fault handler timeout thread masks all signals. If the main
         # thread masks also SIGUSR1, all threads mask this signal. In this
         # case, if we send SIGUSR1 to the process, the signal is pending in the
         # main or the faulthandler timeout thread.  Unblock SIGUSR1 in the main
         # thread calls the signal handler only if the signal is pending for the
-        # main thread.
-        #
-        # Stop the faulthandler timeout thread to workaround this problem.
-        # Another solution would be to send the signal directly to the main
-        # thread using pthread_kill(), but Python doesn't expose this
-        # function.
+        # main thread. Stop the faulthandler timeout thread to workaround this
+        # problem.
+        import faulthandler
         faulthandler.cancel_dump_tracebacks_later()
 
-        # Issue #11998: The _tkinter module loads the Tcl library which creates
-        # a thread waiting events in select(). This thread receives signals
-        # blocked by all other threads. We cannot test blocked signals if the
-        # _tkinter module is loaded.
-        can_test_blocked_signals = ('_tkinter' not in sys.modules)
-        if not can_test_blocked_signals:
-            print("WARNING: _tkinter is loaded, cannot test signals "
-                  "blocked by pthread_sigmask() (issue #11998)")
+        # Issue #11998: The _tkinter module loads the Tcl library which
+        # creates a thread waiting events in select(). This thread receives
+        # signals blocked by all other threads. We cannot test blocked
+        # signals
+        if '_tkinter' in sys.modules:
+            message = ("_tkinter is loaded and pthread_kill() is missing, "
+                       "cannot test blocked signals (issue #11998)")
+            if skip:
+                self.skipTest(message)
+            else:
+                print("WARNING: %s" % message)
+            return False
+        return True
+
+    def kill(self, signum):
+        if self.has_pthread_kill:
+            tid = threading.current_thread().ident
+            signal.pthread_kill(tid, signum)
+        else:
+            pid = os.getpid()
+            os.kill(pid, signum)
+
+    @unittest.skipUnless(hasattr(signal, 'sigpending'),
+                         'need signal.sigpending()')
+    def test_sigpending_empty(self):
+        self.assertEqual(signal.sigpending(), set())
+
+    @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
+                         'need signal.pthread_sigmask()')
+    @unittest.skipUnless(hasattr(signal, 'sigpending'),
+                         'need signal.sigpending()')
+    def test_sigpending(self):
+        self.can_test_blocked_signals(True)
+
+        signum = signal.SIGUSR1
+        old_handler = signal.signal(signum, self.handler)
+        self.addCleanup(signal.signal, signum, old_handler)
+
+        signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
+        self.kill(signum)
+        self.assertEqual(signal.sigpending(), {signum})
+        with self.assertRaises(ZeroDivisionError):
+            signal.pthread_sigmask(signal.SIG_UNBLOCK, [signum])
+
+    @unittest.skipUnless(hasattr(signal, 'pthread_kill'),
+                         'need signal.pthread_kill()')
+    def test_pthread_kill(self):
+        signum = signal.SIGUSR1
+        current = threading.current_thread().ident
+
+        old_handler = signal.signal(signum, self.handler)
+        self.addCleanup(signal.signal, signum, old_handler)
+
+        with self.assertRaises(ZeroDivisionError):
+            signal.pthread_kill(current, signum)
+
+    @unittest.skipUnless(hasattr(signal, 'sigwait'),
+                         'need signal.sigwait()')
+    def test_sigwait(self):
+        old_handler = signal.signal(signal.SIGALRM, self.handler)
+        self.addCleanup(signal.signal, signal.SIGALRM, old_handler)
+
+        signal.alarm(1)
+        self.assertEqual(signal.sigwait([signal.SIGALRM]), signal.SIGALRM)
+
+    @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
+                         'need signal.pthread_sigmask()')
+    def test_pthread_sigmask_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(OSError, signal.pthread_sigmask, 1700, [])
+
+    @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
+                         'need signal.pthread_sigmask()')
+    def test_pthread_sigmask(self):
+        test_blocked_signals = self.can_test_blocked_signals(False)
+        signum = signal.SIGUSR1
 
         # Install our signal handler
         old_handler = signal.signal(signum, self.handler)
@@ -537,13 +612,13 @@ class PendingSignalsTests(unittest.TestCase):
         old_mask = signal.pthread_sigmask(signal.SIG_UNBLOCK, [signum])
         self.addCleanup(signal.pthread_sigmask, signal.SIG_SETMASK, old_mask)
         with self.assertRaises(ZeroDivisionError):
-            os.kill(pid, signum)
+            self.kill(signum)
 
         # Block and then raise SIGUSR1. The signal is blocked: the signal
         # handler is not called, and the signal is now pending
         signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
-        if can_test_blocked_signals:
-            os.kill(pid, signum)
+        if test_blocked_signals:
+            self.kill(signum)
 
         # Check the new mask
         blocked = self.read_sigmask()
@@ -551,14 +626,14 @@ class PendingSignalsTests(unittest.TestCase):
         self.assertEqual(old_mask ^ blocked, {signum})
 
         # Unblock SIGUSR1
-        if can_test_blocked_signals:
+        if test_blocked_signals:
             with self.assertRaises(ZeroDivisionError):
                 # unblock the pending signal calls immediatly the signal handler
                 signal.pthread_sigmask(signal.SIG_UNBLOCK, [signum])
         else:
             signal.pthread_sigmask(signal.SIG_UNBLOCK, [signum])
         with self.assertRaises(ZeroDivisionError):
-            os.kill(pid, signum)
+            self.kill(signum)
 
         # Check the new mask
         unblocked = self.read_sigmask()
@@ -570,7 +645,7 @@ class PendingSignalsTests(unittest.TestCase):
 
 def test_main():
     try:
-        support.run_unittest(BasicSignalTests, InterProcessSignalTests,
+        support.run_unittest(PosixTests, InterProcessSignalTests,
                              WakeupSignalTests, SiginterruptTest,
                              ItimerTest, WindowsSignalTests,
                              PendingSignalsTests)
index f8da3b044c328a4c016a9d11475158eddae5f1c9..274931cc6d1d84f965a7039da3251403ff261439 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -140,6 +140,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #8407: Add pthread_kill(), sigpending() and sigwait() functions to the
+  signal module.
+
 - Issue #11927: SMTP_SSL now uses port 465 by default as documented.  Patch
   by Kasun Herath.
 
index c8626adf7e56f6db2ff212964dc740ec0b3606b4..72850799b5b7c98eb42de18f4bfc26a9545094f4 100644 (file)
@@ -503,7 +503,7 @@ PyDoc_STRVAR(getitimer_doc,
 Returns current value of given itimer.");
 #endif
 
-#ifdef PYPTHREAD_SIGMASK
+#if defined(PYPTHREAD_SIGMASK) || defined(HAVE_SIGWAIT)
 /* Convert an iterable to a sigset.
    Return 0 on success, return -1 and raise an exception on error. */
 
@@ -551,7 +551,9 @@ error:
     Py_XDECREF(iterator);
     return result;
 }
+#endif
 
+#if defined(PYPTHREAD_SIGMASK) || defined(HAVE_SIGPENDING)
 static PyObject*
 sigset_to_set(sigset_t mask)
 {
@@ -585,7 +587,9 @@ sigset_to_set(sigset_t mask)
     }
     return result;
 }
+#endif
 
+#ifdef PYPTHREAD_SIGMASK
 static PyObject *
 signal_pthread_sigmask(PyObject *self, PyObject *args)
 {
@@ -603,7 +607,7 @@ signal_pthread_sigmask(PyObject *self, PyObject *args)
     err = pthread_sigmask(how, &mask, &previous);
     if (err != 0) {
         errno = err;
-        PyErr_SetFromErrno(PyExc_RuntimeError);
+        PyErr_SetFromErrno(PyExc_OSError);
         return NULL;
     }
 
@@ -621,6 +625,88 @@ Fetch and/or change the signal mask of the calling thread.");
 #endif   /* #ifdef PYPTHREAD_SIGMASK */
 
 
+#ifdef HAVE_SIGPENDING
+static PyObject *
+signal_sigpending(PyObject *self)
+{
+    int err;
+    sigset_t mask;
+    err = sigpending(&mask);
+    if (err)
+        return PyErr_SetFromErrno(PyExc_OSError);
+    return sigset_to_set(mask);
+}
+
+PyDoc_STRVAR(signal_sigpending_doc,
+"sigpending() -> list\n\
+\n\
+Examine pending signals.");
+#endif   /* #ifdef HAVE_SIGPENDING */
+
+
+#ifdef HAVE_SIGWAIT
+static PyObject *
+signal_sigwait(PyObject *self, PyObject *args)
+{
+    PyObject *signals;
+    sigset_t set;
+    int err, signum;
+
+    if (!PyArg_ParseTuple(args, "O:sigwait", &signals))
+        return NULL;
+
+    if (iterable_to_sigset(signals, &set))
+        return NULL;
+
+    err = sigwait(&set, &signum);
+    if (err) {
+        errno = err;
+        return PyErr_SetFromErrno(PyExc_OSError);
+    }
+
+    return PyLong_FromLong(signum);
+}
+
+PyDoc_STRVAR(signal_sigwait_doc,
+"sigwait(sigset) -> signum\n\
+\n\
+Wait a signal.");
+#endif   /* #ifdef HAVE_SIGPENDING */
+
+
+#if defined(HAVE_PTHREAD_KILL) && defined(WITH_THREAD)
+static PyObject *
+signal_pthread_kill(PyObject *self, PyObject *args)
+{
+    long tid;
+    int signum;
+    int err;
+
+    if (!PyArg_ParseTuple(args, "li:pthread_kill", &tid, &signum))
+        return NULL;
+
+    err = pthread_kill(tid, signum);
+    if (err != 0) {
+        errno = err;
+        PyErr_SetFromErrno(PyExc_OSError);
+        return NULL;
+    }
+
+    /* the signal may have been send to the current thread */
+    if (PyErr_CheckSignals())
+        return NULL;
+
+    Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(signal_pthread_kill_doc,
+"pthread_kill(thread_id, signum)\n\
+\n\
+Send a signal to a thread.");
+#endif   /* #if defined(HAVE_PTHREAD_KILL) && defined(WITH_THREAD) */
+
+
+
 /* List of functions defined in the module */
 static PyMethodDef signal_methods[] = {
 #ifdef HAVE_ALARM
@@ -644,9 +730,21 @@ static PyMethodDef signal_methods[] = {
 #endif
     {"default_int_handler", signal_default_int_handler,
      METH_VARARGS, default_int_handler_doc},
+#if defined(HAVE_PTHREAD_KILL) && defined(WITH_THREAD)
+    {"pthread_kill",            (PyCFunction)signal_pthread_kill,
+     METH_VARARGS, signal_pthread_kill_doc},
+#endif
 #ifdef PYPTHREAD_SIGMASK
     {"pthread_sigmask",         (PyCFunction)signal_pthread_sigmask,
      METH_VARARGS, signal_pthread_sigmask_doc},
+#endif
+#ifdef HAVE_SIGPENDING
+    {"sigpending",              (PyCFunction)signal_sigpending,
+     METH_NOARGS, signal_sigpending_doc},
+#endif
+#ifdef HAVE_SIGWAIT
+    {"sigwait",                 (PyCFunction)signal_sigwait,
+     METH_VARARGS, signal_sigwait_doc},
 #endif
     {NULL,                      NULL}           /* sentinel */
 };
index 6b4e83b889ccdc43fec49d2340e61164dd2e00d9..4111aea6e287d5f95da215d2ca76729474c9a075 100755 (executable)
--- a/configure
+++ b/configure
@@ -9258,11 +9258,12 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
  initgroups kill killpg lchmod lchown lockf linkat lstat lutimes mbrtowc mkdirat mkfifo \
  mkfifoat mknod mknodat mktime mremap nice openat pathconf pause plock poll \
  posix_fallocate posix_fadvise pread \
- pthread_init putenv pwrite readlink readlinkat readv realpath renameat \
+ pthread_init pthread_kill putenv pwrite readlink readlinkat readv realpath renameat \
  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 sigrelse snprintf strftime strlcpy symlinkat sync \
+ sigaction sigaltstack siginterrupt sigpending \
+ sigrelse sigwait 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
index 146289ff4b88cddfa2a3dfdada349a4344c547da..ede5ea4e815a445aea42b7afc81c311a6ab2f917 100644 (file)
@@ -2503,11 +2503,12 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
  initgroups kill killpg lchmod lchown lockf linkat lstat lutimes mbrtowc mkdirat mkfifo \
  mkfifoat mknod mknodat mktime mremap nice openat pathconf pause plock poll \
  posix_fallocate posix_fadvise pread \
- pthread_init putenv pwrite readlink readlinkat readv realpath renameat \
+ pthread_init pthread_kill putenv pwrite readlink readlinkat readv realpath renameat \
  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 sigrelse snprintf strftime strlcpy symlinkat sync \
+ sigaction sigaltstack siginterrupt sigpending \
+ sigrelse sigwait 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)
index 89565a340d178f05a786332279f968c01bf348bf..89b0c33e8a6446e43d7b671964a2e5c7294f7edb 100644 (file)
 /* Define to 1 if you have the `pthread_sigmask' function. */
 #undef HAVE_PTHREAD_SIGMASK
 
+/* Define to 1 if you have the `pthread_kill' function. */
+#undef HAVE_PTHREAD_KILL
+
 /* Define to 1 if you have the <pty.h> header file. */
 #undef HAVE_PTY_H
 
 /* Define to 1 if you have the `siginterrupt' function. */
 #undef HAVE_SIGINTERRUPT
 
+/* Define to 1 if you have the `sigpending' function. */
+#undef HAVE_SIGPENDING
+
 /* Define to 1 if you have the <signal.h> header file. */
 #undef HAVE_SIGNAL_H
 
 /* Define to 1 if you have the `sigrelse' function. */
 #undef HAVE_SIGRELSE
 
+/* Define to 1 if you have the `sigwait' function. */
+#undef HAVE_SIGWAIT
+
 /* Define to 1 if you have the `snprintf' function. */
 #undef HAVE_SNPRINTF