Merge heads

This commit is contained in:
Serhiy Storchaka 2015-03-31 13:18:35 +03:00
commit 80d84c89ee
7 changed files with 182 additions and 70 deletions

View File

@ -249,6 +249,12 @@ object.
returning. If *timeout* is omitted, -1, or :const:`None`, the call will
block until there is an event for this poll object.
.. versionchanged:: 3.5
The function is now retried with a recomputed timeout when interrupted by
a signal, except if the signal handler raises an exception (see
:pep:`475` for the rationale), instead of raising
:exc:`InterruptedError`.
.. _epoll-objects:
@ -454,6 +460,12 @@ Kqueue Objects
- max_events must be 0 or a positive integer
- timeout in seconds (floats possible)
.. versionchanged:: 3.5
The function is now retried with a recomputed timeout when interrupted by
a signal, except if the signal handler raises an exception (see
:pep:`475` for the rationale), instead of raising
:exc:`InterruptedError`.
.. _kevent-objects:

View File

@ -159,6 +159,12 @@ below:
timeout has elapsed if the current process receives a signal: in this
case, an empty list will be returned.
.. versionchanged:: 3.5
The selector is now retried with a recomputed timeout when interrupted
by a signal if the signal handler did not raise an exception (see
:pep:`475` for the rationale), instead of returning an empty list
of events before the timeout.
.. method:: close()
Close the selector.

View File

@ -619,9 +619,10 @@ Changes in the Python API
instead of raising :exc:`InterruptedError` if the signal handler does not
raise an exception:
- :func:`os.open`, :func:`open`
- :func:`open`, :func:`os.open`, :func:`io.open`
- :func:`os.read`, :func:`os.write`
- :func:`select.select`, :func:`select.poll.poll`, :func:`select.epoll.poll`
- :func:`select.select`, :func:`select.poll.poll`, :func:`select.epoll.poll`,
:func:`select.kqueue.control`, :func:`select.devpoll.poll`
- :func:`time.sleep`
* Before Python 3.5, a :class:`datetime.time` object was considered to be false

View File

@ -479,11 +479,10 @@ if hasattr(select, 'devpoll'):
# devpoll() has a resolution of 1 millisecond, round away from
# zero to wait *at least* timeout seconds.
timeout = math.ceil(timeout * 1e3)
fd_event_list = self._devpoll.poll(timeout)
ready = []
try:
fd_event_list = self._devpoll.poll(timeout)
except InterruptedError:
return ready
for fd, event in fd_event_list:
events = 0
if event & ~select.POLLIN:
@ -549,11 +548,9 @@ if hasattr(select, 'kqueue'):
def select(self, timeout=None):
timeout = None if timeout is None else max(timeout, 0)
max_ev = len(self._fd_to_key)
kev_list = self._kqueue.control(None, max_ev, timeout)
ready = []
try:
kev_list = self._kqueue.control(None, max_ev, timeout)
except InterruptedError:
return ready
for kev in kev_list:
fd = kev.ident
flag = kev.filter

View File

@ -315,8 +315,8 @@ class SelectEINTRTest(EINTRBaseTest):
def test_select(self):
t0 = time.monotonic()
select.select([], [], [], self.sleep_time)
self.stop_alarm()
dt = time.monotonic() - t0
self.stop_alarm()
self.assertGreaterEqual(dt, self.sleep_time)
@unittest.skipUnless(hasattr(select, 'poll'), 'need select.poll')
@ -325,8 +325,8 @@ class SelectEINTRTest(EINTRBaseTest):
t0 = time.monotonic()
poller.poll(self.sleep_time * 1e3)
self.stop_alarm()
dt = time.monotonic() - t0
self.stop_alarm()
self.assertGreaterEqual(dt, self.sleep_time)
@unittest.skipUnless(hasattr(select, 'epoll'), 'need select.epoll')
@ -336,8 +336,30 @@ class SelectEINTRTest(EINTRBaseTest):
t0 = time.monotonic()
poller.poll(self.sleep_time)
self.stop_alarm()
dt = time.monotonic() - t0
self.stop_alarm()
self.assertGreaterEqual(dt, self.sleep_time)
@unittest.skipUnless(hasattr(select, 'kqueue'), 'need select.kqueue')
def test_kqueue(self):
kqueue = select.kqueue()
self.addCleanup(kqueue.close)
t0 = time.monotonic()
kqueue.control(None, 1, self.sleep_time)
dt = time.monotonic() - t0
self.stop_alarm()
self.assertGreaterEqual(dt, self.sleep_time)
@unittest.skipUnless(hasattr(select, 'devpoll'), 'need select.devpoll')
def test_devpoll(self):
poller = select.devpoll()
self.addCleanup(poller.close)
t0 = time.monotonic()
poller.poll(self.sleep_time * 1e3)
dt = time.monotonic() - t0
self.stop_alarm()
self.assertGreaterEqual(dt, self.sleep_time)

