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
- ``gevent.pywsgi`` no longer prints debugging information for the
normal conditions of a premature client disconnect. Issue #136,
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
-------------
......
# Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details.
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 collections import deque
......@@ -73,38 +82,65 @@ class FailureSpawnedLink(SpawnedLink):
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):
"""A light-weight cooperatively-scheduled execution unit."""
value = None
_exc_info = ()
_notifier = None
_start_event = None
args = ()
def __init__(self, run=None, *args, **kwargs):
hub = get_hub()
greenlet.__init__(self, parent=hub)
if run is not None:
self._run = run
self.args = args
if args:
self.args = args
self.kwargs = kwargs
self._links = deque()
self.value = None
self._exception = _NONE
self._notifier = None
self._start_event = None
@_lazy
def _links(self):
return deque()
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
def loop(self):
# needed by killall
return self.parent.loop
if PY3:
def __bool__(self):
return self._start_event is not None and self._exception is _NONE
else:
def __nonzero__(self):
return self._start_event is not None and self._exception is _NONE
def __bool__(self):
return self._start_event is not None and self._exc_info is Greenlet._exc_info
__nonzero__ = __bool__
if PYPY:
# oops - pypy's .dead relies on __nonzero__ which we overriden above
@property
def dead(self):
return self._greenlet__started and not (self._greenlet__main or _continulet.is_pending(self))
......@@ -116,12 +152,12 @@ class Greenlet(greenlet):
def ready(self):
"""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):
"""Return true if and only if the greenlet has finished execution successfully,
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):
classname = self.__class__.__name__
......@@ -158,8 +194,7 @@ class Greenlet(greenlet):
"""Holds the exception instance raised by the function if the greenlet has finished with an error.
Otherwise ``None``.
"""
if self._exception is not _NONE:
return self._exception
return self._exc_info[1] if self._exc_info else None
def throw(self, *args):
"""Immediatelly switch into the greenlet and raise an exception in it.
......@@ -178,7 +213,7 @@ class Greenlet(greenlet):
try:
greenlet.throw(self, *args)
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 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
......@@ -260,33 +295,32 @@ class Greenlet(greenlet):
if self.ready():
if self.successful():
return self.value
else:
raise self._exception
if block:
switch = getcurrent().switch
self.rawlink(switch)
self._raise_exception()
if not block:
raise Timeout()
switch = getcurrent().switch
self.rawlink(switch)
try:
t = Timeout.start_new(timeout)
try:
t = Timeout.start_new(timeout)
try:
result = self.parent.switch()
assert result is self, 'Invalid switch into Greenlet.get(): %r' % (result, )
finally:
t.cancel()
except:
# unlinking in 'except' instead of finally is an optimization:
# if switch occurred normally then link was already removed in _notify_links
# and there's no need to touch the links set.
# Note, however, that if "Invalid switch" assert was removed and invalid switch
# did happen, the link would remain, causing another invalid switch later in this greenlet.
self.unlink(switch)
raise
if self.ready():
if self.successful():
return self.value
else:
raise self._exception
else:
raise Timeout
result = self.parent.switch()
assert result is self, 'Invalid switch into Greenlet.get(): %r' % (result, )
finally:
t.cancel()
except:
# unlinking in 'except' instead of finally is an optimization:
# if switch occurred normally then link was already removed in _notify_links
# and there's no need to touch the links set.
# Note, however, that if "Invalid switch" assert was removed and invalid switch
# did happen, the link would remain, causing another invalid switch later in this greenlet.
self.unlink(switch)
raise
if self.ready():
if self.successful():
return self.value
self._raise_exception()
def join(self, timeout=None):
"""Wait until the greenlet finishes or *timeout* expires.
......@@ -294,36 +328,36 @@ class Greenlet(greenlet):
"""
if self.ready():
return
else:
switch = getcurrent().switch
self.rawlink(switch)
switch = getcurrent().switch
self.rawlink(switch)
try:
t = Timeout.start_new(timeout)
try:
t = Timeout.start_new(timeout)
try:
result = self.parent.switch()
assert result is self, 'Invalid switch into Greenlet.join(): %r' % (result, )
finally:
t.cancel()
except Timeout as ex:
self.unlink(switch)
if ex is not t:
raise
except:
self.unlink(switch)
result = self.parent.switch()
assert result is self, 'Invalid switch into Greenlet.join(): %r' % (result, )
finally:
t.cancel()
except Timeout as ex:
self.unlink(switch)
if ex is not t:
raise
except:
self.unlink(switch)
raise
def _report_result(self, result):
self._exception = None
self._exc_info = (None, None, None)
self.value = result
if self._links and not self._notifier:
self._notifier = self.parent.loop.run_callback(self._notify_links)
def _report_error(self, exc_info):
exception = exc_info[1]
if isinstance(exception, GreenletExit):
self._report_result(exception)
if isinstance(exc_info[1], GreenletExit):
self._report_result(exc_info[1])
return
self._exception = exception
self._exc_info = exc_info
if self._links and not self._notifier:
self._notifier = self.parent.loop.run_callback(self._notify_links)
......@@ -411,10 +445,21 @@ def _kill(greenlet, exception, waiter):
def joinall(greenlets, timeout=None, raise_error=False, count=None):
if not raise_error:
wait(greenlets, timeout=timeout, count=count)
for g in greenlets:
if hasattr(g, '_exc_clear'):
g._exc_clear()
else:
for obj in iwait(greenlets, timeout=timeout, count=count):
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):
......@@ -473,6 +518,3 @@ def getfuncname(func):
if funcname != '<lambda>':
return funcname
return repr(func)
_NONE = Exception("Neither exception nor value")
......@@ -241,6 +241,7 @@ class IMapUnordered(Greenlet):
self.queue.put(greenlet.value)
else:
self.queue.put(Failure(greenlet.exception))
greenlet._exc_clear()
if self.ready() and self.count <= 0 and not self.finished:
self.queue.put(Failure(StopIteration))
self.finished = True
......@@ -250,6 +251,7 @@ class IMapUnordered(Greenlet):
return
if not self.successful():
self.queue.put(Failure(self.exception))
self._exc_clear()
self.finished = True
return
if self.count <= 0:
......@@ -315,6 +317,7 @@ class IMap(Greenlet):
self.queue.put((greenlet.index, greenlet.value))
else:
self.queue.put((greenlet.index, Failure(greenlet.exception)))
greenlet._exc_clear()
if self.ready() and self.count <= 0 and not self.finished:
self.maxindex += 1
self.queue.put((self.maxindex, Failure(StopIteration)))
......@@ -326,6 +329,7 @@ class IMap(Greenlet):
if not self.successful():
self.maxindex += 1
self.queue.put((self.maxindex, Failure(self.exception)))
self._exc_clear()
self.finished = True
return
if self.count <= 0:
......
......@@ -21,6 +21,7 @@
# package is named greentest, not test, so it won't be confused with test in stdlib
import sys
import types
import unittest
from unittest import TestCase as BaseTestCase
import time
......@@ -88,8 +89,11 @@ def wrap_refcount(method):
if not os.getenv('GEVENTTEST_LEAKCHECK'):
return method
if getattr(method, 'ignore_leakcheck', False):
return method
# Some builtin things that we ignore
IGNORED_TYPES = (tuple, dict)
IGNORED_TYPES = (tuple, dict, types.FrameType)
def type_hist():
import collections
......@@ -103,7 +107,7 @@ def wrap_refcount(method):
def report_diff(a, b):
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:
diff_lines.append("%s: %s != %s" % (k, v, b[k]))
......
......@@ -96,6 +96,7 @@ class TestAsyncResultAsLinkTarget(greentest.TestCase):
self.assertRaises(greentest.ExpectedException, s1.get)
assert gevent.with_timeout(DELAY, s2.get, timeout_value=X) is X
self.assertRaises(greentest.ExpectedException, s3.get)
g._exc_clear()
class TestEvent_SetThenClear(greentest.TestCase):
......
......@@ -46,6 +46,7 @@ class Test(greentest.TestCase):
except Exception:
ex = sys.exc_info()[1]
assert ex is error, (ex, error)
g._exc_clear()
def test2(self):
timer = gevent.get_hub().loop.timer(0)
......
......@@ -67,6 +67,7 @@ class TestLink(greentest.TestCase):
event = AsyncResult()
p.link(event)
self.assertRaises(err, event.get)
p._exc_clear()
for i in range(3):
event2 = AsyncResult()
......@@ -238,6 +239,7 @@ class TestRaise_link(LinksTestCase):
assert not callback_flag, callback_flag
self.check_timed_out(*xxxxx)
p._exc_clear()
def test_raise(self):
p = self.p = gevent.spawn(lambda: getcurrent().throw(ExpectedError('test_raise')))
......@@ -275,6 +277,8 @@ class TestStuff(greentest.TestCase):
self.assertRaises(ExpectedError, gevent.joinall, [x, y], raise_error=True)
self.assertRaises(ExpectedError, gevent.joinall, [y], raise_error=True)
x.join()
x._exc_clear()
y._exc_clear()
def test_joinall_exception_order(self):
# if there're several exceptions raised, the earliest one must be raised by joinall
......@@ -342,6 +346,7 @@ class TestStuff(greentest.TestCase):
p.link(listener3)
sleep(DELAY * 10)
assert results in [[10, 20], [20, 10]], results
p._exc_clear()
class Results(object):
......@@ -541,6 +546,7 @@ class TestBasic(greentest.TestCase):
assert g.value is None # not changed
assert g.exception.myattr == 5
assert link_test == [g], link_test
g._exc_clear()
def _assertKilled(self, g):
assert not g
......@@ -550,6 +556,7 @@ class TestBasic(greentest.TestCase):
assert g.successful(), (repr(g), g.value, g.exception)
assert isinstance(g.value, gevent.GreenletExit), (repr(g), g.value, g.exception)
assert g.exception is None
g._exc_clear()
def assertKilled(self, g):
self._assertKilled(g)
......
......@@ -181,6 +181,9 @@ class TestCase(greentest.TestCase):
self.assert_error(TypeError)
finally:
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):
kwargs.setdefault('spawn', self.get_spawn())
......@@ -345,6 +348,11 @@ class TestPoolSpawn(TestDefaultSpawn):
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):
......
......@@ -36,6 +36,7 @@ class Test(greentest.TestCase):
self.assertEqual(receiver.exception.errno, socket.EBADF)
finally:
receiver.kill()
receiver._exc_clear()
def test_recv_twice(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
......
......@@ -64,6 +64,7 @@ class TestSpawn(Test):
def tearDown(self):
gevent.sleep(0.0001)
assert self.x.dead, self.x
self.x._exc_clear()
def start(self, *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