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

This commit is contained in:
Victor Stinner 2015-03-30 21:38:00 +02:00
parent fa09beb150
commit 3c7d6e0693
6 changed files with 111 additions and 61 deletions

View File

@ -408,6 +408,12 @@ linearly scanned again. :c:func:`select` is O(highest file descriptor), while
returning. If *timeout* is omitted, negative, or :const:`None`, the call will returning. If *timeout* is omitted, negative, or :const:`None`, the call will
block until there is an event for this poll object. 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`.
.. _kqueue-objects: .. _kqueue-objects:

View File

@ -621,7 +621,7 @@ 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.select`, :func:`select.poll.poll`
- :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

@ -179,10 +179,8 @@ def poll2(timeout=0.0, map=None):
flags |= select.POLLOUT flags |= select.POLLOUT
if flags: if flags:
pollster.register(fd, flags) pollster.register(fd, flags)
try:
r = pollster.poll(timeout) r = pollster.poll(timeout)
except InterruptedError:
r = []
for fd, flags in r: for fd, flags in r:
obj = map.get(fd) obj = map.get(fd)
if obj is None: if obj is None:

View File

@ -359,11 +359,10 @@ if hasattr(select, 'poll'):
# poll() has a resolution of 1 millisecond, round away from # poll() has a resolution of 1 millisecond, round away from
# zero to wait *at least* timeout seconds. # zero to wait *at least* timeout seconds.
timeout = math.ceil(timeout * 1e3) timeout = math.ceil(timeout * 1e3)
fd_event_list = self._poll.poll(timeout)
ready = [] ready = []
try:
fd_event_list = self._poll.poll(timeout)
except InterruptedError:
return ready
for fd, event in fd_event_list: for fd, event in fd_event_list:
events = 0 events = 0
if event & ~select.POLLIN: if event & ~select.POLLIN:

View File

@ -38,8 +38,12 @@ class EINTRBaseTest(unittest.TestCase):
cls.signal_period) cls.signal_period)
@classmethod @classmethod
def tearDownClass(cls): def stop_alarm(cls):
signal.setitimer(signal.ITIMER_REAL, 0, 0) signal.setitimer(signal.ITIMER_REAL, 0, 0)
@classmethod
def tearDownClass(cls):
cls.stop_alarm()
signal.signal(signal.SIGALRM, cls.orig_handler) signal.signal(signal.SIGALRM, cls.orig_handler)
@classmethod @classmethod
@ -260,7 +264,7 @@ class TimeEINTRTest(EINTRBaseTest):
def test_sleep(self): def test_sleep(self):
t0 = time.monotonic() t0 = time.monotonic()
time.sleep(self.sleep_time) time.sleep(self.sleep_time)
signal.alarm(0) self.stop_alarm()
dt = time.monotonic() - t0 dt = time.monotonic() - t0
self.assertGreaterEqual(dt, self.sleep_time) self.assertGreaterEqual(dt, self.sleep_time)
@ -311,7 +315,17 @@ 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)
signal.alarm(0) self.stop_alarm()
dt = time.monotonic() - t0
self.assertGreaterEqual(dt, self.sleep_time)
@unittest.skipUnless(hasattr(select, 'poll'), 'need select.poll')
def test_poll(self):
poller = select.poll()
t0 = time.monotonic()
poller.poll(self.sleep_time * 1e3)
self.stop_alarm()
dt = time.monotonic() - t0 dt = time.monotonic() - t0
self.assertGreaterEqual(dt, self.sleep_time) self.assertGreaterEqual(dt, self.sleep_time)

View File

