Skip to content

Commit 95602b3

Browse files
pablogsalserhiy-storchaka
authored andcommitted
[3.6] bpo-31786: Make functions in the select module blocking when timeout is a small negative value. (GH-4003). (#4022)
(cherry picked from commit 2c15b29)
1 parent 1781480 commit 95602b3

File tree

7 files changed

+78
-18
lines changed

7 files changed

+78
-18
lines changed

Include/pytime.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,20 @@ typedef enum {
2929
_PyTime_ROUND_CEILING=1,
3030
/* Round to nearest with ties going to nearest even integer.
3131
For example, used to round from a Python float. */
32-
_PyTime_ROUND_HALF_EVEN
32+
_PyTime_ROUND_HALF_EVEN=2,
33+
/* Round away from zero
34+
For example, used for timeout. _PyTime_ROUND_CEILING rounds
35+
-1e-9 to 0 milliseconds which causes bpo-31786 issue.
36+
_PyTime_ROUND_UP rounds -1e-9 to -1 millisecond which keeps
37+
the timeout sign as expected. select.poll(timeout) must block
38+
for negative values." */
39+
_PyTime_ROUND_UP=3,
40+
/* _PyTime_ROUND_TIMEOUT (an alias for _PyTime_ROUND_UP) should be
41+
used for timeouts. */
42+
_PyTime_ROUND_TIMEOUT = _PyTime_ROUND_UP
3343
} _PyTime_round_t;
3444

45+
3546
/* Convert a time_t to a PyLong. */
3647
PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
3748
time_t sec);

Lib/test/test_poll.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,28 @@ def test_threaded_poll(self):
208208
os.write(w, b'spam')
209209
t.join()
210210

211+
@unittest.skipUnless(threading, 'Threading required for this test.')
212+
@reap_threads
213+
def test_poll_blocks_with_negative_ms(self):
214+
for timeout_ms in [None, -1, -1.0, -0.1, -1e-100]:
215+
# Create two file descriptors. This will be used to unlock
216+
# the blocking call to poll.poll inside the thread
217+
r, w = os.pipe()
218+
pollster = select.poll()
219+
pollster.register(r, select.POLLIN)
220+
221+
poll_thread = threading.Thread(target=pollster.poll, args=(timeout_ms,))
222+
poll_thread.start()
223+
poll_thread.join(timeout=0.1)
224+
self.assertTrue(poll_thread.is_alive())
225+
226+
# Write to the pipe so pollster.poll unblocks and the thread ends.
227+
os.write(w, b'spam')
228+
poll_thread.join()
229+
self.assertFalse(poll_thread.is_alive())
230+
os.close(r)
231+
os.close(w)
232+
211233

212234
def test_main():
213235
run_unittest(PollTests)

Lib/test/test_time.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ class _PyTime(enum.IntEnum):
3636
ROUND_CEILING = 1
3737
# Round to nearest with ties going to nearest even integer
3838
ROUND_HALF_EVEN = 2
39+
# Round away from zero
40+
ROUND_UP = 3
3941

4042
# Rounding modes supported by PyTime
4143
ROUNDING_MODES = (
4244
# (PyTime rounding method, decimal rounding method)
4345
(_PyTime.ROUND_FLOOR, decimal.ROUND_FLOOR),
4446
(_PyTime.ROUND_CEILING, decimal.ROUND_CEILING),
4547
(_PyTime.ROUND_HALF_EVEN, decimal.ROUND_HALF_EVEN),
48+
(_PyTime.ROUND_UP, decimal.ROUND_UP),
4649
)
4750

4851

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix timeout rounding in the select module to round correctly negative timeouts between -1.0 and 0.0.
2+
The functions now block waiting for events as expected. Previously, the call was incorrectly non-blocking.
3+
Patch by Pablo Galindo.

Modules/_testcapimodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3016,7 +3016,8 @@ check_time_rounding(int round)
30163016
{
30173017
if (round != _PyTime_ROUND_FLOOR
30183018
&& round != _PyTime_ROUND_CEILING
3019-
&& round != _PyTime_ROUND_HALF_EVEN) {
3019+
&& round != _PyTime_ROUND_HALF_EVEN
3020+
&& round != _PyTime_ROUND_UP) {
30203021
PyErr_SetString(PyExc_ValueError, "invalid rounding");
30213022
return -1;
30223023
}

Modules/selectmodule.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,15 +213,15 @@ select_select(PyObject *self, PyObject *args)
213213
tvp = (struct timeval *)NULL;
214214
else {
215215
if (_PyTime_FromSecondsObject(&timeout, timeout_obj,
216-
_PyTime_ROUND_CEILING) < 0) {
216+
_PyTime_ROUND_TIMEOUT) < 0) {
217217
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
218218
PyErr_SetString(PyExc_TypeError,
219219
"timeout must be a float or None");
220220
}
221221
return NULL;
222222
}
223223

