Commit 2c15b29a authored by Pablo Galindo's avatar Pablo Galindo Committed by Serhiy Storchaka

bpo-31786: Make functions in the select module blocking when timeout is a...

bpo-31786: Make functions in the select module blocking when timeout is a small negative value. (#4003)
parent 552be9d7
...@@ -29,9 +29,20 @@ typedef enum { ...@@ -29,9 +29,20 @@ typedef enum {
_PyTime_ROUND_CEILING=1, _PyTime_ROUND_CEILING=1,
/* Round to nearest with ties going to nearest even integer. /* Round to nearest with ties going to nearest even integer.
For example, used to round from a Python float. */ For example, used to round from a Python float. */
_PyTime_ROUND_HALF_EVEN _PyTime_ROUND_HALF_EVEN=2,
/* Round away from zero
For example, used for timeout. _PyTime_ROUND_CEILING rounds
-1e-9 to 0 milliseconds which causes bpo-31786 issue.
_PyTime_ROUND_UP rounds -1e-9 to -1 millisecond which keeps
the timeout sign as expected. select.poll(timeout) must block
for negative values." */
_PyTime_ROUND_UP=3,
/* _PyTime_ROUND_TIMEOUT (an alias for _PyTime_ROUND_UP) should be
used for timeouts. */
_PyTime_ROUND_TIMEOUT = _PyTime_ROUND_UP
} _PyTime_round_t; } _PyTime_round_t;
/* Convert a time_t to a PyLong. */ /* Convert a time_t to a PyLong. */
PyAPI_FUNC(PyObject *) _PyLong_FromTime_t( PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
time_t sec); time_t sec);
......
...@@ -204,6 +204,28 @@ class PollTests(unittest.TestCase): ...@@ -204,6 +204,28 @@ class PollTests(unittest.TestCase):
os.write(w, b'spam') os.write(w, b'spam')
t.join() t.join()
@unittest.skipUnless(threading, 'Threading required for this test.')
@reap_threads
def test_poll_blocks_with_negative_ms(self):
for timeout_ms in [None, -1, -1.0, -0.1, -1e-100]:
# Create two file descriptors. This will be used to unlock
# the blocking call to poll.poll inside the thread
r, w = os.pipe()
pollster = select.poll()
pollster.register(r, select.POLLIN)
poll_thread = threading.Thread(target=pollster.poll, args=(timeout_ms,))
poll_thread.start()
poll_thread.join(timeout=0.1)
self.assertTrue(poll_thread.is_alive())
# Write to the pipe so pollster.poll unblocks and the thread ends.
os.write(w, b'spam')
poll_thread.join()
self.assertFalse(poll_thread.is_alive())
os.close(r)
os.close(w)
def test_main(): def test_main():
run_unittest(PollTests) run_unittest(PollTests)
......
...@@ -33,6 +33,8 @@ class _PyTime(enum.IntEnum): ...@@ -33,6 +33,8 @@ class _PyTime(enum.IntEnum):
ROUND_CEILING = 1 ROUND_CEILING = 1
# Round to nearest with ties going to nearest even integer # Round to nearest with ties going to nearest even integer
ROUND_HALF_EVEN = 2 ROUND_HALF_EVEN = 2
# Round away from zero
ROUND_UP = 3
# Rounding modes supported by PyTime # Rounding modes supported by PyTime
ROUNDING_MODES = ( ROUNDING_MODES = (
...@@ -40,6 +42,7 @@ ROUNDING_MODES = ( ...@@ -40,6 +42,7 @@ ROUNDING_MODES = (
(_PyTime.ROUND_FLOOR, decimal.ROUND_FLOOR), (_PyTime.ROUND_FLOOR, decimal.ROUND_FLOOR),
(_PyTime.ROUND_CEILING, decimal.ROUND_CEILING), (_PyTime.ROUND_CEILING, decimal.ROUND_CEILING),
(_PyTime.ROUND_HALF_EVEN, decimal.ROUND_HALF_EVEN), (_PyTime.ROUND_HALF_EVEN, decimal.ROUND_HALF_EVEN),
(_PyTime.ROUND_UP, decimal.ROUND_UP),
) )
......
Fix timeout rounding in the select module to round correctly negative timeouts between -1.0 and 0.0.
The functions now block waiting for events as expected. Previously, the call was incorrectly non-blocking.
Patch by Pablo Galindo.
...@@ -3012,7 +3012,8 @@ check_time_rounding(int round) ...@@ -3012,7 +3012,8 @@ check_time_rounding(int round)
{ {
if (round != _PyTime_ROUND_FLOOR if (round != _PyTime_ROUND_FLOOR
&& round != _PyTime_ROUND_CEILING && round != _PyTime_ROUND_CEILING
&& round != _PyTime_ROUND_HALF_EVEN) { && round != _PyTime_ROUND_HALF_EVEN
&& round != _PyTime_ROUND_UP) {
PyErr_SetString(PyExc_ValueError, "invalid rounding"); PyErr_SetString(PyExc_ValueError, "invalid rounding");
return -1; return -1;
} }
......
...@@ -213,7 +213,7 @@ select_select(PyObject *self, PyObject *args) ...@@ -213,7 +213,7 @@ select_select(PyObject *self, PyObject *args)
tvp = (struct timeval *)NULL; tvp = (struct timeval *)NULL;
else { else {
if (_PyTime_FromSecondsObject(&timeout, timeout_obj, if (_PyTime_FromSecondsObject(&timeout, timeout_obj,
_PyTime_ROUND_CEILING) < 0) { _PyTime_ROUND_TIMEOUT) < 0) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) { if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"timeout must be a float or None"); "timeout must be a float or None");
...@@ -221,7 +221,7 @@ select_select(PyObject *self, PyObject *args) ...@@ -221,7 +221,7 @@ select_select(PyObject *self, PyObject *args)
return NULL; return NULL;
} }
if (_PyTime_AsTimeval(timeout, &tv, _PyTime_ROUND_CEILING) == -1) if (_PyTime_AsTimeval(timeout, &tv, _PyTime_ROUND_TIMEOUT) == -1)
return NULL; return NULL;
if (tv.tv_sec < 0) { if (tv.tv_sec < 0) {
PyErr_SetString(PyExc_ValueError, "timeout must be non-negative"); PyErr_SetString(PyExc_ValueError, "timeout must be non-negative");
...@@ -540,7 +540,7 @@ poll_poll(pollObject *self, PyObject *args) ...@@ -540,7 +540,7 @@ poll_poll(pollObject *self, PyObject *args)
} }
else { else {
if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj, if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj,
_PyTime_ROUND_CEILING) < 0) { _PyTime_ROUND_TIMEOUT) < 0) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) { if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"timeout must be an integer or None"); "timeout must be an integer or None");
...@@ -548,7 +548,7 @@ poll_poll(pollObject *self, PyObject *args) ...@@ -548,7 +548,7 @@ poll_poll(pollObject *self, PyObject *args)
return NULL; return NULL;
} }
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING); ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_TIMEOUT);
if (ms < INT_MIN || ms > INT_MAX) { if (ms < INT_MIN || ms > INT_MAX) {
PyErr_SetString(PyExc_OverflowError, "timeout is too large"); PyErr_SetString(PyExc_OverflowError, "timeout is too large");
return NULL; return NULL;
...@@ -896,7 +896,7 @@ devpoll_poll(devpollObject *self, PyObject *args) ...@@ -896,7 +896,7 @@ devpoll_poll(devpollObject *self, PyObject *args)
} }
else { else {
if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj, if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj,
_PyTime_ROUND_CEILING) < 0) { _PyTime_ROUND_TIMEOUT) < 0) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) { if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"timeout must be an integer or None"); "timeout must be an integer or None");
...@@ -904,7 +904,7 @@ devpoll_poll(devpollObject *self, PyObject *args) ...@@ -904,7 +904,7 @@ devpoll_poll(devpollObject *self, PyObject *args)
return NULL; return NULL;
} }
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING); ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_TIMEOUT);
if (ms < -1 || ms > INT_MAX) { if (ms < -1 || ms > INT_MAX) {
PyErr_SetString(PyExc_OverflowError, "timeout is too large"); PyErr_SetString(PyExc_OverflowError, "timeout is too large");
return NULL; return NULL;
...@@ -1513,7 +1513,7 @@ pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds) ...@@ -1513,7 +1513,7 @@ pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds)
/* epoll_wait() has a resolution of 1 millisecond, round towards /* epoll_wait() has a resolution of 1 millisecond, round towards
infinity to wait at least timeout seconds. */ infinity to wait at least timeout seconds. */
if (_PyTime_FromSecondsObject(&timeout, timeout_obj, if (_PyTime_FromSecondsObject(&timeout, timeout_obj,
_PyTime_ROUND_CEILING) < 0) { _PyTime_ROUND_TIMEOUT) < 0) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) { if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"timeout must be an integer or None"); "timeout must be an integer or None");
...@@ -2128,7 +2128,7 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args) ...@@ -2128,7 +2128,7 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
} }
else { else {
if (_PyTime_FromSecondsObject(&timeout, if (_PyTime_FromSecondsObject(&timeout,
otimeout, _PyTime_ROUND_CEILING) < 0) { otimeout, _PyTime_ROUND_TIMEOUT) < 0) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"timeout argument must be a number " "timeout argument must be a number "
"or None, got %.200s", "or None, got %.200s",
......
...@@ -120,9 +120,13 @@ _PyTime_Round(double x, _PyTime_round_t round) ...@@ -120,9 +120,13 @@ _PyTime_Round(double x, _PyTime_round_t round)
else if (round == _PyTime_ROUND_CEILING) { else if (round == _PyTime_ROUND_CEILING) {
d = ceil(d); d = ceil(d);
} }
else { else if (round == _PyTime_ROUND_FLOOR) {
d = floor(d); d = floor(d);
} }
else {
assert(round == _PyTime_ROUND_UP);
d = (d >= 0.0) ? ceil(d) : floor(d);
}
return d; return d;
} }
...@@ -427,7 +431,7 @@ _PyTime_Divide(const _PyTime_t t, const _PyTime_t k, ...@@ -427,7 +431,7 @@ _PyTime_Divide(const _PyTime_t t, const _PyTime_t k,
return t / k; return t / k;
} }
} }
else { else if (round == _PyTime_ROUND_FLOOR){
if (t >= 0) { if (t >= 0) {
return t / k; return t / k;
} }
...@@ -435,6 +439,15 @@ _PyTime_Divide(const _PyTime_t t, const _PyTime_t k, ...@@ -435,6 +439,15 @@ _PyTime_Divide(const _PyTime_t t, const _PyTime_t k,
return (t - (k - 1)) / k; return (t - (k - 1)) / k;
} }
} }
else {
assert(round == _PyTime_ROUND_UP);
if (t >= 0) {
return (t + k - 1) / k;
}
else {
return (t - (k - 1)) / k;
}
}
} }
_PyTime_t _PyTime_t
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment