Issue #23485: select.poll.poll() is now retried when interrupted by a signal
This commit is contained in:
parent
fa09beb150
commit
3c7d6e0693
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue