Commit 1a418d12 authored by Jason Madden's avatar Jason Madden

Make libev follow libuv and not update timers by default.

Make Timeout._start_new_or_dummy stop forcing the issue.

The failing test_telnetlib was fixed by avoiding a zero-duration timer. On libuv zero duration timers can block the event loop.
parent 0774d4c1
...@@ -197,6 +197,15 @@ libuv ...@@ -197,6 +197,15 @@ libuv
libev has also been changed to follow this behaviour. libev has also been changed to follow this behaviour.
- Timers of zero duration do not necessarily cause the event loop to
cycle, as they do in libev. Instead, they may be called
immediately. If zero duration timers are added from other zero
duration timer callbacks, this can lead the loop to appear to
hang, as no IO will actually be done.
In the future, zero duration timers may automatically be changed
to check or prepare watchers.
Again, this is extremely experimental and all of it is subject to Again, this is extremely experimental and all of it is subject to
change. change.
......
...@@ -589,6 +589,14 @@ class timer(_base.TimerMixin, watcher): ...@@ -589,6 +589,14 @@ class timer(_base.TimerMixin, watcher):
# This can lead to us being stuck running timers for a terribly # This can lead to us being stuck running timers for a terribly
# long time, which is not good. So default to not updating the # long time, which is not good. So default to not updating the
# time. # time.
# Also, newly-added timers of 0 duration can *also* stall the loop, because
# they'll be seen to be expired immediately. Updating the time can prevent that,
# *if* there was already a timer for a longer duration scheduled.
# XXX: Have our loop implementation turn 0 duration timers into prepare or
# check watchers instead?
update_loop_time_on_start = False update_loop_time_on_start = False
def _update_now(self): def _update_now(self):
......
...@@ -150,9 +150,16 @@ def select(rlist, wlist, xlist, timeout=None): # pylint:disable=unused-argument ...@@ -150,9 +150,16 @@ def select(rlist, wlist, xlist, timeout=None): # pylint:disable=unused-argument
# Ignore interrupted syscalls # Ignore interrupted syscalls
raise raise
if sel_results[0] or sel_results[1] or sel_results[2]: if sel_results[0] or sel_results[1] or sel_results[2] or (timeout is not None and timeout == 0):
# If we actually had stuff ready, go ahead and return it. No need # If we actually had stuff ready, go ahead and return it. No need
# to go through the trouble of doing our own stuff. # to go through the trouble of doing our own stuff.
# Likewise, if the timeout is 0, we already did a 0 timeout
# select and we don't need to do it again. Note that in libuv,
# zero duration timers may be called immediately, without
# cycling the event loop at all. 2.7/test_telnetlib.py "hangs"
# calling zero-duration timers if we go to the loop here.
# However, because this is typically a place where scheduling switches # However, because this is typically a place where scheduling switches
# can occur, we need to make sure that's still the case; otherwise a single # can occur, we need to make sure that's still the case; otherwise a single
# consumer could monopolize the thread. (shows up in test_ftplib.) # consumer could monopolize the thread. (shows up in test_ftplib.)
......
...@@ -13,7 +13,7 @@ module add timeouts to arbitrary code. ...@@ -13,7 +13,7 @@ module add timeouts to arbitrary code.
If a blocking function is called or an intense calculation is ongoing during If a blocking function is called or an intense calculation is ongoing during
which no switches occur, :class:`Timeout` is powerless. which no switches occur, :class:`Timeout` is powerless.
""" """
from __future__ import absolute_import, print_function, division
from gevent._compat import string_types from gevent._compat import string_types
from gevent.hub import getcurrent, _NONE, get_hub from gevent.hub import getcurrent, _NONE, get_hub
...@@ -152,7 +152,7 @@ class Timeout(BaseException): ...@@ -152,7 +152,7 @@ class Timeout(BaseException):
self.timer.start(getcurrent().throw, self.exception) self.timer.start(getcurrent().throw, self.exception)
@classmethod @classmethod
def start_new(cls, timeout=None, exception=None, ref=True, _update=None): def start_new(cls, timeout=None, exception=None, ref=True):
"""Create a started :class:`Timeout`. """Create a started :class:`Timeout`.
This is a shortcut, the exact action depends on *timeout*'s type: This is a shortcut, the exact action depends on *timeout*'s type:
...@@ -169,8 +169,6 @@ class Timeout(BaseException): ...@@ -169,8 +169,6 @@ class Timeout(BaseException):
timeout.start() timeout.start()
return timeout return timeout
timeout = cls(timeout, exception, ref=ref) timeout = cls(timeout, exception, ref=ref)
if _update:
get_hub().loop.update()
timeout.start() timeout.start()
return timeout return timeout
...@@ -189,10 +187,7 @@ class Timeout(BaseException): ...@@ -189,10 +187,7 @@ class Timeout(BaseException):
# under PyPy in synthetic benchmarks it makes no difference. # under PyPy in synthetic benchmarks it makes no difference.
if timeout is None: if timeout is None:
return _FakeTimer return _FakeTimer
# If we don't update the time here (and the timer watcher doesn't), return Timeout.start_new(timeout, exception)
# as under libuv, then certain tests hang, notably the monkey-patched test_telnetlib
# in test_read_eager_A. libev does not demonstrate this behaviour.
return Timeout.start_new(timeout, exception, _update=True)
@property @property
def pending(self): def pending(self):
......
...@@ -578,12 +578,10 @@ class TestCase(TestCaseMetaClass("NewBase", (BaseTestCase,), {})): ...@@ -578,12 +578,10 @@ class TestCase(TestCaseMetaClass("NewBase", (BaseTestCase,), {})):
econtext, ekind, evalue = error econtext, ekind, evalue = error
if kind is not None: if kind is not None:
self.assertIsInstance(kind, type) self.assertIsInstance(kind, type)
try: self.assertIsNotNone(
assert issubclass(ekind, kind), error ekind,
except TypeError as e: "Error must not be none %r" % (error,))
# Seen on PyPy on Windows assert issubclass(ekind, kind), error
print("TYPE ERROR", e, ekind, kind, type(kind))
raise
if value is not None: if value is not None:
if isinstance(value, str): if isinstance(value, str):
self.assertEqual(str(evalue), value) self.assertEqual(str(evalue), value)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment