merge forward from the python 2.x branch

This commit is contained in:
Jean-Paul Calderone 2010-06-19 19:54:48 +00:00
parent 2aa3af4a16
commit 867c435460
7 changed files with 474 additions and 7 deletions

View File

@ -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
@ -115,6 +112,46 @@ 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:`sigprocmask`
indicating that signals are to be blocked.
.. versionadded:: 2.7
.. data:: SIG_UNBLOCK
A possible value for the *how* parameter to :func:`sigprocmask`
indicating that signals are to be unblocked.
.. versionadded:: 2.7
.. data:: SIG_SETMASK
A possible value for the *how* parameter to :func:`sigprocmask`
indicating that the signal mask is to be replaced.
.. versionadded:: 2.7
.. data:: SFD_CLOEXEC
A possible flag in the *flags* parameter to :func:`signalfd` which causes
the new file descriptor to be marked as close-on-exec.
.. versionadded:: 2.7
.. data:: SFD_NONBLOCK
A possible flag in the *flags* parameter to :func:`signalfd` which causes
the new file description to be set non-blocking.
.. versionadded:: 2.7
The :mod:`signal` module defines one exception:
.. exception:: ItimerError
@ -227,6 +264,44 @@ The :mod:`signal` module defines the following functions:
attribute descriptions in the :mod:`inspect` module).
.. function:: signalfd(fd, mask[, flags])
Create a new file descriptor on which to receive signals or modify the
mask of such a file descriptor previously created by this function.
Availability: Linux (See the manpage :manpage:`signalfd(2)` for further
information).
If *fd* is ``-1``, a new file descriptor will be created. Otherwise,
*fd* must be a file descriptor previously returned by this function.
*mask* is a list of signal numbers which will trigger data on this file
descriptor.
*flags* is a bit mask which may include any :const:`signal.SFD_*` flag.
.. versionadded:: 2.7
.. function:: sigprocmask(how, mask)
Set the signal mask for the process. The old signal mask is returned.
Availability: Unix (See the Unix man page :manpage:`sigprocmask(2)` and
:manpage:`pthread_sigmask(2)`.)
If *how* is :const:`signal.SIG_BLOCK`, the signals in the mask are added
to the set of blocked signals.
If *how* is :const:`signal.SIG_UNBLOCK`, the signals in the mask are
removed from the set of blocked signals.
If *how* is :const:`signal.SIG_SETMASK`, the signals in the mask are set
as blocked and the signals not in the mask are set as unblocked.
*mask* is a list of signal numbers (eg :const:`signal.SIGUSR1`).
.. versionadded:: 2.7
.. _signal-example:
Example

View File

