Commit c7491589 authored by Jason Madden's avatar Jason Madden Committed by GitHub

Merge pull request #1142 from gevent/opt-greenlet

Introduce GEVENT_TRACK_GREENLET_TREE to disable greenlet tree features
parents 373a5271 25ff8d4a
......@@ -14,6 +14,14 @@
monkey-patch the underlying class, ``threading._Event``. Some code
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)
==================
......
......@@ -109,6 +109,8 @@ def validate_bool(value):
def validate_anything(value):
return value
convert_str_value_as_is = validate_anything
class Setting(object):
name = None
value = None
......@@ -396,6 +398,27 @@ class TraceMalloc(Setting):
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
# gevent/resolver/ares.pyx, so we don't do
# any validation here.
......@@ -410,7 +433,7 @@ class AresSettingMixin(object):
validate = staticmethod(validate_anything)
_convert = staticmethod(validate_anything)
_convert = staticmethod(convert_str_value_as_is)
class AresFlags(AresSettingMixin, Setting):
name = 'ares_flags'
......
......@@ -75,6 +75,9 @@ cdef inline list _extract_stack(int limit)
@cython.locals(previous=_Frame, frame=tuple, f=_Frame)
cdef _Frame _Frame_from_list(list frames)
@cython.final
cdef inline get_hub()
cdef class Greenlet(greenlet):
cdef readonly object value
......@@ -128,7 +131,8 @@ cdef class Greenlet(greenlet):
# doing a module global lookup. This is especially important
# for spawning greenlets.
cdef _greenlet__init__
cdef get_hub
cdef _threadlocal
cdef get_hub_class
cdef wref
cdef Timeout
......@@ -139,6 +143,7 @@ cdef wait
cdef iwait
cdef reraise
cdef InvalidSwitchError
cpdef GEVENT_CONFIG
@cython.final
......
......@@ -16,10 +16,13 @@ from gevent._tblib import load_traceback
from gevent.hub import GreenletExit
from gevent.hub import InvalidSwitchError
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 wait
from gevent.timeout import Timeout
from gevent._config import config as GEVENT_CONFIG
from gevent._util import Lazy
from gevent._util import readproperty
......@@ -39,6 +42,16 @@ __all__ = [
locals()['getcurrent'] = __import__('greenlet').getcurrent
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:
import _continuation # pylint:disable=import-error
_continulet = _continuation.continulet
......@@ -204,6 +217,12 @@ class Greenlet(greenlet):
spawning greenlets, specify a larger value for improved debugging.
.. 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)
# Calling it with both positional arguments instead of a keyword
......@@ -233,7 +252,6 @@ class Greenlet(greenlet):
_greenlet__init__(self, None, get_hub())
#super(Greenlet, self).__init__(None, get_hub())
if run is not None:
self._run = run
......@@ -266,19 +284,26 @@ class Greenlet(greenlet):
# Failed with exception: (t, v, dump_traceback(tb)))
self._exc_info = None
spawner = getcurrent() # pylint:disable=undefined-variable
self.spawning_greenlet = wref(spawner)
try:
self.spawn_tree_locals = spawner.spawn_tree_locals
except AttributeError:
self.spawn_tree_locals = {}
if spawner.parent is not None:
# The main greenlet has no parent.
# Its children get separate locals.
spawner.spawn_tree_locals = self.spawn_tree_locals
self._spawning_stack_frames = _extract_stack(self.spawning_stack_limit)
self._spawning_stack_frames.extend(getattr(spawner, '_spawning_stack_frames', []))
if GEVENT_CONFIG.track_greenlet_tree:
spawner = getcurrent() # pylint:disable=undefined-variable
self.spawning_greenlet = wref(spawner)
try:
self.spawn_tree_locals = spawner.spawn_tree_locals
except AttributeError:
self.spawn_tree_locals = {}
if spawner.parent is not None:
# The main greenlet has no parent.
# Its children get separate locals.
spawner.spawn_tree_locals = self.spawn_tree_locals
self._spawning_stack_frames = _extract_stack(self.spawning_stack_limit)
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
def spawning_stack(self):
......@@ -286,7 +311,7 @@ class Greenlet(greenlet):
# code. It's tempting to discard _spawning_stack_frames
# after this, but child greenlets may still be created
# 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):
reg = self.parent.ident_registry
......
......@@ -27,7 +27,7 @@ __all__ = [
'Waiter',
]
from gevent._config import config
from gevent._config import config as GEVENT_CONFIG
from gevent._compat import string_types
from gevent._compat import xrange
from gevent._util import _NONE
......@@ -63,8 +63,6 @@ get_ident = thread.get_ident
MAIN_THREAD = get_ident()
class LoopExit(Exception):
"""
Exception thrown when the hub finishes running.
......@@ -83,7 +81,6 @@ class LoopExit(Exception):
affinity from a different thread
"""
pass
class BlockingSwitchOutError(AssertionError):
......@@ -101,6 +98,24 @@ class ConcurrentObjectUseError(AssertionError):
# first one
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):
"""
......@@ -129,36 +144,31 @@ def spawn_raw(function, *args, **kwargs):
Populate the ``spawning_greenlet`` and ``spawn_tree_locals``
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):
raise TypeError("function must be callable")
# 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
# 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
if kwargs:
function = _functools_partial(function, *args, **kwargs)
g = RawGreenlet(function, hub)
g = factory(function, hub)
hub.loop.run_callback(g.switch)
else:
g = RawGreenlet(function, hub)
g = factory(function, hub)
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
......@@ -187,7 +197,7 @@ def sleep(seconds=0, ref=True):
.. seealso:: :func:`idle`
"""
hub = get_hub()
hub = _get_hub_noargs()
loop = hub.loop
if seconds <= 0:
waiter = Waiter()
......@@ -209,7 +219,7 @@ def idle(priority=0):
.. seealso:: :func:`sleep`
"""
hub = get_hub()
hub = _get_hub_noargs()
watcher = hub.loop.idle()
if priority:
watcher.priority = priority
......@@ -243,7 +253,7 @@ def kill(greenlet, exception=GreenletExit):
# after it's been killed
greenlet.kill(exception=exception, block=False)
else:
get_hub().loop.run_callback(greenlet.throw, exception)
_get_hub_noargs().loop.run_callback(greenlet.throw, exception)
class signal(object):
......@@ -274,7 +284,7 @@ class signal(object):
if not callable(handler):
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.start(self._start)
self.handler = handler
......@@ -393,6 +403,12 @@ def get_hub(*args, **kwargs):
If a hub does not exist in the current thread, a new one is
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
if hub is None:
......@@ -400,6 +416,15 @@ def get_hub(*args, **kwargs):
hub = _threadlocal.hub = hubtype(*args, **kwargs)
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():
"""Return the hub for the current thread.
......@@ -428,7 +453,7 @@ def _config(default, envvar):
hub_ident_registry = IdentRegistry()
class Hub(RawGreenlet):
class Hub(TrackedRawGreenlet):
"""
A greenlet that runs the event loop.
......@@ -457,7 +482,7 @@ class Hub(RawGreenlet):
def __init__(self, loop=None, default=None):
RawGreenlet.__init__(self)
TrackedRawGreenlet.__init__(self, None, None)
if hasattr(loop, 'run'):
if default is not None:
raise TypeError("Unexpected argument: default")
......@@ -476,7 +501,7 @@ class Hub(RawGreenlet):
self.loop = self.loop_class(flags=loop, default=default) # pylint:disable=not-callable
self._resolver = 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)
@Lazy
......@@ -485,11 +510,11 @@ class Hub(RawGreenlet):
@property
def loop_class(self):
return config.loop
return GEVENT_CONFIG.loop
@property
def backend(self):
return config.libev_backend
return GEVENT_CONFIG.libev_backend
def __repr__(self):
if self.loop is None:
......@@ -741,7 +766,7 @@ class Hub(RawGreenlet):
@property
def resolver_class(self):
return config.resolver
return GEVENT_CONFIG.resolver
def _get_resolver(self):
if self._resolver is None:
......@@ -759,7 +784,7 @@ class Hub(RawGreenlet):
@property
def threadpool_class(self):
return config.threadpool
return GEVENT_CONFIG.threadpool
def _get_threadpool(self):
if self._threadpool is None:
......@@ -820,7 +845,7 @@ class Waiter(object):
__slots__ = ['hub', 'greenlet', 'value', '_exception']
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.value = None
self._exception = _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?)
if objects is None:
yield get_hub().join(timeout=timeout)
yield _get_hub_noargs().join(timeout=timeout)
return
count = len(objects) if count is None else min(count, len(objects))
......@@ -971,7 +996,7 @@ def iwait(objects, timeout=None, count=None):
switch = waiter.switch
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)
try:
......@@ -1031,7 +1056,7 @@ def wait(objects=None, timeout=None, count=None):
.. seealso:: :func:`iwait`
"""
if objects is None:
return get_hub().join(timeout=timeout)
return _get_hub_noargs().join(timeout=timeout)
return list(iwait(objects, timeout, count))
......
......@@ -283,6 +283,10 @@ class GreenletTree(object):
tb = ''.join(traceback.format_stack(frame))
tree.child_multidata(tb)
@staticmethod
def __spawning_parent(greenlet):
return (getattr(greenlet, 'spawning_greenlet', None) or _noop)()
def __render_locals(self, tree):
gr_locals = all_local_dicts_for_greenlet(self.greenlet)
if gr_locals:
......@@ -316,7 +320,7 @@ class GreenletTree(object):
if spawning_stack and tree.details and tree.details['stacks']:
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)
if tree_locals and tree_locals is not getattr(spawning_parent, 'spawn_tree_locals', None):
tree.child_data('Spawn Tree Locals')
......@@ -377,7 +381,7 @@ class GreenletTree(object):
if not isinstance(ob, RawGreenlet):
continue
spawn_parent = getattr(ob, 'spawning_greenlet', _noop)()
spawn_parent = cls.__spawning_parent(ob)
if spawn_parent is None:
root = cls._root_greenlet(ob)
......
......@@ -59,15 +59,22 @@ class TestFormat(greentest.TestCase):
@greentest.skipOnPyPy("See TestFormat")
class TestTree(greentest.TestCase):
@greentest.ignores_leakcheck
def test_tree(self):
def setUp(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
# Python 2.7 on Travis seems to show unexpected greenlet objects
# so perhaps we need a GC?
gc.collect()
gc.collect()
for _ in range(3):
gc.collect()
import re
glets = []
l = MyLocal(42)
assert l
......@@ -96,7 +103,9 @@ class TestTree(greentest.TestCase):
return s(t2)
s3 = s(t3)
s3.spawn_tree_locals['stl'] = 'STL'
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.join()
......@@ -104,10 +113,17 @@ class TestTree(greentest.TestCase):
s4.join()
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.assertNotIn('Parent', str(tree)) # Simple output
value = tree.format(details={'stacks': False})
self.assertNotIn('Parent', str_tree) # Simple output
value = tree_format
hexobj = re.compile('0x[0123456789abcdef]+L?', re.I)
value = hexobj.sub('X', value)
value = value.replace('epoll', 'select')
......@@ -145,5 +161,11 @@ class TestTree(greentest.TestCase):
""".strip()
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__':
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