Issue #20311: asyncio: Add a granularity attribute to BaseEventLoop: maximum

between the resolution of the BaseEventLoop.time() method and the resolution of
the selector. The granuarility is used in the scheduler to round time and
deadline.
This commit is contained in:
Victor Stinner 2014-01-25 15:01:33 +01:00
parent 635fca9704
commit f67255ab94
6 changed files with 44 additions and 1 deletions

View file

@ -144,6 +144,12 @@ a different clock than :func:`time.time`.
Return the current time, as a :class:`float` value, according to the Return the current time, as a :class:`float` value, according to the
event loop's internal clock. event loop's internal clock.
.. attribute:: BaseEventLoop.granularity
Granularity of the time: maximum between the resolution of the
:meth:`BaseEventLoop.time` method and the resolution of the selector (see
:attr:`selectors.BaseSelector.resolution`).
.. seealso:: .. seealso::
The :func:`asyncio.sleep` function. The :func:`asyncio.sleep` function.

View file

@ -18,6 +18,7 @@ import collections
import concurrent.futures import concurrent.futures
import heapq import heapq
import logging import logging
import math
import socket import socket
import subprocess import subprocess
import time import time
@ -96,6 +97,7 @@ class BaseEventLoop(events.AbstractEventLoop):
self._default_executor = None self._default_executor = None
self._internal_fds = 0 self._internal_fds = 0
self._running = False self._running = False
self.granularity = time.get_clock_info('monotonic').resolution
def _make_socket_transport(self, sock, protocol, waiter=None, *, def _make_socket_transport(self, sock, protocol, waiter=None, *,
extra=None, server=None): extra=None, server=None):
@ -603,6 +605,8 @@ class BaseEventLoop(events.AbstractEventLoop):
elif self._scheduled: elif self._scheduled:
# Compute the desired timeout. # Compute the desired timeout.
when = self._scheduled[0]._when when = self._scheduled[0]._when
# round deadline aways from zero
when = math.ceil(when / self.granularity) * self.granularity
deadline = max(0, when - self.time()) deadline = max(0, when - self.time())
if timeout is None: if timeout is None:
timeout = deadline timeout = deadline
@ -629,6 +633,8 @@ class BaseEventLoop(events.AbstractEventLoop):
# Handle 'later' callbacks that are ready. # Handle 'later' callbacks that are ready.
now = self.time() now = self.time()
# round current time aways from zero
now = math.ceil(now / self.granularity) * self.granularity
while self._scheduled: while self._scheduled:
handle = self._scheduled[0] handle = self._scheduled[0]
if handle._when > now: if handle._when > now:

View file

@ -34,6 +34,7 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop):
selector = selectors.DefaultSelector() selector = selectors.DefaultSelector()
logger.debug('Using selector: %s', selector.__class__.__name__) logger.debug('Using selector: %s', selector.__class__.__name__)
self._selector = selector self._selector = selector
self.granularity = max(selector.resolution, self.granularity)
self._make_self_pipe() self._make_self_pipe()
def _make_socket_transport(self, sock, protocol, waiter=None, *, def _make_socket_transport(self, sock, protocol, waiter=None, *,

View file

@ -1116,6 +1116,29 @@ class EventLoopTestsMixin:
r.close() r.close()
w.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: class SubprocessTestsMixin:

View file

@ -39,7 +39,9 @@ def list_to_buffer(l=()):
class BaseSelectorEventLoopTests(unittest.TestCase): class BaseSelectorEventLoopTests(unittest.TestCase):
def setUp(self): 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): def test_make_socket_transport(self):
m = unittest.mock.Mock() m = unittest.mock.Mock()

View file

@ -36,6 +36,11 @@ Core and Builtins
Library Library
------- -------
- Issue #20311: asyncio: Add a granularity attribute to BaseEventLoop: maximum
between the resolution of the BaseEventLoop.time() method and the resolution
of the selector. The granuarility is used in the scheduler to round time and
deadline.
- Issue #20311: selectors: Add a resolution attribute to BaseSelector. - Issue #20311: selectors: Add a resolution attribute to BaseSelector.
- Issue #20189: unittest.mock now no longer assumes that any object for - Issue #20189: unittest.mock now no longer assumes that any object for