summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@haypocalc.com>2011-05-08 01:46:11 +0200
committerVictor Stinner <victor.stinner@haypocalc.com>2011-05-08 01:46:11 +0200
commit755cb646d6fcf4af8f468bd60a3443710987b19b (patch)
tree7a464644565d802fa453e862f744509fa42b3200
parent520fec9c4d68f15350d91e3c203dadfd9ed2f2cb (diff)
downloadcpython-755cb646d6fcf4af8f468bd60a3443710987b19b.tar.gz
Issue #8407: Add pthread_kill(), sigpending() and sigwait() functions to the
signal module.
-rw-r--r--Doc/library/os.rst2
-rw-r--r--Doc/library/signal.rst53
-rw-r--r--Doc/whatsnew/3.3.rst9
-rw-r--r--Lib/test/test_signal.py141
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/signalmodule.c102
-rwxr-xr-xconfigure5
-rw-r--r--configure.in5
-rw-r--r--pyconfig.h.in9
9 files changed, 287 insertions, 42 deletions
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index eca51dcb8a..76095807dc 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 f318cfa227..473eda2807 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 0ab4fc86be..14f06af802 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 c74f001b1a..c1054ed834 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 f8da3b044c..274931cc6d 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 c8626adf7e..72850799b5 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 6b4e83b889..4111aea6e2 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 146289ff4b..ede5ea4e81 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 89565a340d..89b0c33e8a 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 <pty.h> 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 <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