Commit 02a81bca authored by Jason Madden's avatar Jason Madden

Reuse an existing loop instance when a new hub is created, fixing a

crash in a particular use cases. Fixes #237 and fixes #238.

Also clean up usage of hub._threadlocal, eliminating many try/except cases.

The particular crash seems to be in a corner-case usage (generally,
destroying a hub and/or loop seems to be advanced or rare usage), so I
feel pretty safe merging it into a release-track branch. The lifetime
management of the loop may not be totally ideal, but it solves the
issue. loop objects may now live longer (until the death of their
thread) if the hub was destroyed but the loop wasn't.
parent 19093838
...@@ -19,7 +19,12 @@ ...@@ -19,7 +19,12 @@
- PyPy: Fix a potential crash. Reported in :issue:`676` by Jay Oster. - PyPy: Fix a potential crash. Reported in :issue:`676` by Jay Oster.
- PyPy: Exceptions raised while handling an error raised by a loop - PyPy: Exceptions raised while handling an error raised by a loop
callback function behave like the CPython implementation: the callback function behave like the CPython implementation: the
exception is printed, and the rest of the callbacks continue processing. exception is printed, and the rest of the callbacks continue
processing.
- If a Hub object with active watchers, was destroyed and then another
one created for the same thread which itself was then destroyed with
``destroy_loop=True``, the process could crash. Documented in
:issue:`237` and fix based on :pr:`238`, both by Jan-Philip Gehrcke.
1.1b6 (Oct 17, 2015) 1.1b6 (Oct 17, 2015)
==================== ====================
......
...@@ -58,9 +58,22 @@ if sys.version_info[0] <= 2: ...@@ -58,9 +58,22 @@ if sys.version_info[0] <= 2:
import thread import thread
else: else:
import _thread as thread import _thread as thread
# These must be the "real" native thread versions,
# not monkey-patched.
threadlocal = thread._local threadlocal = thread._local
_threadlocal = threadlocal()
_threadlocal.Hub = None
class _threadlocal(threadlocal):
def __init__(self):
threadlocal.__init__(self)
self.Hub = None
self.loop = None
self.hub = None
_threadlocal = _threadlocal()
get_ident = thread.get_ident get_ident = thread.get_ident
MAIN_THREAD = get_ident() MAIN_THREAD = get_ident()
...@@ -311,11 +324,7 @@ def get_hub_class(): ...@@ -311,11 +324,7 @@ def get_hub_class():
If there's no type of hub for the current thread yet, 'gevent.hub.Hub' is used. If there's no type of hub for the current thread yet, 'gevent.hub.Hub' is used.
""" """
global _threadlocal hubtype = _threadlocal.Hub
try:
hubtype = _threadlocal.Hub
except AttributeError:
hubtype = None
if hubtype is None: if hubtype is None:
hubtype = _threadlocal.Hub = Hub hubtype = _threadlocal.Hub = Hub
return hubtype return hubtype
...@@ -328,13 +337,11 @@ def get_hub(*args, **kwargs): ...@@ -328,13 +337,11 @@ 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`.
""" """
global _threadlocal hub = _threadlocal.hub
try: if hub is None:
return _threadlocal.hub
except AttributeError:
hubtype = get_hub_class() hubtype = get_hub_class()
hub = _threadlocal.hub = hubtype(*args, **kwargs) hub = _threadlocal.hub = hubtype(*args, **kwargs)
return hub return hub
def _get_hub(): def _get_hub():
...@@ -342,11 +349,7 @@ def _get_hub(): ...@@ -342,11 +349,7 @@ def _get_hub():
Return ``None`` if no hub has been created yet. Return ``None`` if no hub has been created yet.
""" """
global _threadlocal return _threadlocal.hub
try:
return _threadlocal.hub
except AttributeError:
pass
def set_hub(hub): def set_hub(hub):
...@@ -446,6 +449,11 @@ class Hub(greenlet): ...@@ -446,6 +449,11 @@ class Hub(greenlet):
if default is not None: if default is not None:
raise TypeError("Unexpected argument: default") raise TypeError("Unexpected argument: default")
self.loop = loop self.loop = loop
elif _threadlocal.loop is not None:
# Reuse a loop instance previously set by
# destroying a hub without destroying the associated
# loop. See #237 and #238.
self.loop = _threadlocal.loop
else: else:
if default is None and get_ident() != MAIN_THREAD: if default is None and get_ident() != MAIN_THREAD:
default = False default = False
...@@ -632,7 +640,6 @@ class Hub(greenlet): ...@@ -632,7 +640,6 @@ class Hub(greenlet):
return False return False
def destroy(self, destroy_loop=None): def destroy(self, destroy_loop=None):
global _threadlocal
if self._resolver is not None: if self._resolver is not None:
self._resolver.close() self._resolver.close()
del self._resolver del self._resolver
...@@ -642,10 +649,18 @@ class Hub(greenlet): ...@@ -642,10 +649,18 @@ class Hub(greenlet):
if destroy_loop is None: if destroy_loop is None:
destroy_loop = not self.loop.default destroy_loop = not self.loop.default
if destroy_loop: if destroy_loop:
if _threadlocal.loop is self.loop:
# Don't let anyone try to reuse this
_threadlocal.loop = None
self.loop.destroy() self.loop.destroy()
else:
# Store in case another hub is created for this
# thread.
_threadlocal.loop = self.loop
self.loop = None self.loop = None
if getattr(_threadlocal, 'hub', None) is self: if _threadlocal.hub is self:
del _threadlocal.hub _threadlocal.hub = None
def _get_resolver(self): def _get_resolver(self):
if self._resolver is None: if self._resolver is None:
......
from __future__ import print_function
import gevent import gevent
# Loop of initial Hub is default loop. # Loop of initial Hub is default loop.
hub = gevent.get_hub() hub = gevent.get_hub()
assert hub.loop.default, hub assert hub.loop.default, hub
# Destroy hub. Does not destroy default loop if not explicitly told to. # Save `gevent.core.loop` object for later comparison.
initloop = hub.loop
# Increase test complexity via threadpool creation.
# Implicitly creates fork watcher connected to the current event loop.
tp = hub.threadpool
# Destroy hub. Does not destroy libev default loop if not explicitly told to.
hub.destroy() hub.destroy()
# Create new hub. Must re-use existing libev default loop.
hub = gevent.get_hub() hub = gevent.get_hub()
assert hub.loop.default, hub assert hub.loop.default, hub
saved_loop = hub.loop # Ensure that loop object is identical to the initial one.
assert hub.loop is initloop
# Destroy hub including default loop. # Destroy hub including default loop.
hub.destroy(destroy_loop=True) hub.destroy(destroy_loop=True)
assert saved_loop.fileno() is None, saved_loop
print(hub, saved_loop)
# Create new hub and explicitly request creation of a new default loop. # Create new hub and explicitly request creation of a new default loop.
hub = gevent.get_hub(default=True) hub = gevent.get_hub(default=True)
assert hub.loop.default, hub assert hub.loop.default, hub
# Destroy hub including default loop. # `gevent.core.loop` objects as well as libev loop pointers must differ.
hub.destroy(destroy_loop=True) assert hub.loop is not initloop
assert hub.loop.ptr != initloop.ptr
# Create new non-default loop in new hub. # Destroy hub including default loop, create new hub with non-default loop.
hub.destroy(destroy_loop=True)
hub = gevent.get_hub() hub = gevent.get_hub()
assert not hub.loop.default, hub assert not hub.loop.default, hub
hub.destroy() hub.destroy()
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