@ -279,6 +279,7 @@ select_select(PyObject *self, PyObject *args)
break; break;
} }
_PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING); _PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING);
/* retry select() with the recomputed timeout */
} }
} while (1); } while (1);
@ -520,30 +521,39 @@ any descriptors that have events or errors to report.");
static PyObject * static PyObject *
poll_poll(pollObject *self, PyObject *args) poll_poll(pollObject *self, PyObject *args)
{ {
PyObject *result_list = NULL, *tout = NULL; PyObject *result_list = NULL, *timeout_obj = NULL;
int timeout = 0, poll_result, i, j; int poll_result, i, j;
PyObject *value = NULL, *num = NULL; PyObject *value = NULL, *num = NULL;
_PyTime_t timeout, ms, deadline;
int async_err = 0;
if (!PyArg_UnpackTuple(args, "poll", 0, 1, &tout)) { if (!PyArg_ParseTuple(args, "|O:poll", &timeout_obj)) {
return NULL; return NULL;
} }
/* Check values for timeout */ /* Check values for timeout */
if (tout == NULL || tout == Py_None) if (timeout_obj == NULL || timeout_obj == Py_None) {
timeout = -1; timeout = -1;
else if (!PyNumber_Check(tout)) { ms = -1;
PyErr_SetString(PyExc_TypeError, deadline = 0;
"timeout must be an integer or None");
return NULL;
} }
else { else {
tout = PyNumber_Long(tout); if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj,
if (!tout) _PyTime_ROUND_CEILING) < 0) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_SetString(PyExc_TypeError,
"timeout must be an integer or None");
}
return NULL; return NULL;
timeout = _PyLong_AsInt(tout); }
Py_DECREF(tout);
if (timeout == -1 && PyErr_Occurred()) ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
if (ms < INT_MIN || ms > INT_MAX) {
PyErr_SetString(PyExc_OverflowError, "timeout is too large");
return NULL; return NULL;
}
deadline = _PyTime_GetMonotonicClock() + timeout;
} }
/* Avoid concurrent poll() invocation, issue 8865 */ /* Avoid concurrent poll() invocation, issue 8865 */
@ -561,14 +571,38 @@ poll_poll(pollObject *self, PyObject *args)
self->poll_running = 1; self->poll_running = 1;
/* call poll() */ /* call poll() */
Py_BEGIN_ALLOW_THREADS async_err = 0;
poll_result = poll(self->ufds, self->ufd_len, timeout); do {
Py_END_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
errno = 0;
poll_result = poll(self->ufds, self->ufd_len, (int)ms);
Py_END_ALLOW_THREADS
if (errno != EINTR)
break;
/* poll() was interrupted by a signal */
if (PyErr_CheckSignals()) {
async_err = 1;
break;
}
if (timeout >= 0) {
timeout = deadline - _PyTime_GetMonotonicClock();
if (timeout < 0) {
poll_result = 0;
break;
}
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
/* retry poll() with the recomputed timeout */
}
} while (1);
self->poll_running = 0; self->poll_running = 0;
if (poll_result < 0) { if (poll_result < 0) {
PyErr_SetFromErrno(PyExc_OSError); if (!async_err)
PyErr_SetFromErrno(PyExc_OSError);
return NULL; return NULL;
} }
@ -577,41 +611,40 @@ poll_poll(pollObject *self, PyObject *args)
result_list = PyList_New(poll_result); result_list = PyList_New(poll_result);
if (!result_list) if (!result_list)
return NULL; return NULL;
else {
for (i = 0, j = 0; j < poll_result; j++) {
/* skip to the next fired descriptor */
while (!self->ufds[i].revents) {
i++;
}
/* if we hit a NULL return, set value to NULL
and break out of loop; code at end will
clean up result_list */
value = PyTuple_New(2);
if (value == NULL)
goto error;
num = PyLong_FromLong(self->ufds[i].fd);
if (num == NULL) {
Py_DECREF(value);
goto error;
}
PyTuple_SET_ITEM(value, 0, num);
/* The &0xffff is a workaround for AIX. 'revents' for (i = 0, j = 0; j < poll_result; j++) {
is a 16-bit short, and IBM assigned POLLNVAL /* skip to the next fired descriptor */
to be 0x8000, so the conversion to int results while (!self->ufds[i].revents) {
in a negative number. See SF bug #923315. */
num = PyLong_FromLong(self->ufds[i].revents & 0xffff);
if (num == NULL) {
Py_DECREF(value);
goto error;
}
PyTuple_SET_ITEM(value, 1, num);
if ((PyList_SetItem(result_list, j, value)) == -1) {
Py_DECREF(value);
goto error;
}
i++; i++;
} }
/* if we hit a NULL return, set value to NULL
and break out of loop; code at end will
clean up result_list */
value = PyTuple_New(2);
if (value == NULL)
goto error;
num = PyLong_FromLong(self->ufds[i].fd);
if (num == NULL) {
Py_DECREF(value);
goto error;
}
PyTuple_SET_ITEM(value, 0, num);
/* The &0xffff is a workaround for AIX. 'revents'
is a 16-bit short, and IBM assigned POLLNVAL
to be 0x8000, so the conversion to int results
in a negative number. See SF bug #923315. */
num = PyLong_FromLong(self->ufds[i].revents & 0xffff);
if (num == NULL) {
Py_DECREF(value);
goto error;
}
PyTuple_SET_ITEM(value, 1, num);
if ((PyList_SetItem(result_list, j, value)) == -1) {
Py_DECREF(value);
goto error;
}
i++;
} }
return result_list; return result_list;