diff -r c3e787c44885 -r 40ad70cfdb30 Doc/library/asyncio-eventloop.rst --- a/Doc/library/asyncio-eventloop.rst Thu Jan 23 17:40:59 2014 +0100 +++ b/Doc/library/asyncio-eventloop.rst Fri Jan 24 10:29:46 2014 +0100 @@ -144,6 +144,12 @@ a different clock than :func:`time.time` Return the current time, as a :class:`float` value, according to the event loop's internal clock. +.. attribute:: BaseEventLoop.granularity + + Granularity of the time: maximum of the resolution of the + :meth:`BaseEventLoop.time` method and the resolution of the selector (see + :attr:`selectors.BaseSelector.resolution`). + .. seealso:: The :func:`asyncio.sleep` function. diff -r c3e787c44885 -r 40ad70cfdb30 Doc/library/selectors.rst --- a/Doc/library/selectors.rst Thu Jan 23 17:40:59 2014 +0100 +++ b/Doc/library/selectors.rst Fri Jan 24 10:29:46 2014 +0100 @@ -98,6 +98,13 @@ below: :class:`BaseSelector` and its concrete implementations support the :term:`context manager` protocol. + + .. attribute:: resolution + + Resolution of the selector in seconds, or ``None`` if unknown. + For example, :class:`SelectSelector` has a resolution of 1e-6 seconds + (1 microsecond). + .. method:: register(fileobj, events, data=None) Register a file object for selection, monitoring it for I/O events. diff -r c3e787c44885 -r 40ad70cfdb30 Lib/asyncio/base_events.py --- a/Lib/asyncio/base_events.py Thu Jan 23 17:40:59 2014 +0100 +++ b/Lib/asyncio/base_events.py Fri Jan 24 10:29:46 2014 +0100 @@ -18,6 +18,7 @@ import collections import concurrent.futures import heapq import logging +import math import socket import subprocess import time @@ -96,6 +97,7 @@ class BaseEventLoop(events.AbstractEvent self._default_executor = None self._internal_fds = 0 self._running = False + self.granularity = time.get_clock_info('monotonic').resolution def _make_socket_transport(self, sock, protocol, waiter=None, *, extra=None, server=None): @@ -603,7 +605,10 @@ class BaseEventLoop(events.AbstractEvent elif self._scheduled: # Compute the desired timeout. when = self._scheduled[0]._when - deadline = max(0, when - self.time()) + now = self.time() + # round deadline aways from zero + when = math.ceil(when / self.granularity) * self.granularity + deadline = max(0, when - now) if timeout is None: timeout = deadline else: @@ -629,6 +634,8 @@ class BaseEventLoop(events.AbstractEvent # Handle 'later' callbacks that are ready. now = self.time() + # round current time aways from zero + now = math.ceil(now / self.granularity) * self.granularity while self._scheduled: handle = self._scheduled[0] if handle._when > now: diff -r c3e787c44885 -r 40ad70cfdb30 Lib/asyncio/proactor_events.py --- a/Lib/asyncio/proactor_events.py Thu Jan 23 17:40:59 2014 +0100 +++ b/Lib/asyncio/proactor_events.py Fri Jan 24 10:29:46 2014 +0100 @@ -329,6 +329,7 @@ class BaseProactorEventLoop(base_events. super().__init__() logger.debug('Using proactor: %s', proactor.__class__.__name__) self._proactor = proactor + # self.granularity = max(proactor.resolution, self.granularity) self._selector = proactor # convenient alias self._self_reading_future = None self._accept_futures = {} # socket file descriptor => Future diff -r c3e787c44885 -r 40ad70cfdb30 Lib/asyncio/selector_events.py --- a/Lib/asyncio/selector_events.py Thu Jan 23 17:40:59 2014 +0100 +++ b/Lib/asyncio/selector_events.py Fri Jan 24 10:29:46 2014 +0100 @@ -34,6 +34,8 @@ class BaseSelectorEventLoop(base_events. selector = selectors.DefaultSelector() logger.debug('Using selector: %s', selector.__class__.__name__) self._selector = selector + if selector.resolution is not None: + self.granularity = max(selector.resolution, self.granularity) self._make_self_pipe() def _make_socket_transport(self, sock, protocol, waiter=None, *, diff -r c3e787c44885 -r 40ad70cfdb30 Lib/selectors.py --- a/Lib/selectors.py Thu Jan 23 17:40:59 2014 +0100 +++ b/Lib/selectors.py Fri Jan 24 10:29:46 2014 +0100 @@ -8,7 +8,6 @@ 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 @@ -82,6 +81,7 @@ class BaseSelector(metaclass=ABCMeta): depending on the platform. The default `Selector` class uses the most performant implementation on the current platform. """ + resolution = None @abstractmethod def register(self, fileobj, events, data=None): @@ -278,6 +278,7 @@ class _BaseSelectorImpl(BaseSelector): class SelectSelector(_BaseSelectorImpl): """Select-based selector.""" + resolution = 1e-6 def __init__(self): super().__init__() @@ -331,6 +332,7 @@ if hasattr(select, 'poll'): class PollSelector(_BaseSelectorImpl): """Poll-based selector.""" + resolution = 1e-3 def __init__(self): super().__init__() @@ -357,9 +359,8 @@ if hasattr(select, 'poll'): elif timeout <= 0: timeout = 0 else: - # poll() has a resolution of 1 millisecond, round away from - # zero to wait *at least* timeout seconds. - timeout = int(math.ceil(timeout * 1e3)) + # round towards zero + timeout = int(timeout * 1000) ready = [] try: fd_event_list = self._poll.poll(timeout) @@ -382,6 +383,7 @@ if hasattr(select, 'epoll'): class EpollSelector(_BaseSelectorImpl): """Epoll-based selector.""" + resolution = 1e-3 def __init__(self): super().__init__() @@ -413,12 +415,8 @@ if hasattr(select, 'epoll'): def select(self, timeout=None): if timeout is None: timeout = -1 - elif timeout <= 0: + 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: @@ -446,6 +444,7 @@ if hasattr(select, 'kqueue'): class KqueueSelector(_BaseSelectorImpl): """Kqueue-based selector.""" + resolution = 1e-9 def __init__(self): super().__init__() diff -r c3e787c44885 -r 40ad70cfdb30 Lib/test/test_asyncio/test_events.py --- a/Lib/test/test_asyncio/test_events.py Thu Jan 23 17:40:59 2014 +0100 +++ b/Lib/test/test_asyncio/test_events.py Fri Jan 24 10:29:46 2014 +0100 @@ -1116,6 +1116,30 @@ class EventLoopTestsMixin: r.close() w.close() + def test_timeout_rounding(self): + def _run_once(): + self.loop._run_once_counter += 1 + orig_run_once() + + orig_run_once = self.loop._run_once + self.loop._run_once_counter = 0 + self.loop._run_once = _run_once + + calls = [] + + @tasks.coroutine + def wait(): + loop = self.loop + calls.append(loop._run_once_counter) + yield from tasks.sleep(loop.granularity * 10, loop=loop) + calls.append(loop._run_once_counter) + yield from tasks.sleep(loop.granularity / 10, loop=loop) + calls.append(loop._run_once_counter) + + self.loop.run_until_complete(wait()) + calls.append(self.loop._run_once_counter) + self.assertEqual(calls, [1, 3, 5, 6]) + class SubprocessTestsMixin: diff -r c3e787c44885 -r 40ad70cfdb30 Lib/test/test_asyncio/test_selector_events.py --- a/Lib/test/test_asyncio/test_selector_events.py Thu Jan 23 17:40:59 2014 +0100 +++ b/Lib/test/test_asyncio/test_selector_events.py Fri Jan 24 10:29:46 2014 +0100 @@ -39,7 +39,9 @@ def list_to_buffer(l=()): class BaseSelectorEventLoopTests(unittest.TestCase): def setUp(self): - self.loop = TestBaseSelectorEventLoop(unittest.mock.Mock()) + selector = unittest.mock.Mock() + selector.resolution = 1e-3 + self.loop = TestBaseSelectorEventLoop(selector) def test_make_socket_transport(self): m = unittest.mock.Mock() diff -r c3e787c44885 -r 40ad70cfdb30 Lib/test/test_epoll.py --- a/Lib/test/test_epoll.py Thu Jan 23 17:40:59 2014 +0100 +++ b/Lib/test/test_epoll.py Fri Jan 24 10:29:46 2014 +0100 @@ -254,17 +254,6 @@ 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.monotonic() - epoll.poll(timeout) - dt = time.monotonic() - t0 - self.assertGreaterEqual(dt, timeout) - def test_main(): support.run_unittest(TestEPoll) diff -r c3e787c44885 -r 40ad70cfdb30 Lib/test/test_selectors.py --- a/Lib/test/test_selectors.py Thu Jan 23 17:40:59 2014 +0100 +++ b/Lib/test/test_selectors.py Fri Jan 24 10:29:46 2014 +0100 @@ -363,25 +363,6 @@ 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 away from zero 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-3, 1e-4): - t0 = perf_counter() - s.select(timeout) - dt = perf_counter() - t0 - clock = get_clock_info('perf_counter') - self.assertGreaterEqual(dt, timeout, - "%.30f < %.30f ; clock=%s" - % (dt, timeout, clock)) - class ScalableSelectorMixIn: diff -r c3e787c44885 -r 40ad70cfdb30 Misc/NEWS --- a/Misc/NEWS Thu Jan 23 17:40:59 2014 +0100 +++ b/Misc/NEWS Fri Jan 24 10:29:46 2014 +0100 @@ -32,21 +32,12 @@ Core and Builtins Library ------- -- Issue #20311: selector.PollSelector.select() now rounds the timeout away from - zero, instead of rounding towards zero. For example, a timeout of one - microsecond is now rounded to one millisecond, instead of being rounded to - zero. - - Issue #20317: ExitStack.__exit__ could create a self-referential loop if an exception raised by a cleanup operation already had its context set correctly (for example, by the @contextmanager decorator). The infinite loop this caused is now avoided by checking if the expected context is already set before trying to fix it. -- Issue #20311: select.epoll.poll() now rounds the timeout away from zero, - instead of rounding towards zero. For example, a timeout of one microsecond - is now rounded to one millisecond, instead of being rounded to zero. - - Issue #20262: Warnings are raised now when duplicate names are added in the ZIP file or too long ZIP file comment is truncated. @@ -120,7 +111,7 @@ IDLE --Issue #17390: Add Python version to Idle editor window title bar. Original patches by Edmond Burnett and Kent Johnson. - + - Issue #18960: IDLE now ignores the source encoding declaration on the second line if the first line contains anything except a comment. diff -r c3e787c44885 -r 40ad70cfdb30 Modules/selectmodule.c --- a/Modules/selectmodule.c Thu Jan 23 17:40:59 2014 +0100 +++ b/Modules/selectmodule.c Fri Jan 24 10:29:46 2014 +0100 @@ -1458,9 +1458,7 @@ pyepoll_poll(pyEpoll_Object *self, PyObj return NULL; } else { - /* epoll_wait() has a resolution of 1 millisecond, round away from zero - to wait *at least* dtimeout seconds. */ - timeout = (int)ceil(dtimeout * 1000.0); + timeout = (int)(dtimeout * 1000.0); } if (maxevents == -1) {