Commit debb8d83 authored by Jason Madden's avatar Jason Madden

Fixes #1057 by adjusting timers for libuv

But see the comments in the code and the comments in the issue. libuv
has very different timer behaviour than libev.
parent 62e3b300
...@@ -113,8 +113,9 @@ ...@@ -113,8 +113,9 @@
suffers a number of limitations compared to libev, notably: suffers a number of limitations compared to libev, notably:
- Timers (such as ``gevent.sleep`` and ``gevent.Timeout``) only - Timers (such as ``gevent.sleep`` and ``gevent.Timeout``) only
support a resolution of 1ms. Attempting to use something smaller support a resolution of 1ms (in practice, it's closer to 1.5ms).
will automatically increase it to 1ms and issue a warning. Attempting to use something smaller will automatically increase it
to 1ms and issue a warning.
- Using negative timeouts may behave differently from libev. - Using negative timeouts may behave differently from libev.
...@@ -161,7 +162,8 @@ ...@@ -161,7 +162,8 @@
- The order in which timers and other callbacks are invoked may be - The order in which timers and other callbacks are invoked may be
different than in libev. In particular, timers and IO callbacks different than in libev. In particular, timers and IO callbacks
happen in a different order. happen in a different order, and timers may easily be off by up to
half of the supposed 1ms resolution. See :issue:`1057`.
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.
......
...@@ -78,6 +78,10 @@ else: ...@@ -78,6 +78,10 @@ else:
class loop(AbstractLoop): class loop(AbstractLoop):
# XXX: Undocumented. Maybe better named 'timer_resolution'? We can't
# know this in general on libev
min_sleep_time = 0.001 # 1ms
DEFAULT_LOOP_REGENERATES = True DEFAULT_LOOP_REGENERATES = True
error_handler = None error_handler = None
...@@ -219,6 +223,24 @@ class loop(AbstractLoop): ...@@ -219,6 +223,24 @@ class loop(AbstractLoop):
# does. This could also lead to test__systemerror:TestCallback # does. This could also lead to test__systemerror:TestCallback
# appearing to be flaky. # appearing to be flaky.
# As yet another final note, if we are currently running a
# timer callback, meaning we're inside uv__run_timers() in C,
# and the Python starts a new timer, if the Python code then
# update's the loop's time, it's possible that timer will
# expire *and be run in the same iteration of the loop*. This
# is trivial to do: In sequential code, anything after
# `gevent.sleep(0.1)` is running in a timer callback. Starting
# a new timer---e.g., another gevent.sleep() call---will
# update the time, *before* uv__run_timers exits, meaning
# other timers get a chance to run before our check or prepare
# watcher callbacks do. Therefore, we do indeed have to have a 0
# timer to run callbacks---it gets inserted before any other user
# timers---ideally, this should be especially careful about how much time
# it runs for.
# AND YET: We can't actually do that. We get timeouts that I haven't fully
# investigated if we do. Probably stuck in a timer loop.
libuv.uv_check_start(self._timer0, libuv.python_prepare_callback) libuv.uv_check_start(self._timer0, libuv.python_prepare_callback)
......
...@@ -67,7 +67,7 @@ class ThreadPool(GroupMappingMixin): ...@@ -67,7 +67,7 @@ class ThreadPool(GroupMappingMixin):
self.manager.kill() self.manager.kill()
while self._size < size: while self._size < size:
self._add_thread() self._add_thread()
delay = 0.0001 delay = getattr(self.hub.loop, 'min_sleep_time', 0.0001) # For libuv
while self._size > size: while self._size > size:
while self._size - size > self.task_queue.unfinished_tasks: while self._size - size > self.task_queue.unfinished_tasks:
self.task_queue.put(None) self.task_queue.put(None)
......
...@@ -666,6 +666,12 @@ class TestCase(TestCaseMetaClass("NewBase", (BaseTestCase,), {})): ...@@ -666,6 +666,12 @@ class TestCase(TestCaseMetaClass("NewBase", (BaseTestCase,), {})):
self.assertEqual(sig.keywords, gevent_sig.keywords, func_name) self.assertEqual(sig.keywords, gevent_sig.keywords, func_name)
self.assertEqual(sig.defaults, gevent_sig.defaults, func_name) self.assertEqual(sig.defaults, gevent_sig.defaults, func_name)
def assertEqualFlakyRaceCondition(self, a, b):
try:
self.assertEqual(a, b)
except AssertionError:
reraiseFlakyTestRaceCondition()
if not hasattr(TestCase, 'assertRaisesRegex'): if not hasattr(TestCase, 'assertRaisesRegex'):
TestCase.assertRaisesRegex = TestCase.assertRaisesRegexp TestCase.assertRaisesRegex = TestCase.assertRaisesRegexp
......
...@@ -341,16 +341,16 @@ class TestMaxsize(TestCase): ...@@ -341,16 +341,16 @@ class TestMaxsize(TestCase):
def test_inc(self): def test_inc(self):
self.pool = ThreadPool(0) self.pool = ThreadPool(0)
done = [] done = []
# Try to be careful not to tick over the libuv timer.
# See libuv/loop.py:_start_callback_timer
gevent.spawn(self.pool.spawn, done.append, 1) gevent.spawn(self.pool.spawn, done.append, 1)
gevent.spawn_later(0.001, self.pool.spawn, done.append, 2) gevent.spawn_later(0.01, self.pool.spawn, done.append, 2)
gevent.sleep(0.01) gevent.sleep(0.02)
self.assertEqual(done, []) self.assertEqual(done, [])
self.pool.maxsize = 1 self.pool.maxsize = 1
gevent.sleep(0.01) gevent.sleep(0.02)
try:
self.assertEqual(done, [1, 2]) self.assertEqualFlakyRaceCondition(done, [1, 2])
except AssertionError:
greentest.reraiseFlakyTestRaceConditionLibuv()
def test_setzero(self): def test_setzero(self):
pool = self.pool = ThreadPool(3) pool = self.pool = ThreadPool(3)
......
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