Commit 25ff8d4a authored by Jason Madden's avatar Jason Madden

Introduce GEVENT_TRACK_GREENLET_TREE to disable greenlet tree features

As a performance optimization for applications where spawning
greenlets is critical. Plus some other optimizations to speed up
spawning in the general case.

CPython 3.6 with 1.2.2 vs these changes with tracking disabled:

| Benchmark              | 36_122_bench_spawn | 36config_bench_spawn_tree_off |
+------------------------+--------------------+-------------------------------+
| eventlet spawn         | 12.6 us            | 12.2 us: 1.04x faster (-4%)   |
| eventlet sleep         | 5.22 us            | 4.97 us: 1.05x faster (-5%)   |
| gevent spawn           | 4.27 us            | 5.06 us: 1.19x slower (+19%)  |
| gevent sleep           | 2.63 us            | 1.25 us: 2.11x faster (-53%)  |
| geventpool spawn       | 9.00 us            | 8.31 us: 1.08x faster (-8%)   |
| geventpool sleep       | 4.82 us            | 2.83 us: 1.70x faster (-41%)  |
| geventraw spawn        | 2.51 us            | 2.81 us: 1.12x slower (+12%)  |
| geventraw sleep        | 649 ns             | 679 ns: 1.05x slower (+5%)    |
| geventpool join        | 3.47 us            | 1.42 us: 2.44x faster (-59%)  |
| geventpool spawn kwarg | 11.0 us            | 8.95 us: 1.23x faster (-19%)  |
| geventraw spawn kwarg  | 3.87 us            | 4.20 us: 1.08x slower (+8%)   |