View File

@ -357,7 +357,35 @@ class BaseSelectorTestCase(unittest.TestCase):
@unittest.skipUnless(hasattr(signal, "alarm"),
"signal.alarm() required for this test")
def test_select_interrupt(self):
def test_select_interrupt_exc(self):
s = self.SELECTOR()
self.addCleanup(s.close)
rd, wr = self.make_socketpair()
class InterruptSelect(Exception):
pass
def handler(*args):
raise InterruptSelect
orig_alrm_handler = signal.signal(signal.SIGALRM, handler)
self.addCleanup(signal.signal, signal.SIGALRM, orig_alrm_handler)
self.addCleanup(signal.alarm, 0)
signal.alarm(1)
s.register(rd, selectors.EVENT_READ)
t = time()
# select() is interrupted by a signal which raises an exception
with self.assertRaises(InterruptSelect):
s.select(30)
# select() was interrupted before the timeout of 30 seconds
self.assertLess(time() - t, 5.0)
@unittest.skipUnless(hasattr(signal, "alarm"),
"signal.alarm() required for this test")
def test_select_interrupt_noraise(self):
s = self.SELECTOR()
self.addCleanup(s.close)
@ -371,8 +399,11 @@ class BaseSelectorTestCase(unittest.TestCase):
s.register(rd, selectors.EVENT_READ)
t = time()
self.assertFalse(s.select(2))
self.assertLess(time() - t, 2.5)
# select() is interrupted by a signal, but the signal handler doesn't
# raise an exception, so select() should by retries with a recomputed
# timeout
self.assertFalse(s.select(1.5))
self.assertGreaterEqual(time() - t, 1.0)
class ScalableSelectorMixIn:

View File

