diff -r a5bd41cae6df Lib/selectors.py --- a/Lib/selectors.py Thu Jan 16 18:58:01 2014 +0100 +++ b/Lib/selectors.py Mon Jan 20 14:54:34 2014 +0100 @@ -8,6 +8,7 @@ This module allows high-level and effici from abc import ABCMeta, abstractmethod from collections import namedtuple, Mapping import functools +import math import select import sys @@ -351,7 +352,12 @@ if hasattr(select, 'poll'): return key def select(self, timeout=None): - timeout = None if timeout is None else max(int(1000 * timeout), 0) + if timeout is None: + timeout = None + elif timeout < 0: + timeout = 0 + else: + timeout = int(math.ceil(timeout * 1000.0)) ready = [] try: fd_event_list = self._poll.poll(timeout) diff -r a5bd41cae6df Lib/test/test_epoll.py --- a/Lib/test/test_epoll.py Thu Jan 16 18:58:01 2014 +0100 +++ b/Lib/test/test_epoll.py Mon Jan 20 14:54:34 2014 +0100 @@ -255,6 +255,17 @@ class TestEPoll(unittest.TestCase): self.addCleanup(epoll.close) self.assertEqual(os.get_inheritable(epoll.fileno()), False) + def test_timeout_rounding(self): + # epoll_wait() has a resolution of 1 millisecond, check if the timeout + # is correctly rounded to the upper bound + epoll = select.epoll() + self.addCleanup(epoll.close) + for timeout in (1e-2, 1e-3, 1e-4): + t0 = time.perf_counter() + epoll.poll(timeout) + dt = time.perf_counter() - t0 + self.assertGreaterEqual(dt, timeout) + def test_main(): support.run_unittest(TestEPoll) diff -r a5bd41cae6df Lib/test/test_selectors.py --- a/Lib/test/test_selectors.py Thu Jan 16 18:58:01 2014 +0100 +++ b/Lib/test/test_selectors.py Mon Jan 20 14:54:34 2014 +0100 @@ -5,7 +5,7 @@ import selectors import signal import socket from test import support -from time import sleep +from time import sleep, perf_counter import unittest import unittest.mock try: @@ -363,6 +363,23 @@ class BaseSelectorTestCase(unittest.Test self.assertFalse(s.select(2)) self.assertLess(time() - t, 2.5) + def test_timeout_rounding(self): + # Issue #20311: Timeout must be rounded to upper bound to wait + # *at least* timeout seconds. For example, epoll_wait() has a + # resolution of 1 ms (10^-3), epoll.select(0.0001) must wait 1 ms, + # not 0 ms. + s = self.SELECTOR() + self.addCleanup(s.close) + + rd, wr = self.make_socketpair() + s.register(rd, selectors.EVENT_READ) + + for timeout in (1e-2, 1e-4, 1e-7, 1e-10): + t0 = perf_counter() + s.select(timeout) + dt = perf_counter() - t0 + self.assertGreaterEqual(dt, timeout) + class ScalableSelectorMixIn: diff -r a5bd41cae6df Modules/selectmodule.c --- a/Modules/selectmodule.c Thu Jan 16 18:58:01 2014 +0100 +++ b/Modules/selectmodule.c Mon Jan 20 14:54:34 2014 +0100 @@ -1458,7 +1458,9 @@ pyepoll_poll(pyEpoll_Object *self, PyObj return NULL; } else { - timeout = (int)(dtimeout * 1000.0); + /* epoll_wait() has a resolution of 1 millisecond, round to the upper + bound to wait *at least* dtimeout seconds. */ + timeout = (int)ceil(dtimeout * 1000.0); } if (maxevents == -1) {