224-
if (_PyTime_AsTimeval(timeout, &tv, _PyTime_ROUND_CEILING) == -1)
224+
if (_PyTime_AsTimeval(timeout, &tv, _PyTime_ROUND_TIMEOUT) == -1)
225225
return NULL;
226226
if (tv.tv_sec < 0) {
227227
PyErr_SetString(PyExc_ValueError, "timeout must be non-negative");
@@ -543,15 +543,15 @@ poll_poll(pollObject *self, PyObject *args)
543543
}
544544
else {
545545
if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj,
546-
_PyTime_ROUND_CEILING) < 0) {
546+
_PyTime_ROUND_TIMEOUT) < 0) {
547547
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
548548
PyErr_SetString(PyExc_TypeError,
549549
"timeout must be an integer or None");
550550
}
551551
return NULL;
552552
}
553553

554-
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
554+
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_TIMEOUT);
555555
if (ms < INT_MIN || ms > INT_MAX) {
556556
PyErr_SetString(PyExc_OverflowError, "timeout is too large");
557557
return NULL;
@@ -899,15 +899,15 @@ devpoll_poll(devpollObject *self, PyObject *args)
899899
}
900900
else {
901901
if (_PyTime_FromMillisecondsObject(&timeout, timeout_obj,
902-
_PyTime_ROUND_CEILING) < 0) {
902+
_PyTime_ROUND_TIMEOUT) < 0) {
903903
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
904904
PyErr_SetString(PyExc_TypeError,
905905
"timeout must be an integer or None");
906906
}
907907
return NULL;
908908
}
909909

910-
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
910+
ms = _PyTime_AsMilliseconds(timeout, _PyTime_ROUND_TIMEOUT);
911911
if (ms < -1 || ms > INT_MAX) {
912912
PyErr_SetString(PyExc_OverflowError, "timeout is too large");
913913
return NULL;
@@ -1514,7 +1514,7 @@ pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds)
15141514
/* epoll_wait() has a resolution of 1 millisecond, round towards
15151515
infinity to wait at least timeout seconds. */
15161516
if (_PyTime_FromSecondsObject(&timeout, timeout_obj,
1517-
_PyTime_ROUND_CEILING) < 0) {
1517+
_PyTime_ROUND_TIMEOUT) < 0) {
15181518
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
15191519
PyErr_SetString(PyExc_TypeError,
15201520
"timeout must be an integer or None");
@@ -2129,7 +2129,7 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
21292129
}
21302130
else {
21312131
if (_PyTime_FromSecondsObject(&timeout,
2132-
otimeout, _PyTime_ROUND_CEILING) < 0) {
2132+
otimeout, _PyTime_ROUND_TIMEOUT) < 0) {
21332133
PyErr_Format(PyExc_TypeError,
21342134
"timeout argument must be a number "
21352135
"or None, got %.200s",

Python/pytime.c

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,19 @@ _PyTime_Round(double x, _PyTime_round_t round)
8484
volatile double d;
8585

8686
d = x;
87-
if (round == _PyTime_ROUND_HALF_EVEN)
87+
if (round == _PyTime_ROUND_HALF_EVEN){
8888
d = _PyTime_RoundHalfEven(d);
89-
else if (round == _PyTime_ROUND_CEILING)
89+
}
90+
else if (round == _PyTime_ROUND_CEILING){
9091
d = ceil(d);
91-
else
92+
}
93+
else if (round == _PyTime_ROUND_FLOOR) {
9294
d = floor(d);
95+
}
96+
else {
97+
assert(round == _PyTime_ROUND_UP);
98+
d = (d >= 0.0) ? ceil(d) : floor(d);
99+
}
93100
return d;
94101
}
95102

@@ -395,16 +402,29 @@ _PyTime_Divide(const _PyTime_t t, const _PyTime_t k,
395402
return x;
396403
}
397404
else if (round == _PyTime_ROUND_CEILING) {
398-
if (t >= 0)
405+
if (t >= 0){
399406
return (t + k - 1) / k;
400-
else
407+
}
408+
else{
401409
return t / k;
410+
}
402411
}
403-
else {
404-
if (t >= 0)
412+
else if (round == _PyTime_ROUND_FLOOR){
413+
if (t >= 0) {
405414
return t / k;
406-
else
415+
}
416+
else{
407417
return (t - (k - 1)) / k;
418+
}
419+
}
420+
else {
421+
assert(round == _PyTime_ROUND_UP);
422+
if (t >= 0) {
423+
return (t + k - 1) / k;
424+
}
425+
else {
426+
return (t - (k - 1)) / k;
427+
}
408428
}
409429
}
410430

0 commit comments

Comments
 (0)