@ -462,9 +462,210 @@ class ItimerTest(unittest.TestCase):
# and the handler should have been called
self.assertEqual(self.hndl_called, True)
class SomeException(Exception):
"""
A unique exception class to be raised by a signal handler to verify that the
signal handler was invoked.
"""
def raiser(*args):
"""A signal handler which raises SomeException."""
raise SomeException()
class SigprocmaskTests(unittest.TestCase):
"""Tests for sigprocmask."""
def _handle_sigusr1(self):
old_handler = signal.signal(signal.SIGUSR1, raiser)
self.addCleanup(signal.signal, signal.SIGUSR1, old_handler)
def test_signature(self):
"""When invoked with other than two arguments, sigprocmask raises
TypeError.
"""
self.assertRaises(TypeError, signal.sigprocmask)
self.assertRaises(TypeError, signal.sigprocmask, 1)
self.assertRaises(TypeError, signal.sigprocmask, 1, 2, 3)
def test_invalid_how(self):
"""If a value other than SIG_BLOCK, SIG_UNBLOCK, or SIG_SETMASK is
passed for the how argument to sigprocmask, ValueError is raised.
"""
message = "value specified for how \(1700\) invalid"
with self.assertRaisesRegexp(ValueError, message):
signal.sigprocmask(1700, [])
def test_invalid_signal_iterable(self):
"""If iterating over the value passed for the signals parameter to
sigprocmask raises an exception, sigprocmask raises that exception.
"""
class BrokenIter(object):
def __iter__(self):
raise RuntimeError("my __iter__ is broken")
with self.assertRaisesRegexp(RuntimeError, "my __iter__ is broken"):
signal.sigprocmask(signal.SIG_BLOCK, BrokenIter())
def test_invalid_signal(self):
"""If an object in the iterable passed for the signals parameter to
sigprocmask isn't an integer, TypeError is raised."""
with self.assertRaisesRegexp(TypeError, "an integer is required"):
signal.sigprocmask(signal.SIG_BLOCK, [object()])
def test_return_previous_mask(self):
"""sigprocmask returns a list of the signals previously masked.
"""
previous = signal.sigprocmask(signal.SIG_BLOCK, [1, 3, 5])
result = signal.sigprocmask(signal.SIG_BLOCK, previous)
self.assertEquals(result, [1, 3, 5])
def test_block(self):
"""When invoked with SIG_BLOCK, sigprocmask blocks the signals in the
sigmask list.
"""
self._handle_sigusr1()
previous = signal.sigprocmask(signal.SIG_BLOCK, [signal.SIGUSR1])
os.kill(os.getpid(), signal.SIGUSR1)
with self.assertRaises(SomeException):
# Expect to receive SIGUSR1 after unblocking it.
signal.sigprocmask(signal.SIG_SETMASK, previous)
def test_unblock(self):
"""When invoked with SIG_UNBLOCK, sigprocmask unblocks the signals in
the sigmask list.
"""
self._handle_sigusr1()
previous = signal.sigprocmask(signal.SIG_BLOCK, [signal.SIGUSR1])
self.addCleanup(signal.sigprocmask, signal.SIG_SETMASK, previous)
signal.sigprocmask(signal.SIG_UNBLOCK, [signal.SIGUSR1])
with self.assertRaises(SomeException):
os.kill(os.getpid(), signal.SIGUSR1)
def test_long_signals(self):
"""sigprocmask accepts signal numbers as instances of long."""
previous = signal.sigprocmask(
signal.SIG_SETMASK, [long(signal.SIGUSR1), long(signal.SIGUSR2)])
masked = signal.sigprocmask(signal.SIG_SETMASK, previous)
self.assertEquals(masked, [signal.SIGUSR1, signal.SIGUSR2])
class SignalfdTests(unittest.TestCase):
"""
Tests for signal.signalfd.
"""
def test_signature(self):
"""When invoked with fewer than two arguments or more than three,
signalfd raises TypeError.
"""
self.assertRaises(TypeError, signal.signalfd)
self.assertRaises(TypeError, signal.signalfd, 1)
self.assertRaises(TypeError, signal.signalfd, 1, 2, 3, 4)
def test_create_signalfd(self):
"""When invoked with a file descriptor of -1, signalfd allocates a new
file descriptor for signal information delivery and returns it.
"""
fd = signal.signalfd(-1, [])
self.assertTrue(isinstance(fd, int))
os.close(fd)
def test_non_iterable_signals(self):
"""If an object which is not iterable is passed for the sigmask list
argument to signalfd, the exception raised by trying to iterate over
that object is raised.
"""
self.assertRaises(TypeError, signal.signalfd, -1, object())
def test_non_integer_signals(self):
"""If any non-integer values are included in the sigmask list argument
to signalfd, the exception raised by the attempt to convert them to an
integer is raised.
"""
self.assertRaises(TypeError, signal.signalfd, -1, [object()])
def test_out_of_range_signal(self):
"""If a signal number that is out of the valid range is included in the
sigmask list argument to signalfd, ValueError is raised.
"""
message = "signal number -2 out of range"
with self.assertRaisesRegexp(ValueError, message):
signal.signalfd(-1, [-2])
def test_handle_signals(self):
"""After signalfd is called, if a signal is received which was in the
sigmask list passed to that call, information about the signal can be
read from the fd returned by that call.
"""
fd = signal.signalfd(-1, [signal.SIGUSR2])
self.addCleanup(os.close, fd)
previous = signal.sigprocmask(signal.SIG_BLOCK, [signal.SIGUSR2])
self.addCleanup(signal.sigprocmask, signal.SIG_SETMASK, previous)
os.kill(os.getpid(), signal.SIGUSR2)
bytes = os.read(fd, 128)
self.assertTrue(bytes)
def test_close_on_exec(self):
"""If the bit mask passed as the 3rd argument to signalfd includes
SFD_CLOEXEC, the returned file descriptor has FD_CLOEXEC set on it.
"""
import fcntl
fd = signal.signalfd(-1, [], signal.SFD_CLOEXEC)
self.addCleanup(os.close, fd)
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
self.assertTrue(flags & fcntl.FD_CLOEXEC)
def test_nonblocking(self):
"""If the bit mask passed as the 3rd argument to signalfd includes
SFD_NOBLOCK, the file description referenced by the returned file
descriptor has O_NONBLOCK set on it.
"""
import fcntl
fd = signal.signalfd(-1, [], signal.SFD_NONBLOCK)
self.addCleanup(os.close, fd)
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
self.assertTrue(flags & os.O_NONBLOCK)
def test_default_flags(self):
"""If an empty bit mask is passed as the 3rd argument to signalfd,
neither FD_CLOEXEC nor O_NONBLOCK is set on the resulting file
descriptor/description.
"""
import fcntl
fd = signal.signalfd(-1, [])
self.addCleanup(os.close, fd)
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
self.assertFalse(flags & fcntl.FD_CLOEXEC)
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
self.assertFalse(flags & os.O_NONBLOCK)
def test_main():
support.run_unittest(BasicSignalTests, InterProcessSignalTests,
WakeupSignalTests, SiginterruptTest, ItimerTest)
support.run_unittest(
BasicSignalTests, InterProcessSignalTests,
WakeupSignalTests, SiginterruptTest, ItimerTest, SignalfdTests,
SigprocmaskTests)
if __name__ == "__main__":