The differences compared to master are hard to quantify because the
standard deviation ends up being more than 10% of the mean in many
cases---and about a 10% improvement is what we typically see, so it
goes back and forth.
parent 373a5271
...@@ -14,6 +14,14 @@ ...@@ -14,6 +14,14 @@
monkey-patch the underlying class, ``threading._Event``. Some code monkey-patch the underlying class, ``threading._Event``. Some code
may be type-checking for that. See :issue:`1136`. may be type-checking for that. See :issue:`1136`.
- Introduce the configuration variable
`gevent.config.track_greenlet_tree` (aka
``GEVENT_TRACK_GREENLET_TREE``) to allow disabling the greenlet tree
features for applications where greenlet spawning is performance
critical. This restores spawning performance to 1.2 levels.
- Add additional optimizations for spawning greenlets, making it
faster than 1.3a2.
1.3a2 (2018-03-06) 1.3a2 (2018-03-06)
================== ==================
......
...@@ -109,6 +109,8 @@ def validate_bool(value): ...@@ -109,6 +109,8 @@ def validate_bool(value):
def validate_anything(value): def validate_anything(value):
return value return value
convert_str_value_as_is = validate_anything
class Setting(object): class Setting(object):
name = None name = None
value = None value = None
...@@ -396,6 +398,27 @@ class TraceMalloc(Setting): ...@@ -396,6 +398,27 @@ class TraceMalloc(Setting):
tracking information for FFI objects. tracking information for FFI objects.
""" """
class TrackGreenletTree(Setting):
name = 'track_greenlet_tree'
environment_key = 'GEVENT_TRACK_GREENLET_TREE'
default = True
validate = staticmethod(validate_bool)
# Don't do string-to-list conversion.
_convert = staticmethod(convert_str_value_as_is)
desc = """\
Should `Greenlet` objects track their spawning tree?
Setting this to a false value will make spawning `Greenlet`
objects and using `spawn_raw` faster, but the
``spawning_greenlet``, ``spawn_tree_locals`` and ``spawning_stack``
will not be captured.
.. versionadded:: 1.3b1
"""
# The ares settings are all interpreted by # The ares settings are all interpreted by
# gevent/resolver/ares.pyx, so we don't do # gevent/resolver/ares.pyx, so we don't do
# any validation here. # any validation here.
...@@ -410,7 +433,7 @@ class AresSettingMixin(object): ...@@ -410,7 +433,7 @@ class AresSettingMixin(object):
validate = staticmethod(validate_anything) validate = staticmethod(validate_anything)
_convert = staticmethod(validate_anything) _convert = staticmethod(convert_str_value_as_is)
class AresFlags(AresSettingMixin, Setting): class AresFlags(AresSettingMixin, Setting):
name = 'ares_flags' name = 'ares_flags'
......
...@@ -75,6 +75,9 @@ cdef inline list _extract_stack(int limit) ...@@ -75,6 +75,9 @@ cdef inline list _extract_stack(int limit)
@cython.locals(previous=_Frame, frame=tuple, f=_Frame) @cython.locals(previous=_Frame, frame=tuple, f=_Frame)
cdef _Frame _Frame_from_list(list frames) cdef _Frame _Frame_from_list(list frames)
@cython.final
cdef inline get_hub()
cdef class Greenlet(greenlet): cdef class Greenlet(greenlet):
cdef readonly object value cdef readonly object value
...@@ -128,7 +131,8 @@ cdef class Greenlet(greenlet): ...@@ -128,7 +131,8 @@ cdef class Greenlet(greenlet):
# doing a module global lookup. This is especially important # doing a module global lookup. This is especially important
# for spawning greenlets. # for spawning greenlets.
cdef _greenlet__init__ cdef _greenlet__init__
cdef get_hub cdef _threadlocal
cdef get_hub_class
cdef wref cdef wref
cdef Timeout cdef Timeout
...@@ -139,6 +143,7 @@ cdef wait ...@@ -139,6 +143,7 @@ cdef wait
cdef iwait cdef iwait
cdef reraise cdef reraise
cdef InvalidSwitchError cdef InvalidSwitchError
cpdef GEVENT_CONFIG
@cython.final @cython.final
......
...@@ -16,10 +16,13 @@ from gevent._tblib import load_traceback ...@@ -16,10 +16,13 @@ from gevent._tblib import load_traceback
from gevent.hub import GreenletExit from gevent.hub import GreenletExit
from gevent.hub import InvalidSwitchError from gevent.hub import InvalidSwitchError
from gevent.hub import Waiter from gevent.hub import Waiter
from gevent.hub import get_hub from gevent.hub import _threadlocal
from gevent.hub import get_hub_class
from gevent.hub import iwait from gevent.hub import iwait
from gevent.hub import wait from gevent.hub import wait
from gevent.timeout import Timeout from gevent.timeout import Timeout
from gevent._config import config as GEVENT_CONFIG
from gevent._util import Lazy from gevent._util import Lazy
from gevent._util import readproperty from gevent._util import readproperty
...@@ -39,6 +42,16 @@ __all__ = [ ...@@ -39,6 +42,16 @@ __all__ = [
locals()['getcurrent'] = __import__('greenlet').getcurrent locals()['getcurrent'] = __import__('greenlet').getcurrent
locals()['greenlet_init'] = lambda: None locals()['greenlet_init'] = lambda: None
def get_hub():
# This is identical to gevent.hub._get_hub_noargs so that it
# can be inlined for greenlet spawning by cython.
hub = _threadlocal.hub
if hub is None:
hubtype = get_hub_class()
hub = _threadlocal.hub = hubtype()
return hub
if _PYPY: if _PYPY:
import _continuation # pylint:disable=import-error import _continuation # pylint:disable=import-error
_continulet = _continuation.continulet _continulet = _continuation.continulet
...@@ -204,6 +217,12 @@ class Greenlet(greenlet): ...@@ -204,6 +217,12 @@ class Greenlet(greenlet):
spawning greenlets, specify a larger value for improved debugging. spawning greenlets, specify a larger value for improved debugging.
.. versionadded:: 1.3a2 .. versionadded:: 1.3a2
.. versionchanged:: 1.3b1
The ``GEVENT_TRACK_GREENLET_TREE`` configuration value may be set to
a false value to disable ``spawn_tree_locals``, ``spawning_greenlet``,
and ``spawning_stack``. The first two will be None in that case, and the
latter will be empty.
""" """
# greenlet.greenlet(run=None, parent=None) # greenlet.greenlet(run=None, parent=None)
# Calling it with both positional arguments instead of a keyword # Calling it with both positional arguments instead of a keyword
...@@ -233,7 +252,6 @@ class Greenlet(greenlet): ...@@ -233,7 +252,6 @@ class Greenlet(greenlet):
_greenlet__init__(self, None, get_hub()) _greenlet__init__(self, None, get_hub())
#super(Greenlet, self).__init__(None, get_hub())
if run is not None: if run is not None:
self._run = run self._run = run
...@@ -266,6 +284,7 @@ class Greenlet(greenlet): ...@@ -266,6 +284,7 @@ class Greenlet(greenlet):
# Failed with exception: (t, v, dump_traceback(tb))) # Failed with exception: (t, v, dump_traceback(tb)))
self._exc_info = None self._exc_info = None
if GEVENT_CONFIG.track_greenlet_tree:
spawner = getcurrent() # pylint:disable=undefined-variable spawner = getcurrent() # pylint:disable=undefined-variable
self.spawning_greenlet = wref(spawner) self.spawning_greenlet = wref(spawner)
try: try:
...@@ -279,6 +298,12 @@ class Greenlet(greenlet): ...@@ -279,6 +298,12 @@ class Greenlet(greenlet):
self._spawning_stack_frames = _extract_stack(self.spawning_stack_limit) self._spawning_stack_frames = _extract_stack(self.spawning_stack_limit)
self._spawning_stack_frames.extend(getattr(spawner, '_spawning_stack_frames', [])) self._spawning_stack_frames.extend(getattr(spawner, '_spawning_stack_frames', []))
else:
# None is the default for all of these in Cython, but we
# need to declare them for pure-Python mode.
self.spawning_greenlet = None
self.spawn_tree_locals = None
self._spawning_stack_frames = None
@Lazy @Lazy
def spawning_stack(self): def spawning_stack(self):
...@@ -286,7 +311,7 @@ class Greenlet(greenlet): ...@@ -286,7 +311,7 @@ class Greenlet(greenlet):
# code. It's tempting to discard _spawning_stack_frames # code. It's tempting to discard _spawning_stack_frames
# after this, but child greenlets may still be created # after this, but child greenlets may still be created
# that need it. # that need it.
return _Frame_from_list(self._spawning_stack_frames) return _Frame_from_list(self._spawning_stack_frames or [])
def _get_minimal_ident(self): def _get_minimal_ident(self):
reg = self.parent.ident_registry reg = self.parent.ident_registry
......
...@@ -27,7 +27,7 @@ __all__ = [ ...@@ -27,7 +27,7 @@ __all__ = [
'Waiter', 'Waiter',
] ]
from gevent._config import config from gevent._config import config as GEVENT_CONFIG
from gevent._compat import string_types from gevent._compat import string_types
from gevent._compat import xrange from gevent._compat import xrange
from gevent._util import _NONE from gevent._util import _NONE
...@@ -63,8 +63,6 @@ get_ident = thread.get_ident ...@@ -63,8 +63,6 @@ get_ident = thread.get_ident
MAIN_THREAD = get_ident() MAIN_THREAD = get_ident()
class LoopExit(Exception): class LoopExit(Exception):
""" """
Exception thrown when the hub finishes running. Exception thrown when the hub finishes running.
...@@ -83,7 +81,6 @@ class LoopExit(Exception): ...@@ -83,7 +81,6 @@ class LoopExit(Exception):
affinity from a different thread affinity from a different thread
""" """
pass
class BlockingSwitchOutError(AssertionError): class BlockingSwitchOutError(AssertionError):
...@@ -101,6 +98,24 @@ class ConcurrentObjectUseError(AssertionError): ...@@ -101,6 +98,24 @@ class ConcurrentObjectUseError(AssertionError):
# first one # first one
pass pass
class TrackedRawGreenlet(RawGreenlet):
def __init__(self, function, parent):
RawGreenlet.__init__(self, function, parent)
# See greenlet.py's Greenlet class. We capture the cheap
# parts to maintain the tree structure, but we do not capture
# the stack because that's too expensive for 'spawn_raw'.
current = getcurrent()
self.spawning_greenlet = wref(current)
# See Greenlet for how trees are maintained.
try:
self.spawn_tree_locals = current.spawn_tree_locals
except AttributeError:
self.spawn_tree_locals = {}
if current.parent:
current.spawn_tree_locals = self.spawn_tree_locals
def spawn_raw(function, *args, **kwargs): def spawn_raw(function, *args, **kwargs):
""" """
...@@ -129,36 +144,31 @@ def spawn_raw(function, *args, **kwargs): ...@@ -129,36 +144,31 @@ def spawn_raw(function, *args, **kwargs):
Populate the ``spawning_greenlet`` and ``spawn_tree_locals`` Populate the ``spawning_greenlet`` and ``spawn_tree_locals``
attributes of the returned greenlet. attributes of the returned greenlet.
.. versionchanged:: 1.3b1
*Only* populate ``spawning_greenlet`` and ``spawn_tree_locals``
if ``GEVENT_TRACK_GREENLET_TREE`` is enabled (the default). If not enabled,
those attributes will not be set.
""" """
if not callable(function): if not callable(function):
raise TypeError("function must be callable") raise TypeError("function must be callable")
# The hub is always the parent. # The hub is always the parent.
hub = get_hub() hub = _get_hub_noargs()
factory = TrackedRawGreenlet if GEVENT_CONFIG.track_greenlet_tree else RawGreenlet
# The callback class object that we use to run this doesn't # The callback class object that we use to run this doesn't
# accept kwargs (and those objects are heavily used, as well as being # accept kwargs (and those objects are heavily used, as well as being
# implemented twice in core.ppyx and corecffi.py) so do it with a partial # implemented twice in core.ppyx and corecffi.py) so do it with a partial
if kwargs: if kwargs:
function = _functools_partial(function, *args, **kwargs) function = _functools_partial(function, *args, **kwargs)
g = RawGreenlet(function, hub) g = factory(function, hub)
hub.loop.run_callback(g.switch) hub.loop.run_callback(g.switch)
else: else:
g = RawGreenlet(function, hub) g = factory(function, hub)
hub.loop.run_callback(g.switch, *args) hub.loop.run_callback(g.switch, *args)
# See greenlet.py's Greenlet class. We capture the cheap
# parts to maintain the tree structure, but we do not capture
# the stack because that's too expensive.
current = getcurrent()
g.spawning_greenlet = wref(current)
# See Greenlet for how trees are maintained.
try:
g.spawn_tree_locals = current.spawn_tree_locals
except AttributeError:
g.spawn_tree_locals = {}
if current.parent:
current.spawn_tree_locals = g.spawn_tree_locals
return g return g
...@@ -187,7 +197,7 @@ def sleep(seconds=0, ref=True): ...@@ -187,7 +197,7 @@ def sleep(seconds=0, ref=True):
.. seealso:: :func:`idle` .. seealso:: :func:`idle`
""" """
hub = get_hub() hub = _get_hub_noargs()
loop = hub.loop loop = hub.loop
if seconds <= 0: if seconds <= 0:
waiter = Waiter() waiter = Waiter()
...@@ -209,7 +219,7 @@ def idle(priority=0): ...@@ -209,7 +219,7 @@ def idle(priority=0):
.. seealso:: :func:`sleep` .. seealso:: :func:`sleep`
""" """
hub = get_hub() hub = _get_hub_noargs()
watcher = hub.loop.idle() watcher = hub.loop.idle()
if priority: if priority:
watcher.priority = priority watcher.priority = priority
...@@ -243,7 +253,7 @@ def kill(greenlet, exception=GreenletExit): ...@@ -243,7 +253,7 @@ def kill(greenlet, exception=GreenletExit):
# after it's been killed # after it's been killed
greenlet.kill(exception=exception, block=False) greenlet.kill(exception=exception, block=False)
else: else:
get_hub().loop.run_callback(greenlet.throw, exception) _get_hub_noargs().loop.run_callback(greenlet.throw, exception)
class signal(object): class signal(object):
...@@ -274,7 +284,7 @@ class signal(object): ...@@ -274,7 +284,7 @@ class signal(object):
if not callable(handler): if not callable(handler):
raise TypeError("signal handler must be callable.") raise TypeError("signal handler must be callable.")
self.hub = get_hub() self.hub = _get_hub_noargs()
self.watcher = self.hub.loop.signal(signalnum, ref=False) self.watcher = self.hub.loop.signal(signalnum, ref=False)
self.watcher.start(self._start) self.watcher.start(self._start)
self.handler = handler self.handler = handler
...@@ -393,6 +403,12 @@ def get_hub(*args, **kwargs): ...@@ -393,6 +403,12 @@ def get_hub(*args, **kwargs):
If a hub does not exist in the current thread, a new one is If a hub does not exist in the current thread, a new one is
created of the type returned by :func:`get_hub_class`. created of the type returned by :func:`get_hub_class`.
.. deprecated:: 1.3b1
The ``*args`` and ``**kwargs`` arguments are deprecated. They were
only used when the hub was created, and so were non-deterministic---to be
sure they were used, *all* callers had to pass them, or they were order-dependent.
Use ``set_hub`` instead.
""" """
hub = _threadlocal.hub hub = _threadlocal.hub
if hub is None: if hub is None:
...@@ -400,6 +416,15 @@ def get_hub(*args, **kwargs): ...@@ -400,6 +416,15 @@ def get_hub(*args, **kwargs):
hub = _threadlocal.hub = hubtype(*args, **kwargs) hub = _threadlocal.hub = hubtype(*args, **kwargs)
return hub return hub
def _get_hub_noargs():
# Just like get_hub, but cheaper to call because it
# takes no arguments or kwargs. See also a copy in
# gevent/greenlet.py
hub = _threadlocal.hub
if hub is None:
hubtype = get_hub_class()
hub = _threadlocal.hub = hubtype()
return hub
def _get_hub(): def _get_hub():
"""Return the hub for the current thread. """Return the hub for the current thread.
...@@ -428,7 +453,7 @@ def _config(default, envvar): ...@@ -428,7 +453,7 @@ def _config(default, envvar):
hub_ident_registry = IdentRegistry() hub_ident_registry = IdentRegistry()
class Hub(RawGreenlet): class Hub(TrackedRawGreenlet):
""" """
A greenlet that runs the event loop. A greenlet that runs the event loop.
...@@ -457,7 +482,7 @@ class Hub(RawGreenlet): ...@@ -457,7 +482,7 @@ class Hub(RawGreenlet):
def __init__(self, loop=None, default=None): def __init__(self, loop=None, default=None):
RawGreenlet.__init__(self) TrackedRawGreenlet.__init__(self, None, None)
if hasattr(loop, 'run'): if hasattr(loop, 'run'):
if default is not None: if default is not None:
raise TypeError("Unexpected argument: default") raise TypeError("Unexpected argument: default")
...@@ -476,7 +501,7 @@ class Hub(RawGreenlet): ...@@ -476,7 +501,7 @@ class Hub(RawGreenlet):
self.loop = self.loop_class(flags=loop, default=default) # pylint:disable=not-callable self.loop = self.loop_class(flags=loop, default=default) # pylint:disable=not-callable
self._resolver = None self._resolver = None
self._threadpool = None self._threadpool = None
self.format_context = config.format_context self.format_context = GEVENT_CONFIG.format_context
self.minimal_ident = hub_ident_registry.get_ident(self) self.minimal_ident = hub_ident_registry.get_ident(self)
@Lazy @Lazy
...@@ -485,11 +510,11 @@ class Hub(RawGreenlet): ...@@ -485,11 +510,11 @@ class Hub(RawGreenlet):
@property @property
def loop_class(self): def loop_class(self):
return config.loop return GEVENT_CONFIG.loop
@property @property
def backend(self): def backend(self):
return config.libev_backend return GEVENT_CONFIG.libev_backend
def __repr__(self): def __repr__(self):
if self.loop is None: if self.loop is None:
...@@ -741,7 +766,7 @@ class Hub(RawGreenlet): ...@@ -741,7 +766,7 @@ class Hub(RawGreenlet):
@property @property
def resolver_class(self): def resolver_class(self):
return config.resolver return GEVENT_CONFIG.resolver
def _get_resolver(self): def _get_resolver(self):
if self._resolver is None: if self._resolver is None:
...@@ -759,7 +784,7 @@ class Hub(RawGreenlet): ...@@ -759,7 +784,7 @@ class Hub(RawGreenlet):
@property @property
def threadpool_class(self): def threadpool_class(self):
return config.threadpool return GEVENT_CONFIG.threadpool
def _get_threadpool(self): def _get_threadpool(self):
if self._threadpool is None: if self._threadpool is None:
...@@ -820,7 +845,7 @@ class Waiter(object): ...@@ -820,7 +845,7 @@ class Waiter(object):
__slots__ = ['hub', 'greenlet', 'value', '_exception'] __slots__ = ['hub', 'greenlet', 'value', '_exception']
def __init__(self, hub=None): def __init__(self, hub=None):
self.hub = get_hub() if hub is None else hub self.hub = _get_hub_noargs() if hub is None else hub
self.greenlet = None self.greenlet = None
self.value = None self.value = None
self._exception = _NONE self._exception = _NONE
...@@ -963,7 +988,7 @@ def iwait(objects, timeout=None, count=None): ...@@ -963,7 +988,7 @@ def iwait(objects, timeout=None, count=None):
""" """
# QQQ would be nice to support iterable here that can be generated slowly (why?) # QQQ would be nice to support iterable here that can be generated slowly (why?)
if objects is None: if objects is None:
yield get_hub().join(timeout=timeout) yield _get_hub_noargs().join(timeout=timeout)
return return
count = len(objects) if count is None else min(count, len(objects)) count = len(objects) if count is None else min(count, len(objects))
...@@ -971,7 +996,7 @@ def iwait(objects, timeout=None, count=None): ...@@ -971,7 +996,7 @@ def iwait(objects, timeout=None, count=None):
switch = waiter.switch switch = waiter.switch
if timeout is not None: if timeout is not None:
timer = get_hub().loop.timer(timeout, priority=-1) timer = _get_hub_noargs().loop.timer(timeout, priority=-1)
timer.start(switch, _NONE) timer.start(switch, _NONE)
try: try:
...@@ -1031,7 +1056,7 @@ def wait(objects=None, timeout=None, count=None): ...@@ -1031,7 +1056,7 @@ def wait(objects=None, timeout=None, count=None):
.. seealso:: :func:`iwait` .. seealso:: :func:`iwait`
""" """
if objects is None: if objects is None:
return get_hub().join(timeout=timeout) return _get_hub_noargs().join(timeout=timeout)
return list(iwait(objects, timeout, count)) return list(iwait(objects, timeout, count))
......
...@@ -283,6 +283,10 @@ class GreenletTree(object): ...@@ -283,6 +283,10 @@ class GreenletTree(object):
tb = ''.join(traceback.format_stack(frame)) tb = ''.join(traceback.format_stack(frame))
tree.child_multidata(tb) tree.child_multidata(tb)
@staticmethod
def __spawning_parent(greenlet):
return (getattr(greenlet, 'spawning_greenlet', None) or _noop)()
def __render_locals(self, tree): def __render_locals(self, tree):
gr_locals = all_local_dicts_for_greenlet(self.greenlet) gr_locals = all_local_dicts_for_greenlet(self.greenlet)
if gr_locals: if gr_locals:
...@@ -316,7 +320,7 @@ class GreenletTree(object): ...@@ -316,7 +320,7 @@ class GreenletTree(object):
if spawning_stack and tree.details and tree.details['stacks']: if spawning_stack and tree.details and tree.details['stacks']:
self.__render_tb(tree, 'Spawned at:', spawning_stack) self.__render_tb(tree, 'Spawned at:', spawning_stack)
spawning_parent = getattr(self.greenlet, 'spawning_greenlet', _noop)() spawning_parent = self.__spawning_parent(self.greenlet)
tree_locals = getattr(self.greenlet, 'spawn_tree_locals', None) tree_locals = getattr(self.greenlet, 'spawn_tree_locals', None)
if tree_locals and tree_locals is not getattr(spawning_parent, 'spawn_tree_locals', None): if tree_locals and tree_locals is not getattr(spawning_parent, 'spawn_tree_locals', None):
tree.child_data('Spawn Tree Locals') tree.child_data('Spawn Tree Locals')
...@@ -377,7 +381,7 @@ class GreenletTree(object): ...@@ -377,7 +381,7 @@ class GreenletTree(object):
if not isinstance(ob, RawGreenlet): if not isinstance(ob, RawGreenlet):
continue continue
spawn_parent = getattr(ob, 'spawning_greenlet', _noop)() spawn_parent = cls.__spawning_parent(ob)
if spawn_parent is None: if spawn_parent is None:
root = cls._root_greenlet(ob) root = cls._root_greenlet(ob)
......
...@@ -59,15 +59,22 @@ class TestFormat(greentest.TestCase): ...@@ -59,15 +59,22 @@ class TestFormat(greentest.TestCase):
@greentest.skipOnPyPy("See TestFormat") @greentest.skipOnPyPy("See TestFormat")
class TestTree(greentest.TestCase): class TestTree(greentest.TestCase):
@greentest.ignores_leakcheck def setUp(self):
def test_tree(self): super(TestTree, self).setUp()
self.track_greenlet_tree = gevent.config.track_greenlet_tree
gevent.config.track_greenlet_tree = True
def tearDown(self):
gevent.config.track_greenlet_tree = self.track_greenlet_tree
super(TestTree, self).tearDown()
def _build_tree(self):
# pylint:disable=too-many-locals # pylint:disable=too-many-locals
# Python 2.7 on Travis seems to show unexpected greenlet objects # Python 2.7 on Travis seems to show unexpected greenlet objects
# so perhaps we need a GC? # so perhaps we need a GC?
gc.collect() for _ in range(3):
gc.collect() gc.collect()
import re
glets = [] glets = []
l = MyLocal(42) l = MyLocal(42)
assert l assert l
...@@ -96,6 +103,8 @@ class TestTree(greentest.TestCase): ...@@ -96,6 +103,8 @@ class TestTree(greentest.TestCase):
return s(t2) return s(t2)
s3 = s(t3) s3 = s(t3)
if s3.spawn_tree_locals is not None:
# Can only do this if we're tracking spawn trees
s3.spawn_tree_locals['stl'] = 'STL' s3.spawn_tree_locals['stl'] = 'STL'
s3.join() s3.join()
...@@ -104,10 +113,17 @@ class TestTree(greentest.TestCase): ...@@ -104,10 +113,17 @@ class TestTree(greentest.TestCase):
s4.join() s4.join()
tree = s4.value tree = s4.value
return tree, str(tree), tree.format(details={'stacks': False})
@greentest.ignores_leakcheck
def test_tree(self):
import re
tree, str_tree, tree_format = self._build_tree()
self.assertTrue(tree.root) self.assertTrue(tree.root)
self.assertNotIn('Parent', str(tree)) # Simple output self.assertNotIn('Parent', str_tree) # Simple output
value = tree.format(details={'stacks': False}) value = tree_format
hexobj = re.compile('0x[0123456789abcdef]+L?', re.I) hexobj = re.compile('0x[0123456789abcdef]+L?', re.I)
value = hexobj.sub('X', value) value = hexobj.sub('X', value)
value = value.replace('epoll', 'select') value = value.replace('epoll', 'select')
...@@ -145,5 +161,11 @@ class TestTree(greentest.TestCase): ...@@ -145,5 +161,11 @@ class TestTree(greentest.TestCase):
""".strip() """.strip()
self.assertEqual(value, expected) self.assertEqual(value, expected)
@greentest.ignores_leakcheck
def test_tree_no_track(self):
gevent.config.track_greenlet_tree = False
self._build_tree()
if __name__ == '__main__': if __name__ == '__main__':
greentest.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