@ -876,40 +876,38 @@ static PyObject *
devpoll_poll(devpollObject *self, PyObject *args)
{
struct dvpoll dvp;
PyObject *result_list = NULL, *tout = NULL;
PyObject *result_list = NULL, *timeout_obj = NULL;
int poll_result, i;
long timeout;
PyObject *value, *num1, *num2;
_PyTime_t timeout, ms, deadline = 0;
if (self->fd_devpoll < 0)
return devpoll_err_closed();
if (!PyArg_UnpackTuple(args, "poll", 0, 1, &tout)) {
if (!PyArg_ParseTuple(args, "|O:poll", &timeout_obj)) {
return NULL;
}
/* Check values for timeout */
if (tout == NULL || tout == Py_None)
if (timeout_obj == NULL || timeout_obj == Py_None) {
timeout = -1;
else if (!PyNumber_Check(tout)) {
PyErr_SetString(PyExc_TypeError,
"timeout must be an integer or None");
return NULL;
ms = -1;
}
else {
tout = PyNumber_Long(tout);
if (!tout)
if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj,
_PyTime_ROUND_CEILING) < 0) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_SetString(PyExc_TypeError,
"timeout must be an integer or None");
}
return NULL;
timeout = PyLong_AsLong(tout);
Py_DECREF(tout);
if (timeout == -1 && PyErr_Occurred())
return NULL;
}
}
if ((timeout < -1) || (timeout > INT_MAX)) {
PyErr_SetString(PyExc_OverflowError,
"timeout is out of range");
return NULL;
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
if (ms < -1 || ms > INT_MAX) {
PyErr_SetString(PyExc_OverflowError, "timeout is too large");
return NULL;
}
}
if (devpoll_flush(self))
@ -917,12 +915,36 @@ devpoll_poll(devpollObject *self, PyObject *args)
dvp.dp_fds = self->fds;
dvp.dp_nfds = self->max_n_fds;
dvp.dp_timeout = timeout;
dvp.dp_timeout = (int)ms;
/* call devpoll() */
Py_BEGIN_ALLOW_THREADS
poll_result = ioctl(self->fd_devpoll, DP_POLL, &dvp);
Py_END_ALLOW_THREADS
if (timeout >= 0)
deadline = _PyTime_GetMonotonicClock() + timeout;
do {
/* call devpoll() */
Py_BEGIN_ALLOW_THREADS
errno = 0;
poll_result = ioctl(self->fd_devpoll, DP_POLL, &dvp);
Py_END_ALLOW_THREADS
if (errno != EINTR)
break;
/* devpoll() was interrupted by a signal */
if (PyErr_CheckSignals())
return NULL;
if (timeout >= 0) {
timeout = deadline - _PyTime_GetMonotonicClock();
if (timeout < 0) {
poll_result = 0;
break;
}
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
dvp.dp_timeout = (int)ms;
/* retry devpoll() with the recomputed timeout */
}
} while (1);
if (poll_result < 0) {
PyErr_SetFromErrno(PyExc_IOError);
@ -930,28 +952,26 @@ devpoll_poll(devpollObject *self, PyObject *args)
}
/* build the result list */
result_list = PyList_New(poll_result);
if (!result_list)
return NULL;
else {
for (i = 0; i < poll_result; i++) {
num1 = PyLong_FromLong(self->fds[i].fd);
num2 = PyLong_FromLong(self->fds[i].revents);
if ((num1 == NULL) || (num2 == NULL)) {
Py_XDECREF(num1);
Py_XDECREF(num2);
goto error;
}
value = PyTuple_Pack(2, num1, num2);
Py_DECREF(num1);
Py_DECREF(num2);
if (value == NULL)
goto error;
if ((PyList_SetItem(result_list, i, value)) == -1) {
Py_DECREF(value);
goto error;
}
for (i = 0; i < poll_result; i++) {
num1 = PyLong_FromLong(self->fds[i].fd);
num2 = PyLong_FromLong(self->fds[i].revents);
if ((num1 == NULL) || (num2 == NULL)) {
Py_XDECREF(num1);
Py_XDECREF(num2);
goto error;
}
value = PyTuple_Pack(2, num1, num2);
Py_DECREF(num1);
Py_DECREF(num2);
if (value == NULL)
goto error;
if ((PyList_SetItem(result_list, i, value)) == -1) {
Py_DECREF(value);
goto error;
}
}
@ -2083,8 +2103,9 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
PyObject *result = NULL;
struct kevent *evl = NULL;
struct kevent *chl = NULL;
struct timespec timeout;
struct timespec timeoutspec;
struct timespec *ptimeoutspec;
_PyTime_t timeout, deadline = 0;
if (self->kqfd < 0)
return kqueue_queue_err_closed();
@ -2103,9 +2124,7 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
ptimeoutspec = NULL;
}
else {
_PyTime_t ts;
if (_PyTime_FromSecondsObject(&ts,
if (_PyTime_FromSecondsObject(&timeout,
otimeout, _PyTime_ROUND_CEILING) < 0) {
PyErr_Format(PyExc_TypeError,
"timeout argument must be an number "
@ -2114,15 +2133,15 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
return NULL;
}
if (_PyTime_AsTimespec(ts, &timeout) == -1)
if (_PyTime_AsTimespec(timeout, &timeoutspec) == -1)
return NULL;
if (timeout.tv_sec < 0) {
if (timeoutspec.tv_sec < 0) {
PyErr_SetString(PyExc_ValueError,
"timeout must be positive or None");
return NULL;
}
ptimeoutspec = &timeout;
ptimeoutspec = &timeoutspec;
}
if (ch != NULL && ch != Py_None) {
@ -2167,10 +2186,34 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
}
}
Py_BEGIN_ALLOW_THREADS
gotevents = kevent(self->kqfd, chl, nchanges,
evl, nevents, ptimeoutspec);
Py_END_ALLOW_THREADS
if (ptimeoutspec)
deadline = _PyTime_GetMonotonicClock() + timeout;
do {
Py_BEGIN_ALLOW_THREADS
errno = 0;
gotevents = kevent(self->kqfd, chl, nchanges,
evl, nevents, ptimeoutspec);
Py_END_ALLOW_THREADS
if (errno != EINTR)
break;
/* kevent() was interrupted by a signal */
if (PyErr_CheckSignals())
goto error;
if (ptimeoutspec) {
timeout = deadline - _PyTime_GetMonotonicClock();
if (timeout < 0) {
gotevents = 0;
break;
}
if (_PyTime_AsTimespec(timeout, &timeoutspec) == -1)
goto error;
/* retry kevent() with the recomputed timeout */
}
} while (1);
if (gotevents == -1) {
PyErr_SetFromErrno(PyExc_OSError);