View File

@ -1025,6 +1025,11 @@ Library
- Issue #5949: added check for correct lineends in input from IMAP server
in imaplib.
- Issue #8407: The signal module gains the ``signalfd()`` and
``sigprocmask(2)`` functions providing access to the signalfd(2) and
sigprocmask(2) system calls respectively on Linux systems which implement
them.
- Add count() and reverse() methods to collections.deque().
- Fix variations of extending deques: d.extend(d) d.extendleft(d) d+=d

View File

@ -22,6 +22,10 @@
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SIGNALFD
#include <sys/signalfd.h>
#endif
#ifndef SIG_ERR
#define SIG_ERR ((PyOS_sighandler_t)(-1))
@ -464,6 +468,144 @@ Returns current value of given itimer.");
#endif
static int
_iterable_to_mask(PyObject *iterable, sigset_t *mask)
{
static const char* range_format = "signal number %d out of range";
char range_buffer[1024];
int result = 0;
PyObject *item, *iterator = NULL;
sigemptyset(mask);
iterator = PyObject_GetIter(iterable);
if (iterator == NULL) {
result = -1;
goto error;
}
while ((item = PyIter_Next(iterator))) {
int signum = PyInt_AsLong(item);
Py_DECREF(item);
if (signum == -1 && PyErr_Occurred()) {
result = -1;
goto error;
}
if (sigaddset(mask, signum) == -1) {
PyOS_snprintf(range_buffer, sizeof(range_buffer), range_format, signum);
PyErr_SetString(PyExc_ValueError, range_buffer);
result = -1;
goto error;
}
}
error:
Py_XDECREF(iterator);
return result;
}
#if defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK)
# define PY_SIGMASK pthread_sigmask
#elif defined(HAVE_SIGPROCMASK)
# define PY_SIGMASK sigprocmask
#endif
#ifdef PY_SIGMASK
static PyObject *
signal_sigprocmask(PyObject *self, PyObject *args)
{
static const char* how_format = "value specified for how (%d) invalid";
char how_buffer[1024];
int how, sig;
PyObject *signals, *result, *signum;
sigset_t mask, previous;
if (!PyArg_ParseTuple(args, "iO:sigprocmask", &how, &signals)) {
return NULL;
}
if (_iterable_to_mask(signals, &mask) == -1) {
return NULL;
}
if (PY_SIGMASK(how, &mask, &previous) != 0) {
PyOS_snprintf(how_buffer, sizeof(how_buffer), how_format, how);
PyErr_SetString(PyExc_ValueError, how_buffer);
return NULL;
}
result = PyList_New(0);
if (result == NULL) {
return NULL;
}
for (sig = 1; sig < NSIG; ++sig) {
if (sigismember(&previous, sig) == 1) {
/* 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 = PyInt_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(sigprocmask_doc,
"sigprocmask(how, mask) -> old mask\n\
\n\
Examine and change blocked signals.");
#endif
#ifdef HAVE_SIGNALFD
static PyObject *
signal_signalfd(PyObject *self, PyObject *args)
{
int result, flags = 0;
sigset_t mask;
int fd;
PyObject *signals;
if (!PyArg_ParseTuple(args, "iO|i:signalfd", &fd, &signals, &flags)) {
return NULL;
}
if (_iterable_to_mask(signals, &mask) == -1) {
return NULL;
}
result = signalfd(-1, &mask, flags);
if (result == -1) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
return PyInt_FromLong(result);
}
PyDoc_STRVAR(signalfd_doc,
"signalfd(fd, mask, flags)\n\
\n\
Create a file descriptor for accepting signals.");
#endif
/* List of functions defined in the module */
static PyMethodDef signal_methods[] = {
#ifdef HAVE_ALARM
@ -478,6 +620,14 @@ static PyMethodDef signal_methods[] = {
{"signal", signal_signal, METH_VARARGS, signal_doc},
{"getsignal", signal_getsignal, METH_VARARGS, getsignal_doc},
{"set_wakeup_fd", signal_set_wakeup_fd, METH_VARARGS, set_wakeup_fd_doc},
#ifdef PY_SIGMASK
{"sigprocmask", signal_sigprocmask, METH_VARARGS, sigprocmask_doc},
/* It's no longer needed, so clean up the namespace. */
#undef PY_SIGMASK
#endif
#ifdef HAVE_SIGNALFD
{"signalfd", signal_signalfd, METH_VARARGS, signalfd_doc},
#endif
#ifdef HAVE_SIGINTERRUPT
{"siginterrupt", signal_siginterrupt, METH_VARARGS, siginterrupt_doc},
#endif
@ -811,6 +961,36 @@ PyInit_signal(void)
PyDict_SetItemString(d, "ItimerError", ItimerError);
#endif
#ifdef SIG_BLOCK
x = PyLong_FromLong(SIG_BLOCK);
PyDict_SetItemString(d, "SIG_BLOCK", x);
Py_DECREF(x);
#endif
#ifdef SIG_UNBLOCK
x = PyLong_FromLong(SIG_UNBLOCK);
PyDict_SetItemString(d, "SIG_UNBLOCK", x);
Py_DECREF(x);
#endif
#ifdef SIG_SETMASK
x = PyLong_FromLong(SIG_SETMASK);
PyDict_SetItemString(d, "SIG_SETMASK", x);
Py_DECREF(x);
#endif
#ifdef SFD_CLOEXEC
x = PyLong_FromLong(SFD_CLOEXEC);
PyDict_SetItemString(d, "SFD_CLOEXEC", x);
Py_DECREF(x);
#endif
#ifdef SFD_NONBLOCK
x = PyLong_FromLong(SFD_NONBLOCK);
PyDict_SetItemString(d, "SFD_NONBLOCK", x);
Py_DECREF(x);
#endif
#ifdef CTRL_C_EVENT
x = PyLong_FromLong(CTRL_C_EVENT);
PyDict_SetItemString(d, "CTRL_C_EVENT", x);

2
configure vendored
View File

@ -9297,7 +9297,7 @@ for ac_func in alarm setitimer getitimer bind_textdomain_codeset chown \
select sem_open sem_timedwait sem_getvalue sem_unlink setegid seteuid \
setgid \
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setuid setvbuf \
sigaction siginterrupt sigrelse snprintf strftime strlcpy \
sigaction siginterrupt signalfd sigprocmask sigrelse snprintf strftime strlcpy \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
truncate uname unsetenv utimes waitpid wait3 wait4 \
wcscoll wcsftime wcsxfrm _getpty

View File

@ -2580,7 +2580,7 @@ AC_CHECK_FUNCS(alarm setitimer getitimer bind_textdomain_codeset chown \
select sem_open sem_timedwait sem_getvalue sem_unlink setegid seteuid \
setgid \
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setuid setvbuf \
sigaction siginterrupt sigrelse snprintf strftime strlcpy \
sigaction siginterrupt signalfd sigprocmask sigrelse snprintf strftime strlcpy \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
truncate uname unsetenv utimes waitpid wait3 wait4 \
wcscoll wcsftime wcsxfrm _getpty)

View File

@ -620,6 +620,12 @@
/* Define to 1 if you have the <signal.h> header file. */
#undef HAVE_SIGNAL_H
/* Define to 1 if you have the `signalfd' function. */
#undef HAVE_SIGNALFD
/* Define to 1 if you have the `sigprocmask' function. */
#undef HAVE_SIGPROCMASK
/* Define to 1 if you have the `sigrelse' function. */
#undef HAVE_SIGRELSE