Commit c9952ac5 authored by Jason Madden's avatar Jason Madden

Fix the leak checks by clearing the tracebacks at certain points in the tests.

parent e00862dd
...@@ -24,6 +24,10 @@ Unreleased ...@@ -24,6 +24,10 @@ Unreleased
- ``gevent.queue.JoinableQueue`` treats ``items`` passed to - ``gevent.queue.JoinableQueue`` treats ``items`` passed to
``__init__`` as unfinished tasks, the same as if they were ``put``. ``__init__`` as unfinished tasks, the same as if they were ``put``.
Initial PR #554 by DuLLSoN. Initial PR #554 by DuLLSoN.
- (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
------------- -------------
......
...@@ -122,6 +122,14 @@ class Greenlet(greenlet): ...@@ -122,6 +122,14 @@ class Greenlet(greenlet):
def _raise_exception(self): def _raise_exception(self):
reraise(*self._exc_info) 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
...@@ -437,11 +445,19 @@ def _kill(greenlet, exception, waiter): ...@@ -437,11 +445,19 @@ 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:
if hasattr(obj, '_raise_exception'): if hasattr(obj, '_raise_exception'):
obj._raise_exception() try:
obj._raise_exception()
finally:
for g in greenlets:
if hasattr(g, '_exc_clear'):
g._exc_clear()
else: else:
raise obj.exception raise obj.exception
......
...@@ -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
...@@ -89,7 +90,7 @@ def wrap_refcount(method): ...@@ -89,7 +90,7 @@ def wrap_refcount(method):
return method 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
......
...@@ -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())
......
...@@ -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