aboutsummaryrefslogtreecommitdiffstats
path: root/python/gevent/threading.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/gevent/threading.py')
-rw-r--r--python/gevent/threading.py231
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')