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
support in certain tools (setuptools v24.2.1 or newer is required).
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
:pr:`999` by Jan van Valburg.
......@@ -218,8 +218,9 @@ libuv
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.
To mitigate this issue, ``loop.timer()`` detects attempts to use
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
change.
......
......@@ -466,3 +466,10 @@ class loop(AbstractLoop):
io_watcher._no_more_watchers = lambda: delitem(io_watchers, fd)
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):
# long time, which is not good. So default to not updating the
# 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.
# 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?
# To mitigate the above problems, our loop implementation turns
# 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
def _update_now(self):
self.loop.update()
_again = False
def _watcher_ffi_init(self, args):
......@@ -707,3 +707,21 @@ class idle(_base.IdleMixin, watcher):
# Because libuv doesn't support priorities, idle watchers are
# potentially quite a bit different than under libev
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):
def start(self):
"""Schedule the timeout."""
assert not self.pending, '%r is already started; to restart it, cancel it first' % self
if self.seconds is None: # "fake" timeout (never expires)
if self.pending:
raise AssertionError('%r is already started; to restart it, cancel it first' % self)
if self.seconds is None:
# "fake" timeout (never expires)
return
if self.exception is None or self.exception is False or isinstance(self.exception, string_types):
# timeout that raises self
self.timer.start(getcurrent().throw, self)
else: # regular timeout with user-provided exception
self.timer.start(getcurrent().throw, self.exception)
throws = self
else:
# regular timeout with user-provided exception
throws = self.exception
self.timer.start(getcurrent().throw, throws)
@classmethod
def start_new(cls, timeout=None, exception=None, ref=True):
......
......@@ -39,6 +39,7 @@ else:
DEBUG = False
RUN_LEAKCHECKS = os.getenv('GEVENTTEST_LEAKCHECK')
RUN_COVERAGE = os.getenv("COVERAGE_PROCESS_START")
# Generally, ignore the portions that are only implemented
# on particular platforms; they generally contain partial
......@@ -50,6 +51,7 @@ if WIN:
PY2 = None
PY3 = None
PY34 = None
PY35 = None
PY36 = None
PY37 = None
......@@ -61,6 +63,8 @@ if sys.version_info[0] == 3:
PY3 = True
if sys.version_info[1] >= 4:
PY34 = True
if sys.version_info[1] >= 5:
PY35 = True
if sys.version_info[1] >= 6:
PY36 = True
if sys.version_info[1] >= 7:
......@@ -78,6 +82,12 @@ elif sys.version_info[0] == 2:
PYPY3 = PYPY and PY3
PYGTE279 = (
sys.version_info[0] == 2
and sys.version_info[1] >= 7
and sys.version_info[2] >= 9
)
if WIN:
NON_APPLICABLE_SUFFIXES.append("posix")
# This is intimately tied to FileObjectPosix
......@@ -110,3 +120,5 @@ from errno import ECONNRESET
CONN_ABORTED_ERRORS.append(ECONNRESET)
CONN_ABORTED_ERRORS = frozenset(CONN_ABORTED_ERRORS)
RESOLVER_ARES = os.getenv('GEVENT_RESOLVER') == 'ares'
......@@ -6,22 +6,17 @@ import os
import sys
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')
TRAVIS = os.getenv('TRAVIS')
LEAKTEST = os.getenv('GEVENTTEST_LEAKCHECK')
COVERAGE = os.getenv("COVERAGE_PROCESS_START")
PYPY = hasattr(sys, 'pypy_version_info')
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
from greentest.sysinfo import PYPY
from greentest.sysinfo import PY3
from greentest.sysinfo import PY35
from greentest.sysinfo import LIBUV
FAILING_TESTS = [
......
from __future__ import absolute_import, print_function, division
import greentest
import gevent
from gevent.event import Event, AsyncResult
......@@ -49,7 +50,7 @@ class TestWaitEvent(greentest.GenericWaitTestCase):
# immediately add the waiter and call it
o = gevent.wait((event,), timeout=0.01)
self.assertFalse(event.ready())
self.assertFalse(event in o, o)
self.assertNotIn(event, o)
gevent.spawn(waiter).join()
......@@ -87,31 +88,40 @@ class TestAsyncResult(greentest.TestCase):
self.assertEqual(e.exc_info, ())
self.assertEqual(e.exception, None)
class MyException(Exception):
pass
def waiter():
try:
result = e.get()
log.append(('received', result))
except Exception as ex:
log.append(('catched', ex))
with self.assertRaises(MyException) as exc:
e.get()
log.append(('caught', exc.exception))
gevent.spawn(waiter)
obj = Exception()
obj = MyException()
e.set_exception(obj)
gevent.sleep(0)
assert log == [('catched', obj)], log
self.assertEqual(log, [('caught', obj)])
def test_set(self):
event1 = AsyncResult()
event2 = AsyncResult()
class MyException(Exception):
pass
timer_exc = MyException('interrupted')
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:
result = event1.get()
except ValueError:
X = object()
result = gevent.with_timeout(DELAY, event2.get, timeout_value=X)
assert result is X, 'Nobody sent anything to event2 yet it received %r' % (result, )
with self.assertRaises(MyException) as exc:
event1.get()
self.assertEqual(timer_exc, exc.exception)
X = object()
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:
t.cancel()
g.kill()
......@@ -131,9 +141,11 @@ class TestAsyncResultAsLinkTarget(greentest.TestCase):
g.link(s1)
g.link_value(s2)
g.link_exception(s3)
assert s1.get() == 1
assert s2.get() == 1
assert gevent.with_timeout(DELAY, s3.get, timeout_value=X) is X
self.assertEqual(s1.get(), 1)
self.assertEqual(s2.get(), 1)
X = object()
result = gevent.with_timeout(DELAY, s3.get, timeout_value=X)
self.assertIs(result, X)
def test_set_exception(self):
def func():
......@@ -144,7 +156,9 @@ class TestAsyncResultAsLinkTarget(greentest.TestCase):
g.link_value(s2)
g.link_exception(s3)
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)
......@@ -216,7 +230,5 @@ class TestWait_count2(TestWait):
count = 2
X = object()
if __name__ == '__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