Commit 74a2e929 authored by Jason Madden's avatar Jason Madden

Workaround the issue with test__systemerror sometimes failing on libuv

The good news is this probably explains a lot of the timer oddities we
see under libuv (see detailed comments in the code). The bad news is
it means we need to rethink some things for libuv.
parent 83b8e650
...@@ -341,6 +341,7 @@ class AbstractLoop(object): ...@@ -341,6 +341,7 @@ class AbstractLoop(object):
# as quickly as possible. # as quickly as possible.
# TODO: There may be a more efficient way to do this using ev_timer_again; # TODO: There may be a more efficient way to do this using ev_timer_again;
# see the "ev_timer" section of the ev manpage (http://linux.die.net/man/3/ev) # see the "ev_timer" section of the ev manpage (http://linux.die.net/man/3/ev)
# Alternatively, setting the ev maximum block time may also work.
self._timer0 = self._ffi.new(self._TIMER_POINTER) self._timer0 = self._ffi.new(self._TIMER_POINTER)
self._init_callback_timer() self._init_callback_timer()
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
""" """
Event-loop hub. Event-loop hub.
""" """
from __future__ import absolute_import from __future__ import absolute_import, print_function
# XXX: FIXME: Refactor to make this smaller # XXX: FIXME: Refactor to make this smaller
# pylint:disable=too-many-lines # pylint:disable=too-many-lines
from functools import partial as _functools_partial from functools import partial as _functools_partial
...@@ -644,7 +644,7 @@ class Hub(RawGreenlet): ...@@ -644,7 +644,7 @@ class Hub(RawGreenlet):
:class:`gevent.core.child`, :class:`gevent.core.stat` :class:`gevent.core.child`, :class:`gevent.core.stat`
""" """
waiter = Waiter() waiter = Waiter(self)
unique = object() unique = object()
watcher.start(waiter.switch, unique) watcher.start(waiter.switch, unique)
try: try:
......
...@@ -150,6 +150,40 @@ class loop(AbstractLoop): ...@@ -150,6 +150,40 @@ class loop(AbstractLoop):
def _start_callback_timer(self): def _start_callback_timer(self):
libuv.uv_timer_start(self._timer0, libuv.gevent_noop, 0, 0) libuv.uv_timer_start(self._timer0, libuv.gevent_noop, 0, 0)
# XXX: The purpose of the callback timer is to ensure that we run
# callbacks as soon as possible on the next iteration of the event loop.
# In libev, a 0 timer expires *after* the IO poll is done (it actually
# determines the time that the IO poll will block for), so
# having the timer present simply spins the loop, and our normal
# prepare watcher kicks in to run the callbacks.
# In libuv, however, timers are run *first*, before prepare callbacks
# and before polling for IO. So this 0 time timer actually does *nothing*.
# From the loop inside uv_run:
# while True:
# uv__run_timers(loop);
# ran_pending = uv__run_pending(loop);
# uv__run_idle(loop);
# uv__run_prepare(loop);
# ...
# uv__io_poll(loop, timeout);
# libuv looks something like this (pseudo code because the real code is
# hard to read):
#
# do {
# run_prepare_callbacks();
# timeout = min(time of all timers or normal block time)
# io_poll()
# run_timers()
# run_pending()
# }
# We need to rethink this to get better behaviour. See
# test__systemerror:TestCallback for an example of an issue
# this reveals. Instead of gevent_noop, we probably need to actually run
# callbacks.
def _stop_aux_watchers(self): def _stop_aux_watchers(self):
libuv.uv_prepare_stop(self._prepare) libuv.uv_prepare_stop(self._prepare)
......
...@@ -3,7 +3,6 @@ import greentest ...@@ -3,7 +3,6 @@ import greentest
import gevent import gevent
from gevent.hub import get_hub from gevent.hub import get_hub
def raise_(ex): def raise_(ex):
raise ex raise ex
...@@ -63,18 +62,24 @@ class Test(greentest.TestCase): ...@@ -63,18 +62,24 @@ class Test(greentest.TestCase):
def test_exception(self): def test_exception(self):
self.start(raise_, Exception('regular exception must not kill the program')) self.start(raise_, Exception('regular exception must not kill the program'))
# XXX: libuv: libuv only allows a 0.001 minimum sleep time argument. gevent.sleep(0.001)
# If we pass that, sometimes TestCallback finds that the callback has not run
# when it tries to tearDown the test. Doubling that actually lets the callback run.
# So there's some interaction with minimum timers and the callback timer (?) which
# uses a timer of 0, asking to be run immediately on the next loop.
gevent.sleep(0.002)
class TestCallback(Test): class TestCallback(Test):
def tearDown(self): def tearDown(self):
if self.x is not None: if self.x is not None:
# XXX: Yield to other greenlets and specifically to other callbacks.
# It's possible that our callback from `start` got scheduled
# *after* the callback from sleep. Or at least, that's what it looks like.
# Only under libuv have we seen test_exception fail with the callback still
# pending. Yielding here (or doubling the time of the sleep) solves the issue
# and lets the callback run.
# What's happening is that sleep timer is running before the prepare callback
# that normally runs callbacks *sometimes*, depending on timing.
# See libuv/loop.py for an explanation.
gevent.sleep(0)
assert not self.x.pending, self.x assert not self.x.pending, self.x
def start(self, *args): def start(self, *args):
......
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