(original) (raw)

changeset: 95307:69b1683ee001 user: Victor Stinner victor.stinner@gmail.com date: Mon Mar 30 21:38:00 2015 +0200 files: Doc/library/select.rst Doc/whatsnew/3.5.rst Lib/asyncore.py Lib/selectors.py Lib/test/eintrdata/eintr_tester.py Modules/selectmodule.c description: Issue #23485: select.poll.poll() is now retried when interrupted by a signal diff -r 0591cf5c9ebd -r 69b1683ee001 Doc/library/select.rst --- a/Doc/library/select.rst Mon Mar 30 21:36:10 2015 +0200 +++ b/Doc/library/select.rst Mon Mar 30 21:38:00 2015 +0200 @@ -408,6 +408,12 @@ returning. If *timeout* is omitted, negative, 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`. + .. _kqueue-objects: diff -r 0591cf5c9ebd -r 69b1683ee001 Doc/whatsnew/3.5.rst --- a/Doc/whatsnew/3.5.rst Mon Mar 30 21:36:10 2015 +0200 +++ b/Doc/whatsnew/3.5.rst Mon Mar 30 21:38:00 2015 +0200 @@ -621,7 +621,7 @@ - :func:`os.open`, :func:`open` - :func:`os.read`, :func:`os.write` - - :func:`select.select` + - :func:`select.select`, :func:`select.poll.poll` - :func:`time.sleep` * Before Python 3.5, a :class:`datetime.time` object was considered to be false diff -r 0591cf5c9ebd -r 69b1683ee001 Lib/asyncore.py --- a/Lib/asyncore.py Mon Mar 30 21:36:10 2015 +0200 +++ b/Lib/asyncore.py Mon Mar 30 21:38:00 2015 +0200 @@ -179,10 +179,8 @@ flags |= select.POLLOUT if flags: pollster.register(fd, flags) - try: - r = pollster.poll(timeout) - except InterruptedError: - r = [] + + r = pollster.poll(timeout) for fd, flags in r: obj = map.get(fd) if obj is None: diff -r 0591cf5c9ebd -r 69b1683ee001 Lib/selectors.py --- a/Lib/selectors.py Mon Mar 30 21:36:10 2015 +0200 +++ b/Lib/selectors.py Mon Mar 30 21:38:00 2015 +0200 @@ -359,11 +359,10 @@ # poll() 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._poll.poll(timeout) + ready = [] - try: - fd_event_list = self._poll.poll(timeout) - except InterruptedError: - return ready for fd, event in fd_event_list: events = 0 if event & ~select.POLLIN: diff -r 0591cf5c9ebd -r 69b1683ee001 Lib/test/eintrdata/eintr_tester.py --- a/Lib/test/eintrdata/eintr_tester.py Mon Mar 30 21:36:10 2015 +0200 +++ b/Lib/test/eintrdata/eintr_tester.py Mon Mar 30 21:38:00 2015 +0200 @@ -38,8 +38,12 @@ cls.signal_period) @classmethod + def stop_alarm(cls): + signal.setitimer(signal.ITIMER_REAL, 0, 0) + + @classmethod def tearDownClass(cls): - signal.setitimer(signal.ITIMER_REAL, 0, 0) + cls.stop_alarm() signal.signal(signal.SIGALRM, cls.orig_handler) @classmethod @@ -260,7 +264,7 @@ def test_sleep(self): t0 = time.monotonic() time.sleep(self.sleep_time) - signal.alarm(0) + self.stop_alarm() dt = time.monotonic() - t0 self.assertGreaterEqual(dt, self.sleep_time) @@ -311,7 +315,17 @@ def test_select(self): t0 = time.monotonic() 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 self.assertGreaterEqual(dt, self.sleep_time) diff -r 0591cf5c9ebd -r 69b1683ee001 Modules/selectmodule.c --- a/Modules/selectmodule.c Mon Mar 30 21:36:10 2015 +0200 +++ b/Modules/selectmodule.c Mon Mar 30 21:38:00 2015 +0200 @@ -279,6 +279,7 @@ break; } _PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING); + /* retry select() with the recomputed timeout */ } } while (1); @@ -520,30 +521,39 @@ static PyObject * poll_poll(pollObject *self, PyObject *args) { - PyObject *result_list = NULL, *tout = NULL; - int timeout = 0, poll_result, i, j; + PyObject *result_list = NULL, *timeout_obj = NULL; + int poll_result, i, j; 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; } /* 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; + deadline = 0; } 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_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; + } + + deadline = _PyTime_GetMonotonicClock() + timeout; } /* Avoid concurrent poll() invocation, issue 8865 */ @@ -561,14 +571,38 @@ self->poll_running = 1; /* call poll() */ - Py_BEGIN_ALLOW_THREADS - poll_result = poll(self->ufds, self->ufd_len, timeout); - Py_END_ALLOW_THREADS + async_err = 0; + do { + 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; if (poll_result < 0) { - PyErr_SetFromErrno(PyExc_OSError); + if (!async_err) + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -577,41 +611,40 @@ result_list = PyList_New(poll_result); if (!result_list) 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' - 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; - } + 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' + 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; /victor.stinner@gmail.com