Commit 384ccb69 authored by Jason Madden's avatar Jason Madden

Merge branch 'master' of https://github.com/gevent/gevent

parents 4c949eb7 bfdcadfd
...@@ -27,6 +27,10 @@ Unreleased ...@@ -27,6 +27,10 @@ Unreleased
- ``gevent.pywsgi`` no longer prints debugging information for the - ``gevent.pywsgi`` no longer prints debugging information for the
normal conditions of a premature client disconnect. Issue #136, normal conditions of a premature client disconnect. Issue #136,
fixed in PR #377 by Paul Collier. fixed in PR #377 by Paul Collier.
- (Experimental.) Waiting on or getting results from greenlets that
raised exceptions now usually raises the original traceback. This
should assist things like Sentry to track the original problem. PRs
#450 and #528 by Rodolfo and Eddi Linder.
Release 1.0.2 Release 1.0.2
------------- -------------
......
# Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details. # Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details.
import sys import sys
from gevent.hub import greenlet, getcurrent, get_hub, GreenletExit, Waiter, PY3, iwait, wait, PYPY from gevent.hub import GreenletExit
from gevent.hub import PY3
from gevent.hub import PYPY
from gevent.hub import Waiter
from gevent.hub import get_hub
from gevent.hub import getcurrent
from gevent.hub import greenlet
from gevent.hub import iwait
from gevent.hub import reraise
from gevent.hub import wait
from gevent.timeout import Timeout from gevent.timeout import Timeout
from collections import deque from collections import deque
...@@ -73,38 +82,65 @@ class FailureSpawnedLink(SpawnedLink): ...@@ -73,38 +82,65 @@ class FailureSpawnedLink(SpawnedLink):
return SpawnedLink.__call__(self, source) return SpawnedLink.__call__(self, source)
class _lazy(object):
def __init__(self, func):
self.data = (func, func.__name__)
def __get__(self, inst, class_):
if inst is None:
return self
func, name = self.data
value = func(inst)
inst.__dict__[name] = value
return value
class Greenlet(greenlet): class Greenlet(greenlet):
"""A light-weight cooperatively-scheduled execution unit.""" """A light-weight cooperatively-scheduled execution unit."""
value = None
_exc_info = ()
_notifier = None
_start_event = None
args = ()
def __init__(self, run=None, *args, **kwargs): def __init__(self, run=None, *args, **kwargs):
hub = get_hub() hub = get_hub()
greenlet.__init__(self, parent=hub) greenlet.__init__(self, parent=hub)
if run is not None: if run is not None:
self._run = run self._run = run
self.args = args if args:
self.args = args
self.kwargs = kwargs self.kwargs = kwargs
self._links = deque()
self.value = None @_lazy
self._exception = _NONE def _links(self):
self._notifier = None return deque()
self._start_event = None
def _raise_exception(self):
reraise(*self._exc_info)
def _exc_clear(self):
"""Throw away the traceback associated with the exception on this object.
Call this to resolve any reference cycles.
"""
if self._exc_info:
self._exc_info = (self._exc_info[0], self._exc_info[1], None)
@property @property
def loop(self): def loop(self):
# needed by killall # needed by killall
return self.parent.loop return self.parent.loop
if PY3: def __bool__(self):
def __bool__(self): return self._start_event is not None and self._exc_info is Greenlet._exc_info
return self._start_event is not None and self._exception is _NONE __nonzero__ = __bool__
else:
def __nonzero__(self):
return self._start_event is not None and self._exception is _NONE
if PYPY: if PYPY:
# oops - pypy's .dead relies on __nonzero__ which we overriden above # oops - pypy's .dead relies on __nonzero__ which we overriden above
@property @property
def dead(self): def dead(self):
return self._greenlet__started and not (self._greenlet__main or _continulet.is_pending(self)) return self._greenlet__started and not (self._greenlet__main or _continulet.is_pending(self))
...@@ -116,12 +152,12 @@ class Greenlet(greenlet): ...@@ -116,12 +152,12 @@ class Greenlet(greenlet):
def ready(self): def ready(self):
"""Return true if and only if the greenlet has finished execution.""" """Return true if and only if the greenlet has finished execution."""
return self.dead or self._exception is not _NONE return self.dead or self._exc_info
def successful(self): def successful(self):
"""Return true if and only if the greenlet has finished execution successfully, """Return true if and only if the greenlet has finished execution successfully,
that is, without raising an error.""" that is, without raising an error."""
return self._exception is None return self._exc_info and self._exc_info[1] is None
def __repr__(self): def __repr__(self):
classname = self.__class__.__name__ classname = self.__class__.__name__
...@@ -158,8 +194,7 @@ class Greenlet(greenlet): ...@@ -158,8 +194,7 @@ class Greenlet(greenlet):
"""Holds the exception instance raised by the function if the greenlet has finished with an error. """Holds the exception instance raised by the function if the greenlet has finished with an error.
Otherwise ``None``. Otherwise ``None``.
""" """
if self._exception is not _NONE: return self._exc_info[1] if self._exc_info else None
return self._exception
def throw(self, *args): def throw(self, *args):
"""Immediatelly switch into the greenlet and raise an exception in it. """Immediatelly switch into the greenlet and raise an exception in it.
...@@ -178,7 +213,7 @@ class Greenlet(greenlet): ...@@ -178,7 +213,7 @@ class Greenlet(greenlet):
try: try:
greenlet.throw(self, *args) greenlet.throw(self, *args)
finally: finally:
if self._exception is _NONE and self.dead: if self._exc_info is Greenlet._exc_info and self.dead:
# the greenlet was never switched to before and it will never be, _report_error was not called # the greenlet was never switched to before and it will never be, _report_error was not called
# the result was not set and the links weren't notified. let's do it here. # the result was not set and the links weren't notified. let's do it here.
# checking that self.dead is true is essential, because throw() does not necessarily kill the greenlet # checking that self.dead is true is essential, because throw() does not necessarily kill the greenlet
...@@ -260,33 +295,32 @@ class Greenlet(greenlet): ...@@ -260,33 +295,32 @@ class Greenlet(greenlet):
if self.ready(): if self.ready():
if self.successful(): if self.successful():
return self.value return self.value
else: self._raise_exception()
raise self._exception if not block:
if block: raise Timeout()
switch = getcurrent().switch
self.rawlink(switch) switch = getcurrent().switch
self.rawlink(switch)
try:
t = Timeout.start_new(timeout)
try: try:
t = Timeout.start_new(timeout) result = self.parent.switch()
try: assert result is self, 'Invalid switch into Greenlet.get(): %r' % (result, )
result = self.parent.switch() finally:
assert result is self, 'Invalid switch into Greenlet.get(): %r' % (result, ) t.cancel()
finally: except:
t.cancel() # unlinking in 'except' instead of finally is an optimization:
except: # if switch occurred normally then link was already removed in _notify_links
# unlinking in 'except' instead of finally is an optimization: # and there's no need to touch the links set.
# if switch occurred normally then link was already removed in _notify_links # Note, however, that if "Invalid switch" assert was removed and invalid switch
# and there's no need to touch the links set. # did happen, the link would remain, causing another invalid switch later in this greenlet.
# Note, however, that if "Invalid switch" assert was removed and invalid switch self.unlink(switch)
# did happen, the link would remain, causing another invalid switch later in this greenlet. raise
self.unlink(switch)
raise if self.ready():
if self.ready(): if self.successful():
if self.successful(): return self.value
return self.value self._raise_exception()
else:
raise self._exception
else:
raise Timeout
def join(self, timeout=None): def join(self, timeout=None):
"""Wait until the greenlet finishes or *timeout* expires. """Wait until the greenlet finishes or *timeout* expires.
...@@ -294,36 +328,36 @@ class Greenlet(greenlet): ...@@ -294,36 +328,36 @@ class Greenlet(greenlet):
""" """
if self.ready(): if self.ready():
return return
else:
switch = getcurrent().switch switch = getcurrent().switch
self.rawlink(switch) self.rawlink(switch)
try:
t = Timeout.start_new(timeout)
try: try:
t = Timeout.start_new(timeout) result = self.parent.switch()
try: assert result is self, 'Invalid switch into Greenlet.join(): %r' % (result, )
result = self.parent.switch() finally:
assert result is self, 'Invalid switch into Greenlet.join(): %r' % (result, ) t.cancel()
finally: except Timeout as ex:
t.cancel() self.unlink(switch)
except Timeout as ex: if ex is not t:
self.unlink(switch)
if ex is not t:
raise
except:
self.unlink(switch)
raise raise
except:
self.unlink(switch)
raise
def _report_result(self, result): def _report_result(self, result):
self._exception = None self._exc_info = (None, None, None)
self.value = result self.value = result
if self._links and not self._notifier: if self._links and not self._notifier:
self._notifier = self.parent.loop.run_callback(self._notify_links) self._notifier = self.parent.loop.run_callback(self._notify_links)
def _report_error(self, exc_info): def _report_error(self, exc_info):
exception = exc_info[1] if isinstance(exc_info[1], GreenletExit):
if isinstance(exception, GreenletExit): self._report_result(exc_info[1])
self._report_result(exception)
return return
self._exception = exception
self._exc_info = exc_info
if self._links and not self._notifier: if self._links and not self._notifier:
self._notifier = self.parent.loop.run_callback(self._notify_links) self._notifier = self.parent.loop.run_callback(self._notify_links)
...@@ -411,10 +445,21 @@ def _kill(greenlet, exception, waiter): ...@@ -411,10 +445,21 @@ def _kill(greenlet, exception, waiter):
def joinall(greenlets, timeout=None, raise_error=False, count=None): def joinall(greenlets, timeout=None, raise_error=False, count=None):
if not raise_error: if not raise_error:
wait(greenlets, timeout=timeout, count=count) wait(greenlets, timeout=timeout, count=count)
for g in greenlets:
if hasattr(g, '_exc_clear'):
g._exc_clear()
else: else:
for obj in iwait(greenlets, timeout=timeout, count=count): for obj in iwait(greenlets, timeout=timeout, count=count):
if getattr(obj, 'exception', None) is not None: if getattr(obj, 'exception', None) is not None:
raise obj.exception if hasattr(obj, '_raise_exception'):
try:
obj._raise_exception()
finally:
for g in greenlets:
if hasattr(g, '_exc_clear'):
g._exc_clear()
else:
raise obj.exception
def _killall3(greenlets, exception, waiter): def _killall3(greenlets, exception, waiter):
...@@ -473,6 +518,3 @@ def getfuncname(func): ...@@ -473,6 +518,3 @@ def getfuncname(func):
if funcname != '<lambda>': if funcname != '<lambda>':
return funcname return funcname
return repr(func) return repr(func)
_NONE = Exception("Neither exception nor value")
...@@ -241,6 +241,7 @@ class IMapUnordered(Greenlet): ...@@ -241,6 +241,7 @@ class IMapUnordered(Greenlet):
self.queue.put(greenlet.value) self.queue.put(greenlet.value)
else: else:
self.queue.put(Failure(greenlet.exception)) self.queue.put(Failure(greenlet.exception))
greenlet._exc_clear()
if self.ready() and self.count <= 0 and not self.finished: if self.ready() and self.count <= 0 and not self.finished:
self.queue.put(Failure(StopIteration)) self.queue.put(Failure(StopIteration))
self.finished = True self.finished = True
...@@ -250,6 +251,7 @@ class IMapUnordered(Greenlet): ...@@ -250,6 +251,7 @@ class IMapUnordered(Greenlet):
return return
if not self.successful(): if not self.successful():
self.queue.put(Failure(self.exception)) self.queue.put(Failure(self.exception))
self._exc_clear()
self.finished = True self.finished = True
return return
if self.count <= 0: if self.count <= 0:
...@@ -315,6 +317,7 @@ class IMap(Greenlet): ...@@ -315,6 +317,7 @@ class IMap(Greenlet):
self.queue.put((greenlet.index, greenlet.value)) self.queue.put((greenlet.index, greenlet.value))
else: else:
self.queue.put((greenlet.index, Failure(greenlet.exception))) self.queue.put((greenlet.index, Failure(greenlet.exception)))
greenlet._exc_clear()
if self.ready() and self.count <= 0 and not self.finished: if self.ready() and self.count <= 0 and not self.finished:
self.maxindex += 1 self.maxindex += 1
self.queue.put((self.maxindex, Failure(StopIteration))) self.queue.put((self.maxindex, Failure(StopIteration)))
...@@ -326,6 +329,7 @@ class IMap(Greenlet): ...@@ -326,6 +329,7 @@ class IMap(Greenlet):
if not self.successful(): if not self.successful():
self.maxindex += 1 self.maxindex += 1
self.queue.put((self.maxindex, Failure(self.exception))) self.queue.put((self.maxindex, Failure(self.exception)))
self._exc_clear()
self.finished = True self.finished = True
return return
if self.count <= 0: if self.count <= 0:
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
# package is named greentest, not test, so it won't be confused with test in stdlib # package is named greentest, not test, so it won't be confused with test in stdlib
import sys import sys
import types
import unittest import unittest
from unittest import TestCase as BaseTestCase from unittest import TestCase as BaseTestCase
import time import time
...@@ -88,8 +89,11 @@ def wrap_refcount(method): ...@@ -88,8 +89,11 @@ def wrap_refcount(method):
if not os.getenv('GEVENTTEST_LEAKCHECK'): if not os.getenv('GEVENTTEST_LEAKCHECK'):
return method return method
if getattr(method, 'ignore_leakcheck', False):
return method
# Some builtin things that we ignore # Some builtin things that we ignore
IGNORED_TYPES = (tuple, dict) IGNORED_TYPES = (tuple, dict, types.FrameType)
def type_hist(): def type_hist():
import collections import collections
...@@ -103,7 +107,7 @@ def wrap_refcount(method): ...@@ -103,7 +107,7 @@ def wrap_refcount(method):
def report_diff(a, b): def report_diff(a, b):
diff_lines = [] diff_lines = []
for k, v in sorted(a.items()): for k, v in sorted(a.items(), key=lambda i: i[0].__name__):
if b[k] != v: if b[k] != v:
diff_lines.append("%s: %s != %s" % (k, v, b[k])) diff_lines.append("%s: %s != %s" % (k, v, b[k]))
......
...@@ -96,6 +96,7 @@ class TestAsyncResultAsLinkTarget(greentest.TestCase): ...@@ -96,6 +96,7 @@ class TestAsyncResultAsLinkTarget(greentest.TestCase):
self.assertRaises(greentest.ExpectedException, s1.get) self.assertRaises(greentest.ExpectedException, s1.get)
assert gevent.with_timeout(DELAY, s2.get, timeout_value=X) is X assert gevent.with_timeout(DELAY, s2.get, timeout_value=X) is X
self.assertRaises(greentest.ExpectedException, s3.get) self.assertRaises(greentest.ExpectedException, s3.get)
g._exc_clear()
class TestEvent_SetThenClear(greentest.TestCase): class TestEvent_SetThenClear(greentest.TestCase):
......
...@@ -46,6 +46,7 @@ class Test(greentest.TestCase): ...@@ -46,6 +46,7 @@ class Test(greentest.TestCase):
except Exception: except Exception:
ex = sys.exc_info()[1] ex = sys.exc_info()[1]
assert ex is error, (ex, error) assert ex is error, (ex, error)
g._exc_clear()
def test2(self): def test2(self):
timer = gevent.get_hub().loop.timer(0) timer = gevent.get_hub().loop.timer(0)
......
...@@ -67,6 +67,7 @@ class TestLink(greentest.TestCase): ...@@ -67,6 +67,7 @@ class TestLink(greentest.TestCase):
event = AsyncResult() event = AsyncResult()
p.link(event) p.link(event)
self.assertRaises(err, event.get) self.assertRaises(err, event.get)
p._exc_clear()
for i in range(3): for i in range(3):
event2 = AsyncResult() event2 = AsyncResult()
...@@ -238,6 +239,7 @@ class TestRaise_link(LinksTestCase): ...@@ -238,6 +239,7 @@ class TestRaise_link(LinksTestCase):
assert not callback_flag, callback_flag assert not callback_flag, callback_flag
self.check_timed_out(*xxxxx) self.check_timed_out(*xxxxx)
p._exc_clear()
def test_raise(self): def test_raise(self):
p = self.p = gevent.spawn(lambda: getcurrent().throw(ExpectedError('test_raise'))) p = self.p = gevent.spawn(lambda: getcurrent().throw(ExpectedError('test_raise')))
...@@ -275,6 +277,8 @@ class TestStuff(greentest.TestCase): ...@@ -275,6 +277,8 @@ class TestStuff(greentest.TestCase):
self.assertRaises(ExpectedError, gevent.joinall, [x, y], raise_error=True) self.assertRaises(ExpectedError, gevent.joinall, [x, y], raise_error=True)
self.assertRaises(ExpectedError, gevent.joinall, [y], raise_error=True) self.assertRaises(ExpectedError, gevent.joinall, [y], raise_error=True)
x.join() x.join()
x._exc_clear()
y._exc_clear()
def test_joinall_exception_order(self): def test_joinall_exception_order(self):
# if there're several exceptions raised, the earliest one must be raised by joinall # if there're several exceptions raised, the earliest one must be raised by joinall
...@@ -342,6 +346,7 @@ class TestStuff(greentest.TestCase): ...@@ -342,6 +346,7 @@ class TestStuff(greentest.TestCase):
p.link(listener3) p.link(listener3)
sleep(DELAY * 10) sleep(DELAY * 10)
assert results in [[10, 20], [20, 10]], results assert results in [[10, 20], [20, 10]], results
p._exc_clear()
class Results(object): class Results(object):
...@@ -541,6 +546,7 @@ class TestBasic(greentest.TestCase): ...@@ -541,6 +546,7 @@ class TestBasic(greentest.TestCase):
assert g.value is None # not changed assert g.value is None # not changed
assert g.exception.myattr == 5 assert g.exception.myattr == 5
assert link_test == [g], link_test assert link_test == [g], link_test
g._exc_clear()
def _assertKilled(self, g): def _assertKilled(self, g):
assert not g assert not g
...@@ -550,6 +556,7 @@ class TestBasic(greentest.TestCase): ...@@ -550,6 +556,7 @@ class TestBasic(greentest.TestCase):
assert g.successful(), (repr(g), g.value, g.exception) assert g.successful(), (repr(g), g.value, g.exception)
assert isinstance(g.value, gevent.GreenletExit), (repr(g), g.value, g.exception) assert isinstance(g.value, gevent.GreenletExit), (repr(g), g.value, g.exception)
assert g.exception is None assert g.exception is None
g._exc_clear()
def assertKilled(self, g): def assertKilled(self, g):
self._assertKilled(g) self._assertKilled(g)
......
...@@ -181,6 +181,9 @@ class TestCase(greentest.TestCase): ...@@ -181,6 +181,9 @@ class TestCase(greentest.TestCase):
self.assert_error(TypeError) self.assert_error(TypeError)
finally: finally:
self.server.stop() self.server.stop()
# XXX: There's an unreachable greenlet that has a traceback.
# We need to clear it to make the leak checks work
import gc; gc.collect()
def ServerClass(self, *args, **kwargs): def ServerClass(self, *args, **kwargs):
kwargs.setdefault('spawn', self.get_spawn()) kwargs.setdefault('spawn', self.get_spawn())
...@@ -345,6 +348,11 @@ class TestPoolSpawn(TestDefaultSpawn): ...@@ -345,6 +348,11 @@ class TestPoolSpawn(TestDefaultSpawn):
test_pool_full.error_fatal = False test_pool_full.error_fatal = False
# XXX: Travis CI is reporting a leak test failure with this method
# but so far I can't reproduce it locally. Attempting a smash-and-grab
# fix
test_pool_full.ignore_leakcheck = True
class TestNoneSpawn(TestCase): class TestNoneSpawn(TestCase):
......
...@@ -36,6 +36,7 @@ class Test(greentest.TestCase): ...@@ -36,6 +36,7 @@ class Test(greentest.TestCase):
self.assertEqual(receiver.exception.errno, socket.EBADF) self.assertEqual(receiver.exception.errno, socket.EBADF)
finally: finally:
receiver.kill() receiver.kill()
receiver._exc_clear()
def test_recv_twice(self): def test_recv_twice(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
......
...@@ -64,6 +64,7 @@ class TestSpawn(Test): ...@@ -64,6 +64,7 @@ class TestSpawn(Test):
def tearDown(self): def tearDown(self):
gevent.sleep(0.0001) gevent.sleep(0.0001)
assert self.x.dead, self.x assert self.x.dead, self.x
self.x._exc_clear()
def start(self, *args): def start(self, *args):
self.x = gevent.spawn(*args) self.x = gevent.spawn(*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