summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Burke <tim.burke@gmail.com>2020-05-07 16:00:30 -0700
committerTim Burke <tim.burke@gmail.com>2020-08-28 20:58:07 -0700
commit115103d5608cbe8f15df10e27eba1644f5364e95 (patch)
tree0c1d24791b66f2650bf822cda3b5fa2efd5f5abc
parent44bfc1e0a95c50af4121695aaefc02e6642f938d (diff)
downloadeventlet-115103d5608cbe8f15df10e27eba1644f5364e95.tar.gz
Clean up threading book-keeping at fork when monkey-patched
Previously, if we patched threading then forked (or, in some cases, used the subprocess module), Python would log an ignored exception like Exception ignored in: <function _after_fork at 0x7f16493489d8> Traceback (most recent call last): File "/usr/lib/python3.7/threading.py", line 1335, in _after_fork assert len(_active) == 1 AssertionError: This comes down to threading in Python 3.7+ having an import side-effect of registering an at-fork callback. When we re-import threading to patch it, the old (but still registered) callback still points to the old thread-tracking dict, rather than the new dict that's actually doing the tracking. Now, register our own at_fork hook that will fix up the dict reference before threading's _at_fork runs and put it back afterwards. Closes #592
-rw-r--r--eventlet/patcher.py20
-rw-r--r--tests/isolated/patcher_fork_after_monkey_patch.py59
-rw-r--r--tests/patcher_test.py4
3 files changed, 83 insertions, 0 deletions
diff --git a/eventlet/patcher.py b/eventlet/patcher.py
index bfc7faa..a578637 100644
--- a/eventlet/patcher.py
+++ b/eventlet/patcher.py
@@ -312,6 +312,26 @@ def monkey_patch(**on):
for attr_name in deleted:
if hasattr(orig_mod, attr_name):
delattr(orig_mod, attr_name)
+
+ if name == 'threading' and sys.version_info >= (3, 7):
+ def fix_threading_active(
+ _os=original('os'),
+ _global_dict=original('threading').current_thread.__globals__,
+ _patched=orig_mod
+ ):
+ _prefork_active = [None]
+
+ def before_fork():
+ _prefork_active[0] = _global_dict['_active']
+ _global_dict['_active'] = _patched._active
+
+ def after_fork():
+ _global_dict['_active'] = _prefork_active[0]
+
+ _os.register_at_fork(
+ before=before_fork,
+ after_in_parent=after_fork)
+ fix_threading_active()
finally:
imp.release_lock()
diff --git a/tests/isolated/patcher_fork_after_monkey_patch.py b/tests/isolated/patcher_fork_after_monkey_patch.py
new file mode 100644
index 0000000..6c5c445
--- /dev/null
+++ b/tests/isolated/patcher_fork_after_monkey_patch.py
@@ -0,0 +1,59 @@
+# Monkey patching interferes with threading in Python 3.7
+# https://github.com/eventlet/eventlet/issues/592
+__test__ = False
+
+
+def check(n, mod, tag):
+ assert len(mod._active) == n, 'Expected {} {} threads, got {}'.format(n, tag, mod._active)
+
+
+if __name__ == '__main__':
+ import eventlet
+ import eventlet.patcher
+ eventlet.monkey_patch()
+ import os
+ import sys
+ import threading
+ _threading = eventlet.patcher.original('threading')
+ import eventlet.green.threading
+
+ def target():
+ eventlet.sleep(0.1)
+
+ threads = [
+ threading.Thread(target=target, name='patched'),
+ _threading.Thread(target=target, name='original-1'),
+ _threading.Thread(target=target, name='original-2'),
+ eventlet.green.threading.Thread(target=target, name='green-1'),
+ eventlet.green.threading.Thread(target=target, name='green-2'),
+ eventlet.green.threading.Thread(target=target, name='green-3'),
+ ]
+ for t in threads:
+ t.start()
+
+ check(2, threading, 'pre-fork patched')
+ check(3, _threading, 'pre-fork original')
+ check(4, eventlet.green.threading, 'pre-fork green')
+
+ if os.fork() == 0:
+ # Inside the child, we should only have a main thread,
+ # but old pythons make it difficult to ensure
+ if sys.version_info >= (3, 7):
+ check(1, threading, 'child post-fork patched')
+ check(1, _threading, 'child post-fork original')
+ check(1, eventlet.green.threading, 'child post-fork green')
+ sys.exit()
+ else:
+ os.wait()
+
+ check(2, threading, 'post-fork patched')
+ check(3, _threading, 'post-fork original')
+ check(4, eventlet.green.threading, 'post-fork green')
+
+ for t in threads:
+ t.join()
+
+ check(1, threading, 'post-join patched')
+ check(1, _threading, 'post-join original')
+ check(1, eventlet.green.threading, 'post-join green')
+ print('pass')
diff --git a/tests/patcher_test.py b/tests/patcher_test.py
index 8c60bf1..f1ef1f9 100644
--- a/tests/patcher_test.py
+++ b/tests/patcher_test.py
@@ -515,3 +515,7 @@ def test_threading_current():
def test_threadpoolexecutor():
tests.run_isolated('patcher_threadpoolexecutor.py')
+
+
+def test_fork_after_monkey_patch():
+ tests.run_isolated('patcher_fork_after_monkey_patch.py')