From b3e7219abfdb52321b7e8f1f57499ece23a7d2f8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 8 May 2011 01:46:11 +0200 Subject: Issue #8407: Add pthread_kill(), sigpending() and sigwait() functions to the signal module. --- Doc/library/os.rst | 2 + Doc/library/signal.rst | 53 ++++++++++++++++++ Doc/whatsnew/3.3.rst | 9 ++-- Lib/test/test_signal.py | 141 ++++++++++++++++++++++++++++++++++++------------ Misc/NEWS | 3 ++ Modules/signalmodule.c | 102 ++++++++++++++++++++++++++++++++++- configure | 5 +- configure.in | 5 +- pyconfig.h.in | 9 ++++ 9 files changed, 287 insertions(+), 42 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index eca51dc..7609580 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -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. diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst index f318cfa..473eda2 100644 --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -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 diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index 0ab4fc8..14f06af 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -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 diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index c74f001..c1054ed 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -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) diff --git a/Misc/NEWS b/Misc/NEWS index f8da3b0..274931c 100644 --- 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. diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index c8626ad..7285079 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -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,10 +730,22 @@ 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 */ }; diff --git a/configure b/configure index 6b4e83b..4111aea 100755 --- 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 diff --git a/configure.in b/configure.in index 146289f..ede5ea4 100644 --- a/configure.in +++ b/configure.in @@ -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) diff --git a/pyconfig.h.in b/pyconfig.h.in index 89565a3..89b0c33 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -590,6 +590,9 @@ /* 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 header file. */ #undef HAVE_PTY_H @@ -719,12 +722,18 @@ /* 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 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 -- cgit v0.12