Commit 0e713359 authored by Jason Madden's avatar Jason Madden

Fix #1839 in a ham-fisted way.

There is probably a more elegant solution.
parent 23d13143
Fix hanging the interpreter on shutdown if gevent monkey patching
occurred on a non-main thread in Python 3.9.8 and above. (Note that
this is not a recommended practice.)
......@@ -862,6 +862,7 @@ def patch_thread(threading=True, _threading_local=True, Event=True, logging=True
# gevent.threading.
greenlet = __import__('greenlet')
already_patched = is_object_patched('threading', '_shutdown')
orig_shutdown = threading_mod._shutdown
if orig_current_thread == threading_mod.main_thread() and not already_patched:
main_thread = threading_mod.main_thread()
......@@ -879,7 +880,7 @@ def patch_thread(threading=True, _threading_local=True, Event=True, logging=True
# C data structure).
main_thread._tstate_lock = threading_mod.Lock()
main_thread._tstate_lock.acquire()
orig_shutdown = threading_mod._shutdown
def _shutdown():
# Release anyone trying to join() me,
# and let us switch to them.
......@@ -933,6 +934,23 @@ def patch_thread(threading=True, _threading_local=True, Event=True, logging=True
"threading.main_thread().join() will hang from a greenlet",
_warnings)
main_thread = threading_mod.main_thread()
def _shutdown():
# We've patched get_ident but *did not* patch the
# main_thread.ident value. Beginning in Python 3.9.8
# and then later releases (3.10.1, probably), the
# _main_thread object is only _stop() if the ident of
# the current thread (the *real* main thread) matches
# the ident of the _main_thread object. But without doing that,
# the main thread's shutdown lock (threading._shutdown_locks) is never
# removed *or released*, thus hanging the interpreter.
# XXX: There's probably a better way to do this. Probably need to take a
# step back and look at the whole picture.
main_thread._ident = threading_mod.get_ident()
orig_shutdown()
patch_item(threading_mod, '_shutdown', orig_shutdown)
patch_item(threading_mod, '_shutdown', _shutdown)
from gevent import events
_notify_patch(events.GeventDidPatchModuleEvent('thread', gevent_thread_mod, thread_mod))
_notify_patch(events.GeventDidPatchModuleEvent('threading', gevent_threading_mod, threading_mod))
......
......@@ -26,7 +26,7 @@ def wrap_error_fatal(method):
system_error = get_hub_class().SYSTEM_ERROR
@wraps(method)
def wrapper(self, *args, **kwargs):
def fatal_error_wrapper(self, *args, **kwargs):
# XXX should also be able to do gevent.SYSTEM_ERROR = object
# which is a global default to all hubs
get_hub_class().SYSTEM_ERROR = object
......@@ -34,7 +34,7 @@ def wrap_error_fatal(method):
return method(self, *args, **kwargs)
finally:
get_hub_class().SYSTEM_ERROR = system_error
return wrapper
return fatal_error_wrapper
def wrap_restore_handle_error(method):
......@@ -42,7 +42,7 @@ def wrap_restore_handle_error(method):
from gevent import getcurrent
@wraps(method)
def wrapper(self, *args, **kwargs):
def restore_fatal_error_wrapper(self, *args, **kwargs):
try:
return method(self, *args, **kwargs)
finally:
......@@ -54,4 +54,4 @@ def wrap_restore_handle_error(method):
pass
if self.peek_error()[0] is not None:
getcurrent().throw(*self.peek_error()[1:])
return wrapper
return restore_fatal_error_wrapper
......@@ -173,6 +173,10 @@ def get_python_version():
return version
# XXX: In Python 3.10, distutils is deprecated and slated for removal in
# 3.12. The suggestion is to use setuptools, but it only has LooseVersion
# in an internal package and suggests using the new dependency of 'packaging'
def libev_supports_linux_aio():
# libev requires kernel 4.19 or above to be able to support
# linux AIO. It can still be compiled in, but will fail to create
......
......@@ -177,11 +177,11 @@ def _wrap_timeout(timeout, method):
return method
@wraps(method)
def wrapper(self, *args, **kwargs):
def timeout_wrapper(self, *args, **kwargs):
with TestTimeout(timeout, method):
return method(self, *args, **kwargs)
return wrapper
return timeout_wrapper
def _get_class_attr(classDict, bases, attr, default=AttributeError):
NONE = object()
......
......@@ -4,5 +4,6 @@ import threading
# a threading lock. Under Python2, where RLock is implemented
# in python code, this used to throw RuntimeErro("Cannot release un-acquired lock")
# See https://github.com/gevent/gevent/issues/615
# pylint:disable=useless-with-lock
with threading.RLock():
monkey.patch_all() # pragma: testrunner-no-monkey-combine
......@@ -45,15 +45,17 @@ class Test(greentest.TestCase):
# We generated some warnings
if greentest.PY3:
self.assertEqual(all_warnings,
['Monkey-patching outside the main native thread. Some APIs will not be '
'available. Expect a KeyError to be printed at shutdown.',
'Monkey-patching not on the main thread; threading.main_thread().join() '
'will hang from a greenlet'])
self.assertEqual(
all_warnings,
['Monkey-patching outside the main native thread. Some APIs will not be '
'available. Expect a KeyError to be printed at shutdown.',
'Monkey-patching not on the main thread; threading.main_thread().join() '
'will hang from a greenlet'])
else:
self.assertEqual(all_warnings,
['Monkey-patching outside the main native thread. Some APIs will not be '
'available. Expect a KeyError to be printed at shutdown.'])
self.assertEqual(
all_warnings,
['Monkey-patching outside the main native thread. Some APIs will not be '
'available. Expect a KeyError to be printed at shutdown.'])
# Manual clean up so we don't get a KeyError
......@@ -61,5 +63,6 @@ class Test(greentest.TestCase):
threading._active[(getattr(threading, 'get_ident', None) or threading._get_ident)()] = current
if __name__ == '__main__':
greentest.main()
......@@ -14,6 +14,9 @@ def func():
raise AssertionError('localdata.x must raise AttributeError')
except AttributeError:
pass
# We really want to check this is exactly an empty dict,
# not just anything falsey
# pylint:disable=use-implicit-booleaness-not-comparison
assert localdata.__dict__ == {}, localdata.__dict__
success.append(1)
......
......@@ -19,6 +19,7 @@ from greenlet import getcurrent
from gevent._compat import NativeStrIO
class MyLocal(local.local):
# pylint:disable=disallowed-name
def __init__(self, foo):
self.foo = foo
......
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