mirror of
https://github.com/python/cpython.git
synced 2024-12-12 19:33:52 +08:00
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.
This commit is contained in:
parent
d5c355ccc7
commit
a929335961
@ -13,9 +13,6 @@ rules for working with signals and their handlers:
|
||||
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 @@ The variables defined in the :mod:`signal` module are:
|
||||
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 @@ The :mod:`signal` module defines the following functions:
|
||||
: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`,
|
||||
|
@ -118,7 +118,16 @@ sys
|
||||
* 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
|
||||
=============
|
||||
|
@ -2627,6 +2627,8 @@ class SignalsTest(unittest.TestCase):
|
||||
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)
|
||||
|
@ -483,11 +483,65 @@ class ItimerTest(unittest.TestCase):
|
||||
# and the handler should have been called
|
||||
self.assertEqual(self.hndl_called, True)
|
||||
|
||||
|
||||
@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()
|
||||
|
||||
|
@ -127,6 +127,9 @@ Core and Builtins
|
||||
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 @@ Extensions
|
||||
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.
|
||||
|
@ -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 @@ PyDoc_STRVAR(getitimer_doc,
|
||||
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 @@ static PyMethodDef signal_methods[] = {
|
||||
#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 @@ PyInit_signal(void)
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user