Issue #23485: select.kqueue.control() is now retried when interrupted by a signal

This commit is contained in:
Victor Stinner 2015-03-31 11:48:34 +02:00
parent b6faf0dfa7
commit 4448c08451
5 changed files with 58 additions and 19 deletions

View File

@ -454,6 +454,12 @@ Kqueue Objects
- max_events must be 0 or a positive integer - max_events must be 0 or a positive integer
- timeout in seconds (floats possible) - 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: .. _kevent-objects:

View File

@ -621,7 +621,8 @@ Changes in the Python API
- :func:`os.open`, :func:`open` - :func:`os.open`, :func:`open`
- :func:`os.read`, :func:`os.write` - :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:`time.sleep` - :func:`time.sleep`
* Before Python 3.5, a :class:`datetime.time` object was considered to be false * Before Python 3.5, a :class:`datetime.time` object was considered to be false

View File

@ -549,11 +549,9 @@ if hasattr(select, 'kqueue'):
def select(self, timeout=None): def select(self, timeout=None):
timeout = None if timeout is None else max(timeout, 0) timeout = None if timeout is None else max(timeout, 0)
max_ev = len(self._fd_to_key) max_ev = len(self._fd_to_key)
kev_list = self._kqueue.control(None, max_ev, timeout)
ready = [] ready = []
try:
kev_list = self._kqueue.control(None, max_ev, timeout)
except InterruptedError:
return ready
for kev in kev_list: for kev in kev_list:
fd = kev.ident fd = kev.ident
flag = kev.filter flag = kev.filter

View File

@ -315,8 +315,8 @@ class SelectEINTRTest(EINTRBaseTest):
def test_select(self): def test_select(self):
t0 = time.monotonic() t0 = time.monotonic()
select.select([], [], [], self.sleep_time) select.select([], [], [], self.sleep_time)
self.stop_alarm()
dt = time.monotonic() - t0 dt = time.monotonic() - t0
self.stop_alarm()
self.assertGreaterEqual(dt, self.sleep_time) self.assertGreaterEqual(dt, self.sleep_time)
@unittest.skipUnless(hasattr(select, 'poll'), 'need select.poll') @unittest.skipUnless(hasattr(select, 'poll'), 'need select.poll')
@ -325,8 +325,8 @@ class SelectEINTRTest(EINTRBaseTest):
t0 = time.monotonic() t0 = time.monotonic()
poller.poll(self.sleep_time * 1e3) poller.poll(self.sleep_time * 1e3)
self.stop_alarm()
dt = time.monotonic() - t0 dt = time.monotonic() - t0
self.stop_alarm()
self.assertGreaterEqual(dt, self.sleep_time) self.assertGreaterEqual(dt, self.sleep_time)
@unittest.skipUnless(hasattr(select, 'epoll'), 'need select.epoll') @unittest.skipUnless(hasattr(select, 'epoll'), 'need select.epoll')
@ -336,8 +336,19 @@ class SelectEINTRTest(EINTRBaseTest):
t0 = time.monotonic() t0 = time.monotonic()
poller.poll(self.sleep_time) poller.poll(self.sleep_time)
self.stop_alarm()
dt = time.monotonic() - t0 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) self.assertGreaterEqual(dt, self.sleep_time)

View File

@ -2083,8 +2083,9 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
PyObject *result = NULL; PyObject *result = NULL;
struct kevent *evl = NULL; struct kevent *evl = NULL;
struct kevent *chl = NULL; struct kevent *chl = NULL;
struct timespec timeout; struct timespec timeoutspec;
struct timespec *ptimeoutspec; struct timespec *ptimeoutspec;
_PyTime_t timeout, deadline = 0;
if (self->kqfd < 0) if (self->kqfd < 0)
return kqueue_queue_err_closed(); return kqueue_queue_err_closed();
@ -2103,9 +2104,7 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
ptimeoutspec = NULL; ptimeoutspec = NULL;
} }
else { else {
_PyTime_t ts; if (_PyTime_FromSecondsObject(&timeout,
if (_PyTime_FromSecondsObject(&ts,
otimeout, _PyTime_ROUND_CEILING) < 0) { otimeout, _PyTime_ROUND_CEILING) < 0) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"timeout argument must be an number " "timeout argument must be an number "
@ -2114,15 +2113,15 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
return NULL; return NULL;
} }
if (_PyTime_AsTimespec(ts, &timeout) == -1) if (_PyTime_AsTimespec(timeout, &timeoutspec) == -1)
return NULL; return NULL;
if (timeout.tv_sec < 0) { if (timeoutspec.tv_sec < 0) {
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"timeout must be positive or None"); "timeout must be positive or None");
return NULL; return NULL;
} }
ptimeoutspec = &timeout; ptimeoutspec = &timeoutspec;
} }
if (ch != NULL && ch != Py_None) { if (ch != NULL && ch != Py_None) {
@ -2167,10 +2166,34 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
} }
} }
Py_BEGIN_ALLOW_THREADS if (ptimeoutspec)
gotevents = kevent(self->kqfd, chl, nchanges, deadline = _PyTime_GetMonotonicClock() + timeout;
evl, nevents, ptimeoutspec);
Py_END_ALLOW_THREADS 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) { if (gotevents == -1) {
PyErr_SetFromErrno(PyExc_OSError); PyErr_SetFromErrno(PyExc_OSError);