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
- ``gevent.queue.JoinableQueue`` treats ``items`` passed to
``__init__`` as unfinished tasks, the same as if they were ``put``.
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
-------------
......
......@@ -122,6 +122,14 @@ class Greenlet(greenlet):
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
......@@ -437,11 +445,19 @@ 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:
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:
raise obj.exception
......
......@@ -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
......@@ -89,7 +90,7 @@ def wrap_refcount(method):
return method
# Some builtin things that we ignore
IGNORED_TYPES = (tuple, dict)
IGNORED_TYPES = (tuple, dict, types.FrameType)
def type_hist():
import collections
......
......@@ -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())
......
......@@ -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