A few missing files from the twisted patch

This commit is contained in:
Vishvananda Ishaya
2010-06-24 04:12:01 +01:00
committed by andy
parent 432beeb670
commit f29d210ef2
4 changed files with 483 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2010 Twisted Matrix Laboratories.
* See LICENSE for details.
*/
#include <signal.h>
#include <errno.h>
#include "Python.h"
static int sigchld_pipe_fd = -1;
static void got_signal(int sig) {
int saved_errno = errno;
int ignored_result;
/* write() errors are unhandled. If the buffer is full, we don't
* care. What about other errors? */
ignored_result = write(sigchld_pipe_fd, "x", 1);
errno = saved_errno;
}
PyDoc_STRVAR(install_sigchld_handler_doc, "\
install_sigchld_handler(fd)\n\
\n\
Installs a SIGCHLD handler which will write a byte to the given fd\n\
whenever a SIGCHLD occurs. This is done in C code because the python\n\
signal handling system is not reliable, and additionally cannot\n\
specify SA_RESTART.\n\
\n\
Please ensure fd is in non-blocking mode.\n\
");
static PyObject *
install_sigchld_handler(PyObject *self, PyObject *args) {
int fd, old_fd;
struct sigaction sa;
if (!PyArg_ParseTuple(args, "i:install_sigchld_handler", &fd)) {
return NULL;
}
old_fd = sigchld_pipe_fd;
sigchld_pipe_fd = fd;
if (fd == -1) {
sa.sa_handler = SIG_DFL;
} else {
sa.sa_handler = got_signal;
sa.sa_flags = SA_RESTART;
/* mask all signals so I don't worry about EINTR from the write. */
sigfillset(&sa.sa_mask);
}
if (sigaction(SIGCHLD, &sa, 0) != 0) {
sigchld_pipe_fd = old_fd;
return PyErr_SetFromErrno(PyExc_OSError);
}
return PyLong_FromLong(old_fd);
}
PyDoc_STRVAR(is_default_handler_doc, "\
Return 1 if the SIGCHLD handler is SIG_DFL, 0 otherwise.\n\
");
static PyObject *
is_default_handler(PyObject *self, PyObject *args) {
/*
* This implementation is necessary since the install_sigchld_handler
* function above bypasses the Python signal handler installation API, so
* CPython doesn't notice that the handler has changed and signal.getsignal
* won't return an accurate result.
*/
struct sigaction sa;
if (sigaction(SIGCHLD, NULL, &sa) != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
return PyLong_FromLong(sa.sa_handler == SIG_DFL);
}
static PyMethodDef sigchld_methods[] = {
{"installHandler", install_sigchld_handler, METH_VARARGS,
install_sigchld_handler_doc},
{"isDefaultHandler", is_default_handler, METH_NOARGS,
is_default_handler_doc},
/* sentinel */
{NULL, NULL, 0, NULL}
};
static const char _sigchld_doc[] = "\n\
This module contains an API for receiving SIGCHLD via a file descriptor.\n\
";
PyMODINIT_FUNC
init_sigchld(void) {
/* Create the module and add the functions */
Py_InitModule3(
"twisted.internet._sigchld", sigchld_methods, _sigchld_doc);
}

View File

@@ -0,0 +1,184 @@
# -*- test-case-name: twisted.test.test_process,twisted.internet.test.test_process -*-
# Copyright (c) 2010 Twisted Matrix Laboratories.
# See LICENSE for details.
"""
This module provides a uniform interface to the several mechanisms which are
possibly available for dealing with signals.
This module is used to integrate child process termination into a
reactor event loop. This is a challenging feature to provide because
most platforms indicate process termination via SIGCHLD and do not
provide a way to wait for that signal and arbitrary I/O events at the
same time. The naive implementation involves installing a Python
SIGCHLD handler; unfortunately this leads to other syscalls being
interrupted (whenever SIGCHLD is received) and failing with EINTR
(which almost no one is prepared to handle). This interruption can be
disabled via siginterrupt(2) (or one of the equivalent mechanisms);
however, if the SIGCHLD is delivered by the platform to a non-main
thread (not a common occurrence, but difficult to prove impossible),
the main thread (waiting on select() or another event notification
API) may not wake up leading to an arbitrary delay before the child
termination is noticed.
The basic solution to all these issues involves enabling SA_RESTART
(ie, disabling system call interruption) and registering a C signal
handler which writes a byte to a pipe. The other end of the pipe is
registered with the event loop, allowing it to wake up shortly after
SIGCHLD is received. See L{twisted.internet.posixbase._SIGCHLDWaker}
for the implementation of the event loop side of this solution. The
use of a pipe this way is known as the U{self-pipe
trick<http://cr.yp.to/docs/selfpipe.html>}.
The actual solution implemented in this module depends on the version
of Python. From version 2.6, C{signal.siginterrupt} and
C{signal.set_wakeup_fd} allow the necessary C signal handler which
writes to the pipe to be registered with C{SA_RESTART}. Prior to 2.6,
the L{twisted.internet._sigchld} extension module provides similar
functionality.
If neither of these is available, a Python signal handler is used
instead. This is essentially the naive solution mentioned above and
has the problems described there.
"""
import os
try:
from signal import set_wakeup_fd, siginterrupt
except ImportError:
set_wakeup_fd = siginterrupt = None
try:
import signal
except ImportError:
signal = None
from twisted.python.log import msg
try:
from twisted.internet._sigchld import installHandler as _extInstallHandler, \
isDefaultHandler as _extIsDefaultHandler
except ImportError:
_extInstallHandler = _extIsDefaultHandler = None
class _Handler(object):
"""
L{_Handler} is a signal handler which writes a byte to a file descriptor
whenever it is invoked.
@ivar fd: The file descriptor to which to write. If this is C{None},
nothing will be written.
"""
def __init__(self, fd):
self.fd = fd
def __call__(self, *args):
"""
L{_Handler.__call__} is the signal handler. It will write a byte to
the wrapped file descriptor, if there is one.
"""
if self.fd is not None:
try:
os.write(self.fd, '\0')
except:
pass
def _installHandlerUsingSignal(fd):
"""
Install a signal handler which will write a byte to C{fd} when
I{SIGCHLD} is received.
This is implemented by creating an instance of L{_Handler} with C{fd}
and installing it as the signal handler.
@param fd: The file descriptor to which to write when I{SIGCHLD} is
received.
@type fd: C{int}
"""
if fd == -1:
previous = signal.signal(signal.SIGCHLD, signal.SIG_DFL)
else:
previous = signal.signal(signal.SIGCHLD, _Handler(fd))
if isinstance(previous, _Handler):
return previous.fd
return -1
def _installHandlerUsingSetWakeup(fd):
"""
Install a signal handler which will write a byte to C{fd} when
I{SIGCHLD} is received.
This is implemented by installing an instance of L{_Handler} wrapped
around C{None}, setting the I{SIGCHLD} handler as not allowed to
interrupt system calls, and using L{signal.set_wakeup_fd} to do the
actual writing.
@param fd: The file descriptor to which to write when I{SIGCHLD} is
received.
@type fd: C{int}
"""
if fd == -1:
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
else:
signal.signal(signal.SIGCHLD, _Handler(None))
siginterrupt(signal.SIGCHLD, False)
return set_wakeup_fd(fd)
def _isDefaultHandler():
"""
Determine whether the I{SIGCHLD} handler is the default or not.
"""
return signal.getsignal(signal.SIGCHLD) == signal.SIG_DFL
def _cannotInstallHandler(fd):
"""
Fail to install a signal handler for I{SIGCHLD}.
This implementation is used when the supporting code for the other
implementations is unavailable (on Python versions 2.5 and older where
neither the L{twisted.internet._sigchld} extension nor the standard
L{signal} module is available).
@param fd: Ignored; only for compatibility with the other
implementations of this interface.
@raise RuntimeError: Always raised to indicate no I{SIGCHLD} handler can
be installed.
"""
raise RuntimeError("Cannot install a SIGCHLD handler")
def _cannotDetermineDefault():
raise RuntimeError("No usable signal API available")
if set_wakeup_fd is not None:
msg('using set_wakeup_fd')
installHandler = _installHandlerUsingSetWakeup
isDefaultHandler = _isDefaultHandler
elif _extInstallHandler is not None:
msg('using _sigchld')
installHandler = _extInstallHandler
isDefaultHandler = _extIsDefaultHandler
elif signal is not None:
msg('using signal module')
installHandler = _installHandlerUsingSignal
isDefaultHandler = _isDefaultHandler
else:
msg('nothing unavailable')
installHandler = _cannotInstallHandler
isDefaultHandler = _cannotDetermineDefault

View File

@@ -0,0 +1,194 @@
# Copyright (c) 2010 Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.internet._sigchld}, an alternate, superior SIGCHLD
monitoring API.
"""
import os, signal, errno
from twisted.python.log import msg
from twisted.trial.unittest import TestCase
from twisted.internet.fdesc import setNonBlocking
from twisted.internet._signals import installHandler, isDefaultHandler
from twisted.internet._signals import _extInstallHandler, _extIsDefaultHandler
from twisted.internet._signals import _installHandlerUsingSetWakeup, \
_installHandlerUsingSignal, _isDefaultHandler
class SIGCHLDTestsMixin:
"""
Mixin for L{TestCase} subclasses which defines several tests for
I{installHandler} and I{isDefaultHandler}. Subclasses are expected to
define C{self.installHandler} and C{self.isDefaultHandler} to invoke the
implementation to be tested.
"""
if getattr(signal, 'SIGCHLD', None) is None:
skip = "Platform does not have SIGCHLD"
def installHandler(self, fd):
"""
Override in a subclass to install a SIGCHLD handler which writes a byte
to the given file descriptor. Return the previously registered file
descriptor.
"""
raise NotImplementedError()
def isDefaultHandler(self):
"""
Override in a subclass to determine if the current SIGCHLD handler is
SIG_DFL or not. Return True if it is SIG_DFL, False otherwise.
"""
raise NotImplementedError()
def pipe(self):
"""
Create a non-blocking pipe which will be closed after the currently
running test.
"""
read, write = os.pipe()
self.addCleanup(os.close, read)
self.addCleanup(os.close, write)
setNonBlocking(read)
setNonBlocking(write)
return read, write
def setUp(self):
"""
Save the current SIGCHLD handler as reported by L{signal.signal} and
the current file descriptor registered with L{installHandler}.
"""
handler = signal.getsignal(signal.SIGCHLD)
if handler != signal.SIG_DFL:
self.signalModuleHandler = handler
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
else:
self.signalModuleHandler = None
self.oldFD = self.installHandler(-1)
if self.signalModuleHandler is not None and self.oldFD != -1:
msg("SIGCHLD setup issue: %r %r" % (self.signalModuleHandler, self.oldFD))
raise RuntimeError("You used some signal APIs wrong! Try again.")
def tearDown(self):
"""
Restore whatever signal handler was present when setUp ran.
"""
# If tests set up any kind of handlers, clear them out.
self.installHandler(-1)
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
# Now restore whatever the setup was before the test ran.
if self.signalModuleHandler is not None:
signal.signal(signal.SIGCHLD, self.signalModuleHandler)
elif self.oldFD != -1:
self.installHandler(self.oldFD)
def test_isDefaultHandler(self):
"""
L{isDefaultHandler} returns true if the SIGCHLD handler is SIG_DFL,
false otherwise.
"""
self.assertTrue(self.isDefaultHandler())
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
self.assertFalse(self.isDefaultHandler())
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
self.assertTrue(self.isDefaultHandler())
signal.signal(signal.SIGCHLD, lambda *args: None)
self.assertFalse(self.isDefaultHandler())
def test_returnOldFD(self):
"""
L{installHandler} returns the previously registered file descriptor.
"""
read, write = self.pipe()
oldFD = self.installHandler(write)
self.assertEqual(self.installHandler(oldFD), write)
def test_uninstallHandler(self):
"""
C{installHandler(-1)} removes the SIGCHLD handler completely.
"""
read, write = self.pipe()
self.assertTrue(self.isDefaultHandler())
self.installHandler(write)
self.assertFalse(self.isDefaultHandler())
self.installHandler(-1)
self.assertTrue(self.isDefaultHandler())
def test_installHandler(self):
"""
The file descriptor passed to L{installHandler} has a byte written to
it when SIGCHLD is delivered to the process.
"""
read, write = self.pipe()
self.installHandler(write)
exc = self.assertRaises(OSError, os.read, read, 1)
self.assertEqual(exc.errno, errno.EAGAIN)
os.kill(os.getpid(), signal.SIGCHLD)
self.assertEqual(len(os.read(read, 5)), 1)
class DefaultSIGCHLDTests(SIGCHLDTestsMixin, TestCase):
"""
Tests for whatever implementation is selected for the L{installHandler}
and L{isDefaultHandler} APIs.
"""
installHandler = staticmethod(installHandler)
isDefaultHandler = staticmethod(isDefaultHandler)
class ExtensionSIGCHLDTests(SIGCHLDTestsMixin, TestCase):
"""
Tests for the L{twisted.internet._sigchld} implementation of the
L{installHandler} and L{isDefaultHandler} APIs.
"""
try:
import twisted.internet._sigchld
except ImportError:
skip = "twisted.internet._sigchld is not available"
installHandler = _extInstallHandler
isDefaultHandler = _extIsDefaultHandler
class SetWakeupSIGCHLDTests(SIGCHLDTestsMixin, TestCase):
"""
Tests for the L{signal.set_wakeup_fd} implementation of the
L{installHandler} and L{isDefaultHandler} APIs.
"""
# Check both of these. On Ubuntu 9.10 (to take an example completely at
# random), Python 2.5 has set_wakeup_fd but not siginterrupt.
if (getattr(signal, 'set_wakeup_fd', None) is None
or getattr(signal, 'siginterrupt', None) is None):
skip = "signal.set_wakeup_fd is not available"
installHandler = staticmethod(_installHandlerUsingSetWakeup)
isDefaultHandler = staticmethod(_isDefaultHandler)
class PlainSignalModuleSIGCHLDTests(SIGCHLDTestsMixin, TestCase):
"""
Tests for the L{signal.signal} implementation of the L{installHandler}
and L{isDefaultHandler} APIs.
"""
installHandler = staticmethod(_installHandlerUsingSignal)
isDefaultHandler = staticmethod(_isDefaultHandler)

View File

@@ -0,0 +1,4 @@
On POSIX platforms, reactors now support child processes in a way
which doesn't cause other syscalls to sometimes fail with EINTR (if
running on Python 2.6 or if Twisted's extension modules have been
built).