diff options
Diffstat (limited to 'python/gevent/threading.py')
-rw-r--r-- | python/gevent/threading.py | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/python/gevent/threading.py b/python/gevent/threading.py new file mode 100644 index 0000000..5098aed --- /dev/null +++ b/python/gevent/threading.py @@ -0,0 +1,231 @@ +""" +Implementation of the standard :mod:`threading` using greenlets. + +.. note:: + + This module is a helper for :mod:`gevent.monkey` and is not + intended to be used directly. For spawning greenlets in your + applications, prefer higher level constructs like + :class:`gevent.Greenlet` class or :func:`gevent.spawn`. +""" +from __future__ import absolute_import + + +__implements__ = [ + 'local', + '_start_new_thread', + '_allocate_lock', + 'Lock', + '_get_ident', + '_sleep', + '_DummyThread', +] + + +import threading as __threading__ +_DummyThread_ = __threading__._DummyThread +from gevent.local import local +from gevent.thread import start_new_thread as _start_new_thread, allocate_lock as _allocate_lock, get_ident as _get_ident +from gevent._compat import PYPY +from gevent.hub import sleep as _sleep, getcurrent + +# Exports, prevent unused import warnings +local = local +start_new_thread = _start_new_thread +allocate_lock = _allocate_lock +_get_ident = _get_ident +_sleep = _sleep +getcurrent = getcurrent + +Lock = _allocate_lock + + +def _cleanup(g): + __threading__._active.pop(id(g), None) + +def _make_cleanup_id(gid): + def _(_r): + __threading__._active.pop(gid, None) + return _ + +_weakref = None + +class _DummyThread(_DummyThread_): + # We avoid calling the superclass constructor. This makes us about + # twice as fast (1.16 vs 0.68usec on PyPy, 29.3 vs 17.7usec on + # CPython 2.7), and has the important effect of avoiding + # allocation and then immediate deletion of _Thread__block, a + # lock. This is especially important on PyPy where locks go + # through the cpyext API and Cython, which is known to be slow and + # potentially buggy (e.g., + # https://bitbucket.org/pypy/pypy/issues/2149/memory-leak-for-python-subclass-of-cpyext#comment-22347393) + + # These objects are constructed quite frequently in some cases, so + # the optimization matters: for example, in gunicorn, which uses + # pywsgi.WSGIServer, every request is handled in a new greenlet, + # and every request uses a logging.Logger to write the access log, + # and every call to a log method captures the current thread (by + # default). + # + # (Obviously we have to duplicate the effects of the constructor, + # at least for external state purposes, which is potentially + # slightly fragile.) + + # For the same reason, instances of this class will cleanup their own entry + # in ``threading._active`` + + # Capture the static things as class vars to save on memory/ + # construction time. + # In Py2, they're all private; in Py3, they become protected + _Thread__stopped = _is_stopped = _stopped = False + _Thread__initialized = _initialized = True + _Thread__daemonic = _daemonic = True + _Thread__args = _args = () + _Thread__kwargs = _kwargs = None + _Thread__target = _target = None + _Thread_ident = _ident = None + _Thread__started = _started = __threading__.Event() + _Thread__started.set() + _tstate_lock = None + + def __init__(self): + #_DummyThread_.__init__(self) # pylint:disable=super-init-not-called + + # It'd be nice to use a pattern like "greenlet-%d", but maybe somebody out + # there is checking thread names... + self._name = self._Thread__name = __threading__._newname("DummyThread-%d") + self._set_ident() + + g = getcurrent() + gid = _get_ident(g) # same as id(g) + __threading__._active[gid] = self + rawlink = getattr(g, 'rawlink', None) + if rawlink is not None: + # raw greenlet.greenlet greenlets don't + # have rawlink... + rawlink(_cleanup) + else: + # ... so for them we use weakrefs. + # See https://github.com/gevent/gevent/issues/918 + global _weakref + if _weakref is None: + _weakref = __import__('weakref') + ref = _weakref.ref(g, _make_cleanup_id(gid)) + self.__raw_ref = ref + + def _Thread__stop(self): + pass + + _stop = _Thread__stop # py3 + + def _wait_for_tstate_lock(self, *args, **kwargs): + # pylint:disable=arguments-differ + pass + +if hasattr(__threading__, 'main_thread'): # py 3.4+ + def main_native_thread(): + return __threading__.main_thread() # pylint:disable=no-member +else: + _main_threads = [(_k, _v) for _k, _v in __threading__._active.items() + if isinstance(_v, __threading__._MainThread)] + assert len(_main_threads) == 1, "Too many main threads" + + def main_native_thread(): + return _main_threads[0][1] + +# Make sure the MainThread can be found by our current greenlet ID, +# otherwise we get a new DummyThread, which cannot be joined. +# Fixes tests in test_threading_2 under PyPy, and generally makes things nicer +# when gevent.threading is imported before monkey patching or not at all +# XXX: This assumes that the import is happening in the "main" greenlet/thread. +# XXX: We should really only be doing this from gevent.monkey. +if _get_ident() not in __threading__._active: + _v = main_native_thread() + _k = _v.ident + del __threading__._active[_k] + _v._ident = _v._Thread__ident = _get_ident() + __threading__._active[_get_ident()] = _v + del _k + del _v + + # Avoid printing an error on shutdown trying to remove the thread entry + # we just replaced if we're not fully monkey patched in + # XXX: This causes a hang on PyPy for some unknown reason (as soon as class _active + # defines __delitem__, shutdown hangs. Maybe due to something with the GC? + # XXX: This may be fixed in 2.6.1+ + if not PYPY: + # pylint:disable=no-member + _MAIN_THREAD = __threading__._get_ident() if hasattr(__threading__, '_get_ident') else __threading__.get_ident() + + class _active(dict): + def __delitem__(self, k): + if k == _MAIN_THREAD and k not in self: + return + dict.__delitem__(self, k) + + __threading__._active = _active(__threading__._active) + + +import sys +if sys.version_info[:2] >= (3, 4): + # XXX: Issue 18808 breaks us on Python 3.4. + # Thread objects now expect a callback from the interpreter itself + # (threadmodule.c:release_sentinel). Because this never happens + # when a greenlet exits, join() and friends will block forever. + # The solution below involves capturing the greenlet when it is + # started and deferring the known broken methods to it. + + class Thread(__threading__.Thread): + _greenlet = None + + def is_alive(self): + return bool(self._greenlet) + + isAlive = is_alive + + def _set_tstate_lock(self): + self._greenlet = getcurrent() + + def run(self): + try: + super(Thread, self).run() + finally: + # avoid ref cycles, but keep in __dict__ so we can + # distinguish the started/never-started case + self._greenlet = None + self._stop() # mark as finished + + def join(self, timeout=None): + if '_greenlet' not in self.__dict__: + raise RuntimeError("Cannot join an inactive thread") + if self._greenlet is None: + return + self._greenlet.join(timeout=timeout) + + def _wait_for_tstate_lock(self, *args, **kwargs): + # pylint:disable=arguments-differ + raise NotImplementedError() + + __implements__.append('Thread') + + # The main thread is patched up with more care in monkey.py + #t = __threading__.current_thread() + #if isinstance(t, __threading__.Thread): + # t.__class__ = Thread + # t._greenlet = getcurrent() + +if sys.version_info[:2] >= (3, 3): + __implements__.remove('_get_ident') + __implements__.append('get_ident') + get_ident = _get_ident + __implements__.remove('_sleep') + + # Python 3 changed the implementation of threading.RLock + # Previously it was a factory function around threading._RLock + # which in turn used _allocate_lock. Now, it wants to use + # threading._CRLock, which is imported from _thread.RLock and as such + # is implemented in C. So it bypasses our _allocate_lock function. + # Fortunately they left the Python fallback in place + assert hasattr(__threading__, '_CRLock'), "Unsupported Python version" + _CRLock = None + __implements__.append('_CRLock') |