Commit 3b30b448 authored by Jason Madden's avatar Jason Madden

libuv turns 0 duration timers into check watchers so the loop cycles.

parent b632c4fc
...@@ -93,7 +93,7 @@ Other Changes ...@@ -93,7 +93,7 @@ Other Changes
support in certain tools (setuptools v24.2.1 or newer is required). support in certain tools (setuptools v24.2.1 or newer is required).
See :issue:`995`. See :issue:`995`.
- pywgi also catches and ignores by default - pywsgi also catches and ignores by default
:const:`errno.WSAECONNABORTED` on Windows. Initial patch in :const:`errno.WSAECONNABORTED` on Windows. Initial patch in
:pr:`999` by Jan van Valburg. :pr:`999` by Jan van Valburg.
...@@ -218,8 +218,9 @@ libuv ...@@ -218,8 +218,9 @@ libuv
duration timer callbacks, this can lead the loop to appear to duration timer callbacks, this can lead the loop to appear to
hang, as no IO will actually be done. hang, as no IO will actually be done.
In the future, zero duration timers may automatically be changed To mitigate this issue, ``loop.timer()`` detects attempts to use
to check or prepare watchers. zero duration timers and turns them into a check watcher. check
watchers do not support the ``again`` method.
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.
......
...@@ -466,3 +466,10 @@ class loop(AbstractLoop): ...@@ -466,3 +466,10 @@ class loop(AbstractLoop):
io_watcher._no_more_watchers = lambda: delitem(io_watchers, fd) io_watcher._no_more_watchers = lambda: delitem(io_watchers, fd)
return io_watcher.multiplex(events) return io_watcher.multiplex(events)
def timer(self, after, repeat=0.0, ref=True, priority=None):
if after <= 0 and repeat <= 0:
# Make sure we can spin the loop. See timer.
# XXX: Note that this doesn't have a `again` method.
return self._watchers.OneShotCheck(self, ref, priority)
return super(loop, self).timer(after, repeat, ref, priority)
...@@ -590,18 +590,18 @@ class timer(_base.TimerMixin, watcher): ...@@ -590,18 +590,18 @@ class timer(_base.TimerMixin, watcher):
# 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 # Also, newly-added timers of 0 duration can *also* stall the
# they'll be seen to be expired immediately. Updating the time can prevent that, # loop, because they'll be seen to be expired immediately.
# *if* there was already a timer for a longer duration scheduled. # 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 # To mitigate the above problems, our loop implementation turns
# check watchers instead? # zero duration timers into check watchers instead using OneShotCheck.
# This ensures the loop cycles. Of course, the 'again' method does
# nothing on them and doesn't exist. In practice that's not an issue.
update_loop_time_on_start = False update_loop_time_on_start = False
def _update_now(self):
self.loop.update()
_again = False _again = False
def _watcher_ffi_init(self, args): def _watcher_ffi_init(self, args):
...@@ -707,3 +707,21 @@ class idle(_base.IdleMixin, watcher): ...@@ -707,3 +707,21 @@ class idle(_base.IdleMixin, watcher):
# Because libuv doesn't support priorities, idle watchers are # Because libuv doesn't support priorities, idle watchers are
# potentially quite a bit different than under libev # potentially quite a bit different than under libev
pass pass
class check(_base.CheckMixin, watcher):
pass
class OneShotCheck(check):
_watcher_skip_ffi = True
def start(self, callback, *args):
@functools.wraps(callback)
def _callback(*args):
self.stop()
return callback(*args)
return check.start(self, _callback, *args)
class prepare(_base.PrepareMixin, watcher):
pass
...@@ -142,15 +142,21 @@ class Timeout(BaseException): ...@@ -142,15 +142,21 @@ class Timeout(BaseException):
def start(self): def start(self):
"""Schedule the timeout.""" """Schedule the timeout."""
assert not self.pending, '%r is already started; to restart it, cancel it first' % self if self.pending:
if self.seconds is None: # "fake" timeout (never expires) raise AssertionError('%r is already started; to restart it, cancel it first' % self)
if self.seconds is None:
# "fake" timeout (never expires)
return return
if self.exception is None or self.exception is False or isinstance(self.exception, string_types): if self.exception is None or self.exception is False or isinstance(self.exception, string_types):
# timeout that raises self # timeout that raises self
self.timer.start(getcurrent().throw, self) throws = self
else: # regular timeout with user-provided exception else:
self.timer.start(getcurrent().throw, self.exception) # regular timeout with user-provided exception
throws = self.exception
self.timer.start(getcurrent().throw, throws)
@classmethod @classmethod
def start_new(cls, timeout=None, exception=None, ref=True): def start_new(cls, timeout=None, exception=None, ref=True):
......
...@@ -39,6 +39,7 @@ else: ...@@ -39,6 +39,7 @@ else:
DEBUG = False DEBUG = False
RUN_LEAKCHECKS = os.getenv('GEVENTTEST_LEAKCHECK') RUN_LEAKCHECKS = os.getenv('GEVENTTEST_LEAKCHECK')
RUN_COVERAGE = os.getenv("COVERAGE_PROCESS_START")
# Generally, ignore the portions that are only implemented # Generally, ignore the portions that are only implemented
# on particular platforms; they generally contain partial # on particular platforms; they generally contain partial
...@@ -50,6 +51,7 @@ if WIN: ...@@ -50,6 +51,7 @@ if WIN:
PY2 = None PY2 = None
PY3 = None PY3 = None
PY34 = None PY34 = None
PY35 = None
PY36 = None PY36 = None
PY37 = None PY37 = None
...@@ -61,6 +63,8 @@ if sys.version_info[0] == 3: ...@@ -61,6 +63,8 @@ if sys.version_info[0] == 3:
PY3 = True PY3 = True
if sys.version_info[1] >= 4: if sys.version_info[1] >= 4:
PY34 = True PY34 = True
if sys.version_info[1] >= 5:
PY35 = True
if sys.version_info[1] >= 6: if sys.version_info[1] >= 6:
PY36 = True PY36 = True
if sys.version_info[1] >= 7: if sys.version_info[1] >= 7:
...@@ -78,6 +82,12 @@ elif sys.version_info[0] == 2: ...@@ -78,6 +82,12 @@ elif sys.version_info[0] == 2:
PYPY3 = PYPY and PY3 PYPY3 = PYPY and PY3
PYGTE279 = (
sys.version_info[0] == 2
and sys.version_info[1] >= 7
and sys.version_info[2] >= 9
)
if WIN: if WIN:
NON_APPLICABLE_SUFFIXES.append("posix") NON_APPLICABLE_SUFFIXES.append("posix")
# This is intimately tied to FileObjectPosix # This is intimately tied to FileObjectPosix
...@@ -110,3 +120,5 @@ from errno import ECONNRESET ...@@ -110,3 +120,5 @@ from errno import ECONNRESET
CONN_ABORTED_ERRORS.append(ECONNRESET) CONN_ABORTED_ERRORS.append(ECONNRESET)
CONN_ABORTED_ERRORS = frozenset(CONN_ABORTED_ERRORS) CONN_ABORTED_ERRORS = frozenset(CONN_ABORTED_ERRORS)
RESOLVER_ARES = os.getenv('GEVENT_RESOLVER') == 'ares'
...@@ -6,22 +6,17 @@ import os ...@@ -6,22 +6,17 @@ import os
import sys import sys
import struct import struct
from greentest.sysinfo import RUNNING_ON_APPVEYOR as APPVEYOR
from greentest.sysinfo import RUNNING_ON_TRAVIS as TRAVIS
from greentest.sysinfo import RUN_LEAKCHECKS as LEAKTEST
from greentest.sysinfo import RUN_COVERAGE as COVERAGE
from greentest.sysinfo import RESOLVER_ARES
APPVEYOR = os.getenv('APPVEYOR') from greentest.sysinfo import PYPY
TRAVIS = os.getenv('TRAVIS') from greentest.sysinfo import PY3
LEAKTEST = os.getenv('GEVENTTEST_LEAKCHECK') from greentest.sysinfo import PY35
COVERAGE = os.getenv("COVERAGE_PROCESS_START")
PYPY = hasattr(sys, 'pypy_version_info') from greentest.sysinfo import LIBUV
PY3 = sys.version_info[0] >= 3
PY27 = sys.version_info[0] == 2 and sys.version_info[1] == 7
PY35 = sys.version_info[0] >= 3 and sys.version_info[1] >= 5
PYGTE279 = (
sys.version_info[0] == 2
and sys.version_info[1] >= 7
and sys.version_info[2] >= 9
)
RESOLVER_ARES = os.getenv('GEVENT_RESOLVER') == 'ares'
LIBUV = os.getenv('GEVENT_CORE_CFFI_ONLY') == 'libuv' # XXX: Formalize this better
FAILING_TESTS = [ FAILING_TESTS = [
......
from __future__ import absolute_import, print_function, division
import greentest import greentest
import gevent import gevent
from gevent.event import Event, AsyncResult from gevent.event import Event, AsyncResult
...@@ -49,7 +50,7 @@ class TestWaitEvent(greentest.GenericWaitTestCase): ...@@ -49,7 +50,7 @@ class TestWaitEvent(greentest.GenericWaitTestCase):
# immediately add the waiter and call it # immediately add the waiter and call it
o = gevent.wait((event,), timeout=0.01) o = gevent.wait((event,), timeout=0.01)
self.assertFalse(event.ready()) self.assertFalse(event.ready())
self.assertFalse(event in o, o) self.assertNotIn(event, o)
gevent.spawn(waiter).join() gevent.spawn(waiter).join()
...@@ -87,31 +88,40 @@ class TestAsyncResult(greentest.TestCase): ...@@ -87,31 +88,40 @@ class TestAsyncResult(greentest.TestCase):
self.assertEqual(e.exc_info, ()) self.assertEqual(e.exc_info, ())
self.assertEqual(e.exception, None) self.assertEqual(e.exception, None)
class MyException(Exception):
pass
def waiter(): def waiter():
try: with self.assertRaises(MyException) as exc:
result = e.get() e.get()
log.append(('received', result)) log.append(('caught', exc.exception))
except Exception as ex:
log.append(('catched', ex))
gevent.spawn(waiter) gevent.spawn(waiter)
obj = Exception() obj = MyException()
e.set_exception(obj) e.set_exception(obj)
gevent.sleep(0) gevent.sleep(0)
assert log == [('catched', obj)], log self.assertEqual(log, [('caught', obj)])
def test_set(self): def test_set(self):
event1 = AsyncResult() event1 = AsyncResult()
event2 = AsyncResult() event2 = AsyncResult()
class MyException(Exception):
pass
timer_exc = MyException('interrupted')
g = gevent.spawn_later(DELAY / 2.0, event1.set, 'hello event1') g = gevent.spawn_later(DELAY / 2.0, event1.set, 'hello event1')
t = gevent.Timeout.start_new(0, ValueError('interrupted')) t = gevent.Timeout.start_new(0, timer_exc)
try: try:
try: with self.assertRaises(MyException) as exc:
result = event1.get() event1.get()
except ValueError: self.assertEqual(timer_exc, exc.exception)
X = object()
result = gevent.with_timeout(DELAY, event2.get, timeout_value=X) X = object()
assert result is X, 'Nobody sent anything to event2 yet it received %r' % (result, ) result = gevent.with_timeout(DELAY, event2.get, timeout_value=X)
self.assertIs(
result, X,
'Nobody sent anything to event2 yet it received %r' % (result, ))
finally: finally:
t.cancel() t.cancel()
g.kill() g.kill()
...@@ -131,9 +141,11 @@ class TestAsyncResultAsLinkTarget(greentest.TestCase): ...@@ -131,9 +141,11 @@ class TestAsyncResultAsLinkTarget(greentest.TestCase):
g.link(s1) g.link(s1)
g.link_value(s2) g.link_value(s2)
g.link_exception(s3) g.link_exception(s3)
assert s1.get() == 1 self.assertEqual(s1.get(), 1)
assert s2.get() == 1 self.assertEqual(s2.get(), 1)
assert gevent.with_timeout(DELAY, s3.get, timeout_value=X) is X X = object()
result = gevent.with_timeout(DELAY, s3.get, timeout_value=X)
self.assertIs(result, X)
def test_set_exception(self): def test_set_exception(self):
def func(): def func():
...@@ -144,7 +156,9 @@ class TestAsyncResultAsLinkTarget(greentest.TestCase): ...@@ -144,7 +156,9 @@ class TestAsyncResultAsLinkTarget(greentest.TestCase):
g.link_value(s2) g.link_value(s2)
g.link_exception(s3) g.link_exception(s3)
self.assertRaises(greentest.ExpectedException, s1.get) self.assertRaises(greentest.ExpectedException, s1.get)
assert gevent.with_timeout(DELAY, s2.get, timeout_value=X) is X X = object()
result = gevent.with_timeout(DELAY, s2.get, timeout_value=X)
self.assertIs(result, X)
self.assertRaises(greentest.ExpectedException, s3.get) self.assertRaises(greentest.ExpectedException, s3.get)
...@@ -216,7 +230,5 @@ class TestWait_count2(TestWait): ...@@ -216,7 +230,5 @@ class TestWait_count2(TestWait):
count = 2 count = 2
X = object()
if __name__ == '__main__': if __name__ == '__main__':
greentest.main() greentest.main()
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