Commit 9c8e01d8 authored by Jason Madden's avatar Jason Madden

Compile greenlet with Cython to make up for most of the lost speed.

We're now only ~2x slower, instead of 10x.
parent 15fc1ecc
...@@ -9,6 +9,7 @@ gevent.*.[ch] ...@@ -9,6 +9,7 @@ gevent.*.[ch]
src/gevent/__pycache__ src/gevent/__pycache__
src/gevent/_semaphore.c src/gevent/_semaphore.c
src/gevent/local.c src/gevent/local.c
src/gevent/greenlet.c
src/gevent/libev/corecext.c src/gevent/libev/corecext.c
src/gevent/libev/corecext.h src/gevent/libev/corecext.h
src/gevent/libev/_corecffi.c src/gevent/libev/_corecffi.c
......
...@@ -90,6 +90,9 @@ ...@@ -90,6 +90,9 @@
a "spawn tree local" mapping. Based on a proposal from PayPal and a "spawn tree local" mapping. Based on a proposal from PayPal and
comments by Mahmoud Hashemi and Kurt Rose. See :issue:`755`. comments by Mahmoud Hashemi and Kurt Rose. See :issue:`755`.
- The :mod:`gevent.greenlet` module is now compiled with Cython to
offset any performance loss due to :issue:`755`.
1.3a1 (2018-01-27) 1.3a1 (2018-01-27)
================== ==================
......
...@@ -223,7 +223,6 @@ latex_documents = [ ...@@ -223,7 +223,6 @@ latex_documents = [
# prevent some stuff from showing up in docs # prevent some stuff from showing up in docs
import socket import socket
import gevent.socket import gevent.socket
del gevent.Greenlet.throw
for item in gevent.socket.__all__[:]: for item in gevent.socket.__all__[:]:
if getattr(gevent.socket, item) is getattr(socket, item, None): if getattr(gevent.socket, item) is getattr(socket, item, None):
gevent.socket.__all__.remove(item) gevent.socket.__all__.remove(item)
...@@ -36,25 +36,7 @@ generated. ...@@ -36,25 +36,7 @@ generated.
.. automethod:: Greenlet.__init__ .. automethod:: Greenlet.__init__
.. attribute:: Greenlet.value
Holds the value returned by the function if the greenlet has
finished successfully. Until then, or if it finished in error, ``None``.
.. tip:: Recall that a greenlet killed with the default
:class:`GreenletExit` is considered to have finished
successfully, and the ``GreenletExit`` exception will be
its value.
.. autoattribute:: Greenlet.exception .. autoattribute:: Greenlet.exception
.. autoattribute:: Greenlet.spawn_tree_locals
:annotation: = {}
.. autoattribute:: Greenlet.spawning_greenlet
:annotation: = weakref.ref()
.. autoattribute:: Greenlet.spawning_stack
:annotation: = <Frame>
.. autoattribute:: Greenlet.spawning_stack_limit
.. automethod:: Greenlet.ready .. automethod:: Greenlet.ready
.. automethod:: Greenlet.successful .. automethod:: Greenlet.successful
.. automethod:: Greenlet.start .. automethod:: Greenlet.start
......
...@@ -4,6 +4,7 @@ from __future__ import print_function ...@@ -4,6 +4,7 @@ from __future__ import print_function
import sys import sys
import os import os
import os.path import os.path
import sysconfig
# setuptools is *required* on Windows # setuptools is *required* on Windows
# (https://bugs.python.org/issue23246) and for PyPy. No reason not to # (https://bugs.python.org/issue23246) and for PyPy. No reason not to
...@@ -59,11 +60,28 @@ LOCAL = Extension(name="gevent.local", ...@@ -59,11 +60,28 @@ LOCAL = Extension(name="gevent.local",
depends=['src/gevent/local.pxd']) depends=['src/gevent/local.pxd'])
LOCAL = cythonize1(LOCAL) LOCAL = cythonize1(LOCAL)
# The sysconfig dir is not enough if we're in a virtualenv
# See https://github.com/pypa/pip/issues/4610
include_dirs = [sysconfig.get_path("include")]
venv_include_dir = os.path.join(sys.prefix, 'include', 'site',
'python' + sysconfig.get_python_version())
venv_include_dir = os.path.abspath(venv_include_dir)
if os.path.exists(venv_include_dir):
include_dirs.append(venv_include_dir)
GREENLET = Extension(name="gevent.greenlet",
sources=["src/gevent/greenlet.py"],
depends=['src/gevent/greenlet.pxd'],
include_dirs=include_dirs)
GREENLET = cythonize1(GREENLET)
EXT_MODULES = [ EXT_MODULES = [
CORE, CORE,
ARES, ARES,
SEMAPHORE, SEMAPHORE,
LOCAL, LOCAL,
GREENLET,
] ]
LIBEV_CFFI_MODULE = 'src/gevent/libev/_corecffi_build.py:ffi' LIBEV_CFFI_MODULE = 'src/gevent/libev/_corecffi_build.py:ffi'
...@@ -91,6 +109,7 @@ if PYPY: ...@@ -91,6 +109,7 @@ if PYPY:
setup_requires = [] setup_requires = []
EXT_MODULES.remove(CORE) EXT_MODULES.remove(CORE)
EXT_MODULES.remove(LOCAL) EXT_MODULES.remove(LOCAL)
EXT_MODULES.remove(GREENLET)
EXT_MODULES.remove(SEMAPHORE) EXT_MODULES.remove(SEMAPHORE)
# By building the semaphore with Cython under PyPy, we get # By building the semaphore with Cython under PyPy, we get
# atomic operations (specifically, exiting/releasing), at the # atomic operations (specifically, exiting/releasing), at the
......
# cython: auto_pickle=False
cimport cython
cdef extern from "greenlet/greenlet.h":
ctypedef class greenlet.greenlet [object PyGreenlet]:
pass
cdef class SpawnedLink:
cdef public object callback
@cython.final
cdef class SuccessSpawnedLink(SpawnedLink):
pass
@cython.final
cdef class FailureSpawnedLink(SpawnedLink):
pass
@cython.final
@cython.internal
cdef class _Frame:
cdef readonly object f_code
cdef readonly int f_lineno
cdef public _Frame f_back
cdef class Greenlet(greenlet):
cdef readonly object value
cdef readonly args
cdef readonly object spawning_greenlet
cdef public dict spawn_tree_locals
cdef readonly _Frame spawning_stack
# A test case reads these, otherwise they would
# be private
cdef readonly tuple _exc_info
cdef readonly list _links
cdef object _notifier
cdef object _start_event
cdef dict _kwargs
cdef bint __started_but_aborted(self)
cdef bint __start_cancelled_by_kill(self)
cdef bint __start_pending(self)
cdef bint __never_started_or_killed(self)
cdef bint __start_completed(self)
cdef __cancel_start(self)
cdef _report_result(self, object result)
cdef _report_error(self, tuple exc_info)
@cython.final
@cython.internal
cdef class _dummy_event:
cdef readonly bint pending
cdef readonly bint active
cpdef stop(self)
cpdef start(self, cb)
cpdef close(self)
cdef _dummy_event _cancelled_start_event
cdef _dummy_event _start_completed_event
@cython.locals(diehards=list)
cdef _killall3(list greenlets, object exception, object waiter)
cdef _killall(list greenlets, object exception)
@cython.locals(done=list)
cpdef joinall(greenlets, timeout=*, raise_error=*, count=*)
# Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details. # Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details.
# cython: auto_pickle=False
from __future__ import absolute_import from __future__ import absolute_import
from collections import deque
import sys import sys
from weakref import ref as wref from weakref import ref as wref
...@@ -12,7 +12,6 @@ from greenlet import getcurrent ...@@ -12,7 +12,6 @@ from greenlet import getcurrent
from gevent._compat import PY3 from gevent._compat import PY3
from gevent._compat import PYPY from gevent._compat import PYPY
from gevent._compat import reraise from gevent._compat import reraise
from gevent._util import Lazy
from gevent._tblib import dump_traceback from gevent._tblib import dump_traceback
from gevent._tblib import load_traceback from gevent._tblib import load_traceback
from gevent.hub import GreenletExit from gevent.hub import GreenletExit
...@@ -23,8 +22,6 @@ from gevent.hub import iwait ...@@ -23,8 +22,6 @@ 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
__all__ = [ __all__ = [
'Greenlet', 'Greenlet',
'joinall', 'joinall',
...@@ -38,7 +35,8 @@ if PYPY: ...@@ -38,7 +35,8 @@ if PYPY:
class SpawnedLink(object): class SpawnedLink(object):
"""A wrapper around link that calls it in another greenlet. """
A wrapper around link that calls it in another greenlet.
Can be called only from main loop. Can be called only from main loop.
""" """
...@@ -93,7 +91,6 @@ class FailureSpawnedLink(SpawnedLink): ...@@ -93,7 +91,6 @@ class FailureSpawnedLink(SpawnedLink):
if not source.successful(): if not source.successful():
return SpawnedLink.__call__(self, source) return SpawnedLink.__call__(self, source)
class _Frame(object): class _Frame(object):
__slots__ = ('f_code', 'f_lineno', 'f_back') __slots__ = ('f_code', 'f_lineno', 'f_back')
...@@ -111,55 +108,11 @@ class Greenlet(greenlet): ...@@ -111,55 +108,11 @@ class Greenlet(greenlet):
""" """
# pylint:disable=too-many-public-methods,too-many-instance-attributes # pylint:disable=too-many-public-methods,too-many-instance-attributes
#: A dictionary that is shared between all the greenlets
#: in a "spawn tree", that is, a spawning greenlet and all
#: its descendent greenlets. All children of the main (root)
#: greenlet start their own spawn trees. Assign a new dictionary
#: to this attribute on an instance of this class to create a new
#: spawn tree (as far as locals are concerned).
#:
#: .. versionadded:: 1.3a2
spawn_tree_locals = None
#: A weak-reference to the greenlet that was current when this object
#: was created. Note that the :attr:`parent` attribute is always the
#: hub.
#:
#: .. versionadded:: 1.3a2
spawning_greenlet = None
#: A lightweight frame-like object capturing the stack when
#: this greenlet was created as well as the stack when the spawning
#: greenlet was created (if applicable). This can be passed to
#: :func:`traceback.print_stack`.
#:
#: .. versionadded:: 1.3a2
spawning_stack = None
#: A class attribute specifying how many levels of the spawning
#: stack will be kept. Specify a smaller number for higher performance,
#: specify a larger value for improved debugging.
#:
#: .. versionadded:: 1.3a2
spawning_stack_limit = 10 spawning_stack_limit = 10
value = None
_exc_info = ()
_notifier = None
#: An event, such as a timer or a callback that fires. It is established in
#: start() and start_later() as those two objects, respectively.
#: Once this becomes non-None, the Greenlet cannot be started again. Conversely,
#: kill() and throw() check for non-None to determine if this object has ever been
#: scheduled for starting. A placeholder _dummy_event is assigned by them to prevent
#: the greenlet from being started in the future, if necessary.
_start_event = None
args = ()
_kwargs = None
def __init__(self, run=None, *args, **kwargs): # pylint:disable=keyword-arg-before-vararg def __init__(self, run=None, *args, **kwargs): # pylint:disable=keyword-arg-before-vararg
""" """
Greenlet constructor. Greenlet(run=None, *args, **kwargs) -> Greenlet
:param args: The arguments passed to the ``run`` function. :param args: The arguments passed to the ``run`` function.
:param kwargs: The keyword arguments passed to the ``run`` function. :param kwargs: The keyword arguments passed to the ``run`` function.
...@@ -170,6 +123,53 @@ class Greenlet(greenlet): ...@@ -170,6 +123,53 @@ class Greenlet(greenlet):
The ``run`` argument to the constructor is now verified to be a callable The ``run`` argument to the constructor is now verified to be a callable
object. Previously, passing a non-callable object would fail after the greenlet object. Previously, passing a non-callable object would fail after the greenlet
was spawned. was spawned.
.. attribute:: value
Holds the value returned by the function if the greenlet has
finished successfully. Until then, or if it finished in error, ``None``.
.. tip:: Recall that a greenlet killed with the default
:class:`GreenletExit` is considered to have finished
successfully, and the ``GreenletExit`` exception will be
its value.
.. attribute:: spawn_tree_locals
A dictionary that is shared between all the greenlets
in a "spawn tree", that is, a spawning greenlet and all
its descendent greenlets. All children of the main (root)
greenlet start their own spawn trees. Assign a new dictionary
to this attribute on an instance of this class to create a new
spawn tree (as far as locals are concerned).
.. versionadded:: 1.3a2
.. attribute:: spawning_greenlet
A weak-reference to the greenlet that was current when this object
was created. Note that the :attr:`parent` attribute is always the
hub.
.. versionadded:: 1.3a2
.. attribute:: spawning_stack
A lightweight frame-like object capturing the stack when
this greenlet was created as well as the stack when the spawning
greenlet was created (if applicable). This can be passed to
:func:`traceback.print_stack`.
.. versionadded:: 1.3a2
.. attribute:: spawning_stack_limit
A class attribute specifying how many levels of the spawning
stack will be kept. Specify a smaller number for higher performance,
specify a larger value for improved debugging.
.. versionadded:: 1.3a2
""" """
# 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
...@@ -192,8 +192,14 @@ class Greenlet(greenlet): ...@@ -192,8 +192,14 @@ class Greenlet(greenlet):
# 2.7.14 : Mean +- std dev: 14.8 us +- 0.5 us -> 10.2x # 2.7.14 : Mean +- std dev: 14.8 us +- 0.5 us -> 10.2x
# PyPy2 5.10.0: Mean +- std dev: 3.24 us +- 0.17 us -> 1.5x # PyPy2 5.10.0: Mean +- std dev: 3.24 us +- 0.17 us -> 1.5x
# Compiling with Cython gets us to these numbers:
# 3.6.4 : Mean +- std dev: 4.36 us +- 0.23 us
# 2.7.12 : Mean +- std dev: 3.81 us +- 0.15 us
# PyPy2 5.10.0 : Mean +- std dev: 3.63 us +- 0.22 us
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
...@@ -204,10 +210,21 @@ class Greenlet(greenlet): ...@@ -204,10 +210,21 @@ class Greenlet(greenlet):
if not callable(self._run): if not callable(self._run):
raise TypeError("The run argument or self._run must be callable") raise TypeError("The run argument or self._run must be callable")
if args: #: An event, such as a timer or a callback that fires. It is established in
#: start() and start_later() as those two objects, respectively.
#: Once this becomes non-None, the Greenlet cannot be started again. Conversely,
#: kill() and throw() check for non-None to determine if this object has ever been
#: scheduled for starting. A placeholder _dummy_event is assigned by them to prevent
#: the greenlet from being started in the future, if necessary.
self._start_event = None
self.args = args self.args = args
if kwargs:
self._kwargs = kwargs self._kwargs = kwargs
self.value = None
self._exc_info = ()
self._notifier = None
self._links = []
spawner = getcurrent() spawner = getcurrent()
self.spawning_greenlet = wref(spawner) self.spawning_greenlet = wref(spawner)
...@@ -242,13 +259,6 @@ class Greenlet(greenlet): ...@@ -242,13 +259,6 @@ class Greenlet(greenlet):
def kwargs(self): def kwargs(self):
return self._kwargs or {} return self._kwargs or {}
@Lazy
def _links(self):
return deque()
def _has_links(self):
return '_links' in self.__dict__ and self._links
def _raise_exception(self): def _raise_exception(self):
reraise(*self.exc_info) reraise(*self.exc_info)
...@@ -257,9 +267,14 @@ class Greenlet(greenlet): ...@@ -257,9 +267,14 @@ class Greenlet(greenlet):
# needed by killall # needed by killall
return self.parent.loop return self.parent.loop
def __bool__(self): def __nonzero__(self):
return self._start_event is not None and self._exc_info is Greenlet._exc_info return self._start_event is not None and self._exc_info == ()
__nonzero__ = __bool__ try:
__bool__ = __nonzero__ # Python 3
except NameError: # pragma: no cover
# When we're compiled with Cython, the __nonzero__ function
# goes directly into the slot and can't be accessed by name.
pass
### Lifecycle ### Lifecycle
...@@ -269,38 +284,33 @@ class Greenlet(greenlet): ...@@ -269,38 +284,33 @@ class Greenlet(greenlet):
def dead(self): def dead(self):
if self._greenlet__main: if self._greenlet__main:
return False return False
if self.__start_cancelled_by_kill or self.__started_but_aborted: if self.__start_cancelled_by_kill() or self.__started_but_aborted():
return True return True
return self._greenlet__started and not _continulet.is_pending(self) return self._greenlet__started and not _continulet.is_pending(self)
else: else:
@property @property
def dead(self): def dead(self):
return self.__start_cancelled_by_kill or self.__started_but_aborted or greenlet.dead.__get__(self) return self.__start_cancelled_by_kill() or self.__started_but_aborted() or greenlet.dead.__get__(self)
@property
def __never_started_or_killed(self): def __never_started_or_killed(self):
return self._start_event is None return self._start_event is None
@property
def __start_pending(self): def __start_pending(self):
return (self._start_event is not None return (self._start_event is not None
and (self._start_event.pending or getattr(self._start_event, 'active', False))) and (self._start_event.pending or getattr(self._start_event, 'active', False)))
@property
def __start_cancelled_by_kill(self): def __start_cancelled_by_kill(self):
return self._start_event is _cancelled_start_event return self._start_event is _cancelled_start_event
@property
def __start_completed(self): def __start_completed(self):
return self._start_event is _start_completed_event return self._start_event is _start_completed_event
@property
def __started_but_aborted(self): def __started_but_aborted(self):
return (not self.__never_started_or_killed # we have been started or killed return (not self.__never_started_or_killed() # we have been started or killed
and not self.__start_cancelled_by_kill # we weren't killed, so we must have been started and not self.__start_cancelled_by_kill() # we weren't killed, so we must have been started
and not self.__start_completed # the start never completed and not self.__start_completed() # the start never completed
and not self.__start_pending) # and we're not pending, so we must have been aborted and not self.__start_pending()) # and we're not pending, so we must have been aborted
def __cancel_start(self): def __cancel_start(self):
if self._start_event is None: if self._start_event is None:
...@@ -317,7 +327,7 @@ class Greenlet(greenlet): ...@@ -317,7 +327,7 @@ class Greenlet(greenlet):
def __handle_death_before_start(self, *args): def __handle_death_before_start(self, *args):
# args is (t, v, tb) or simply t or v # args is (t, v, tb) or simply t or v
if self._exc_info is Greenlet._exc_info and self.dead: if self._exc_info == () and self.dead:
# the greenlet was never switched to before and it will never be, _report_error was not called # 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. # 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 # checking that self.dead is true is essential, because throw() does not necessarily kill the greenlet
...@@ -397,8 +407,9 @@ class Greenlet(greenlet): ...@@ -397,8 +407,9 @@ class Greenlet(greenlet):
@property @property
def exception(self): def exception(self):
"""Holds the exception instance raised by the function if the greenlet has finished with an error. """
Otherwise ``None``. Holds the exception instance raised by the function if the
greenlet has finished with an error. Otherwise ``None``.
""" """
return self._exc_info[1] if self._exc_info else None return self._exc_info[1] if self._exc_info else None
...@@ -444,7 +455,12 @@ class Greenlet(greenlet): ...@@ -444,7 +455,12 @@ class Greenlet(greenlet):
self._start_event = self.parent.loop.run_callback(self.switch) self._start_event = self.parent.loop.run_callback(self.switch)
def start_later(self, seconds): def start_later(self, seconds):
"""Schedule the greenlet to run in the future loop iteration *seconds* later""" """
start_later(seconds) -> None
Schedule the greenlet to run in the future loop iteration
*seconds* later
"""
if self._start_event is None: if self._start_event is None:
self._start_event = self.parent.loop.timer(seconds) self._start_event = self.parent.loop.timer(seconds)
self._start_event.start(self.switch) self._start_event.start(self.switch)
...@@ -539,11 +555,17 @@ class Greenlet(greenlet): ...@@ -539,11 +555,17 @@ class Greenlet(greenlet):
# thus it should not raise when the greenlet is already killed (= not started) # thus it should not raise when the greenlet is already killed (= not started)
def get(self, block=True, timeout=None): def get(self, block=True, timeout=None):
"""Return the result the greenlet has returned or re-raise the exception it has raised. """
get(block=True, timeout=None) -> object
If block is ``False``, raise :class:`gevent.Timeout` if the greenlet is still alive. Return the result the greenlet has returned or re-raise the
If block is ``True``, unschedule the current greenlet until the result is available exception it has raised.
or the timeout expires. In the latter case, :class:`gevent.Timeout` is raised.
If block is ``False``, raise :class:`gevent.Timeout` if the
greenlet is still alive. If block is ``True``, unschedule the
current greenlet until the result is available or the timeout
expires. In the latter case, :class:`gevent.Timeout` is
raised.
""" """
if self.ready(): if self.ready():
if self.successful(): if self.successful():
...@@ -577,8 +599,11 @@ class Greenlet(greenlet): ...@@ -577,8 +599,11 @@ class Greenlet(greenlet):
self._raise_exception() self._raise_exception()
def join(self, timeout=None): def join(self, timeout=None):
"""Wait until the greenlet finishes or *timeout* expires. """
Return ``None`` regardless. join(timeout=None) -> None
Wait until the greenlet finishes or *timeout* expires. Return
``None`` regardless.
""" """
if self.ready(): if self.ready():
return return
...@@ -604,7 +629,7 @@ class Greenlet(greenlet): ...@@ -604,7 +629,7 @@ class Greenlet(greenlet):
def _report_result(self, result): def _report_result(self, result):
self._exc_info = (None, None, None) self._exc_info = (None, None, None)
self.value = result self.value = result
if self._has_links() and not self._notifier: if self._links and not self._notifier:
self._notifier = self.parent.loop.run_callback(self._notify_links) self._notifier = self.parent.loop.run_callback(self._notify_links)
def _report_error(self, exc_info): def _report_error(self, exc_info):
...@@ -614,7 +639,7 @@ class Greenlet(greenlet): ...@@ -614,7 +639,7 @@ class Greenlet(greenlet):
self._exc_info = exc_info[0], exc_info[1], dump_traceback(exc_info[2]) self._exc_info = exc_info[0], exc_info[1], dump_traceback(exc_info[2])
if self._has_links() and not self._notifier: if self._links and not self._notifier:
self._notifier = self.parent.loop.run_callback(self._notify_links) self._notifier = self.parent.loop.run_callback(self._notify_links)
try: try:
...@@ -635,8 +660,8 @@ class Greenlet(greenlet): ...@@ -635,8 +660,8 @@ class Greenlet(greenlet):
self._report_result(result) self._report_result(result)
finally: finally:
self.__dict__.pop('_run', None) self.__dict__.pop('_run', None)
self.__dict__.pop('args', None) self.args = None
self.__dict__.pop('kwargs', None) self._kwargs = None
def _run(self): def _run(self):
"""Subclasses may override this method to take any number of arguments and keyword arguments. """Subclasses may override this method to take any number of arguments and keyword arguments.
...@@ -692,13 +717,23 @@ class Greenlet(greenlet): ...@@ -692,13 +717,23 @@ class Greenlet(greenlet):
self.link(callback, SpawnedLink=SpawnedLink) self.link(callback, SpawnedLink=SpawnedLink)
def link_exception(self, callback, SpawnedLink=FailureSpawnedLink): def link_exception(self, callback, SpawnedLink=FailureSpawnedLink):
"""Like :meth:`link` but *callback* is only notified when the greenlet dies because of an unhandled exception.""" """
Like :meth:`link` but *callback* is only notified when the
greenlet dies because of an unhandled exception.
"""
# pylint:disable=redefined-outer-name # pylint:disable=redefined-outer-name
self.link(callback, SpawnedLink=SpawnedLink) self.link(callback, SpawnedLink=SpawnedLink)
def _notify_links(self): def _notify_links(self):
while self._links: while self._links:
link = self._links.popleft() # pylint:disable=no-member # Early links are allowed to remove later links
# before we get to them, and they're also allowed to
# add new links, so we have to be careful about iterating.
# We don't expect this list to be very large, so the time spent
# manipulating it should be small. a deque is probably not justified.
# Cython has optimizations to transform this into a memmove anyway.
link = self._links.pop(0)
try: try:
link(self) link(self)
except: # pylint:disable=bare-except except: # pylint:disable=bare-except
...@@ -706,8 +741,10 @@ class Greenlet(greenlet): ...@@ -706,8 +741,10 @@ class Greenlet(greenlet):
class _dummy_event(object): class _dummy_event(object):
pending = False __slots__ = ('pending', 'active')
active = False
def __init__(self):
self.pending = self.active = False
def stop(self): def stop(self):
pass pass
...@@ -715,11 +752,11 @@ class _dummy_event(object): ...@@ -715,11 +752,11 @@ class _dummy_event(object):
def start(self, cb): # pylint:disable=unused-argument def start(self, cb): # pylint:disable=unused-argument
raise AssertionError("Cannot start the dummy event") raise AssertionError("Cannot start the dummy event")
close = stop def close(self):
pass
_cancelled_start_event = _dummy_event() _cancelled_start_event = _dummy_event()
_start_completed_event = _dummy_event() _start_completed_event = _dummy_event()
del _dummy_event
def _kill(glet, exception, waiter): def _kill(glet, exception, waiter):
......
...@@ -374,7 +374,8 @@ class TestStuff(greentest.TestCase): ...@@ -374,7 +374,8 @@ class TestStuff(greentest.TestCase):
link(results.listener2) link(results.listener2)
link(results.listener3) link(results.listener3)
sleep(DELAY * 10) sleep(DELAY * 10)
assert results.results == [5], results.results self.assertEqual([5], results.results)
def test_multiple_listeners_error_unlink_Greenlet_link(self): def test_multiple_listeners_error_unlink_Greenlet_link(self):
p = gevent.spawn(lambda: 5) p = gevent.spawn(lambda: 5)
...@@ -515,14 +516,14 @@ class TestBasic(greentest.TestCase): ...@@ -515,14 +516,14 @@ class TestBasic(greentest.TestCase):
assert g.exception is None assert g.exception is None
gevent.sleep(0.001) gevent.sleep(0.001)
assert g self.assertTrue(g)
assert not g.dead self.assertFalse(g.dead, g)
assert g.started self.assertTrue(g.started, g)
assert not g.ready() self.assertFalse(g.ready(), g)
assert not g.successful() self.assertFalse(g.successful(), g)
assert g.value is None self.assertIsNone(g.value, g)
assert g.exception is None self.assertIsNone(g.exception, g)
assert not link_test self.assertFalse(link_test)
gevent.sleep(0.02) gevent.sleep(0.02)
assert not g assert not g
......
...@@ -11,7 +11,7 @@ import greentest ...@@ -11,7 +11,7 @@ import greentest
import gevent import gevent
from gevent import util from gevent import util
@greentest.skipOnPyPy("5.10.x is *very* slow formatting stacks")
class TestFormat(greentest.TestCase): class TestFormat(greentest.TestCase):
def test_basic(self): def test_basic(self):
......
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