diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 4b8e3feefe7..05a4c3871c0 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -627,16 +627,11 @@ def _run_once(self): t1 = self.time() # FIXME: remove these debug info (issue #20452) dt = t1-t0 - if timeout is not None: - if dt < timeout and not event_list: - print("WARNING: selector.select(timeout=%.20f) took dt=%.20f sec (dt-timeout=%+.20f)" - % (timeout, dt, dt-timeout), file=sys.__stdout__) - print("WARNING: dt+%.20f > timeout? %s" - % (self._granularity, (dt + self._granularity) > timeout), file=sys.__stdout__) - else: - if not event_list: - print("WARNING: selector.select(timeout=%r) took dt=%.20f sec" - % (timeout, dt), file=sys.__stdout__) + if timeout is not None and dt < timeout and not event_list: + print("WARNING: selector.select(timeout=%.20f) took dt=%.20f sec (dt-timeout=%+.20f)" + % (timeout, dt, dt-timeout), file=sys.__stdout__) + print("WARNING: dt+%.20f > timeout? %s" + % (self._granularity, (dt + self._granularity) > timeout), file=sys.__stdout__) if t1-t0 >= 1: level = logging.INFO else: diff --git a/Lib/selectors.py b/Lib/selectors.py index b1b530afcb4..52ee8dbd564 100644 --- a/Lib/selectors.py +++ b/Lib/selectors.py @@ -8,6 +8,7 @@ from abc import ABCMeta, abstractmethod, abstractproperty from collections import namedtuple, Mapping import functools +import math import select import sys @@ -369,8 +370,9 @@ def select(self, timeout=None): elif timeout <= 0: timeout = 0 else: - # Round towards zero - timeout = int(timeout * 1000) + # poll() has a resolution of 1 millisecond, round away from + # zero to wait *at least* timeout seconds. + timeout = int(math.ceil(timeout * 1e3)) ready = [] try: fd_event_list = self._poll.poll(timeout) @@ -430,6 +432,10 @@ def select(self, timeout=None): timeout = -1 elif timeout <= 0: timeout = 0 + else: + # epoll_wait() has a resolution of 1 millisecond, round away + # from zero to wait *at least* timeout seconds. + timeout = math.ceil(timeout * 1e3) * 1e-3 max_ev = len(self._fd_to_key) ready = [] try: diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index aef4993f285..f0781ab78f2 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -28,6 +28,15 @@ from asyncio import selector_events from asyncio import test_utils +# FIXME: remove these info, used for debug purpose (issue #20452) +print("time.monotonic() info: %r" % (time.get_clock_info('monotonic'),)) +try: + SC_CLK_TCK = os.sysconf('SC_CLK_TCK') + print("os.sysconf('SC_CLK_TCK') = %s" % SC_CLK_TCK) +except Exception: + pass +# FIXME: remove these info, used for debug purpose (issue #20452) + def data_file(filename): if hasattr(support, 'TEST_HOME_DIR'): @@ -1157,11 +1166,6 @@ def main(): w.close() def test_timeout_rounding(self): - # FIXME: remove this imports, used for debug purpose (issue #20452) - import time - import platform - import os - def _run_once(): self.loop._run_once_counter += 1 orig_run_once() @@ -1182,17 +1186,10 @@ def wait(): self.loop.run_until_complete(wait()) calls.append(self.loop._run_once_counter) - try: - SC_CLK_TCK = os.sysconf('SC_CLK_TCK') - except Exception: - SC_CLK_TCK = None self.assertEqual(calls, [1, 3, 5, 6], # FIXME: remove these info, used for debug purpose (issue #20452) (self.loop._granularity, - self.loop._selector.resolution, - time.get_clock_info('monotonic'), - SC_CLK_TCK, - platform.platform())) + self.loop._selector.resolution)) class SubprocessTestsMixin: diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index d44e8de929c..0c9b9d9f8c9 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -1458,7 +1458,9 @@ pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds) return NULL; } else { - timeout = (int)(dtimeout * 1000.0); + /* epoll_wait() has a resolution of 1 millisecond, round away from zero + to wait *at least* dtimeout seconds. */ + timeout = (int)ceil(dtimeout * 1000.0); } if (maxevents == -1) {