Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-eventlet for openSUSE:Factory checked in at 2025-01-07 20:50:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-eventlet (Old) and /work/SRC/openSUSE:Factory/.python-eventlet.new.1881 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-eventlet" Tue Jan 7 20:50:54 2025 rev:59 rq:1235524 version:0.38.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-eventlet/python-eventlet.changes 2024-12-06 14:25:02.853116597 +0100 +++ /work/SRC/openSUSE:Factory/.python-eventlet.new.1881/python-eventlet.changes 2025-01-07 20:51:17.907650642 +0100 @@ -1,0 +2,11 @@ +Mon Jan 6 18:25:35 UTC 2025 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- Update to 0.38.2 + * [fix] fix the monkey patching with the asyncio hub + * [feature] introduce the unmonkeypatching feature +- from version 0.38.1 + * [fix] Python 3.13: Use greenthread's dead state where possible (#1000) + * [env] bump github Actions (#996) + * [fix] Fix bug where asyncio hub didn't support multiple os threads (#995) + +------------------------------------------------------------------- Old: ---- eventlet-0.38.0.tar.gz New: ---- eventlet-0.38.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-eventlet.spec ++++++ --- /var/tmp/diff_new_pack.YKsJ0M/_old 2025-01-07 20:51:18.395670820 +0100 +++ /var/tmp/diff_new_pack.YKsJ0M/_new 2025-01-07 20:51:18.399670985 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-eventlet # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-eventlet -Version: 0.38.0 +Version: 0.38.2 Release: 0 Summary: Concurrent networking library for Python License: MIT ++++++ eventlet-0.38.0.tar.gz -> eventlet-0.38.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/.github/workflows/docs.yaml new/eventlet-0.38.2/.github/workflows/docs.yaml --- old/eventlet-0.38.0/.github/workflows/docs.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/.github/workflows/docs.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -11,9 +11,9 @@ runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install dependencies diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/.github/workflows/publish.yaml new/eventlet-0.38.2/.github/workflows/publish.yaml --- old/eventlet-0.38.0/.github/workflows/publish.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/.github/workflows/publish.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -19,9 +19,9 @@ runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install dependencies diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/.github/workflows/style.yaml new/eventlet-0.38.2/.github/workflows/style.yaml --- old/eventlet-0.38.0/.github/workflows/style.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/.github/workflows/style.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -10,10 +10,10 @@ timeout-minutes: 5 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: cache pip - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/style.yaml') }} @@ -21,7 +21,7 @@ ${{ runner.os }}-pip- ${{ runner.os }}- - name: cache tox - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .tox key: ${{ runner.os }}-tox-style-${{ hashFiles('tox.ini') }} @@ -31,7 +31,7 @@ ${{ runner.os }}- - name: setup python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: 3.x - name: install tox diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/.github/workflows/test.yaml new/eventlet-0.38.2/.github/workflows/test.yaml --- old/eventlet-0.38.0/.github/workflows/test.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/.github/workflows/test.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -50,17 +50,17 @@ - { py: "3.11", toxenv: py311-asyncio, ignore-error: false, os: ubuntu-latest } - { py: "3.12", toxenv: py312-epolls, ignore-error: false, os: ubuntu-latest } - { py: "3.12", toxenv: py312-asyncio, ignore-error: false, os: ubuntu-latest } - - { py: "3.13-dev", toxenv: py313-epolls, ignore-error: false, os: ubuntu-24.04 } - - { py: "3.13-dev", toxenv: py313-asyncio, ignore-error: false, os: ubuntu-24.04 } + - { py: "3.13", toxenv: py313-epolls, ignore-error: false, os: ubuntu-24.04 } + - { py: "3.13", toxenv: py313-asyncio, ignore-error: false, os: ubuntu-24.04 } - { py: pypy3.9, toxenv: pypy3-epolls, ignore-error: true, os: ubuntu-20.04 } steps: - name: install system packages run: sudo apt install -y --no-install-recommends ccache libffi-dev default-libmysqlclient-dev libpq-dev libssl-dev libzmq3-dev - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: cache pip - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ matrix.toxenv }}-${{ hashFiles('.github/workflows/test.yaml', 'setup.py') }} @@ -68,7 +68,7 @@ ${{ runner.os }}-pip- ${{ runner.os }}- - name: cache tox - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .tox key: ${{ runner.os }}-tox-${{ matrix.toxenv }}-${{ hashFiles('tox.ini') }} @@ -77,7 +77,7 @@ ${{ runner.os }}- - name: setup python ${{ matrix.py }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.py }} - name: install codecov, tox @@ -100,14 +100,14 @@ matrix: include: - { py: "3.12", toxenv: py312-asyncio, ignore-error: false, os: macos-latest } - - { py: "3.13-dev", toxenv: py313-asyncio, ignore-error: false, os: macos-latest } + - { py: "3.13", toxenv: py313-asyncio, ignore-error: false, os: macos-latest } # This isn't working very well at the moment, but that might just be # tox config? In any case main focus is on asyncio so someone can # revisit separately. #- { py: "3.12", toxenv: py312-kqueue, ignore-error: false, os: macos-latest } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: install codecov, tox run: pip install codecov tox - run: env diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/NEWS new/eventlet-0.38.2/NEWS --- old/eventlet-0.38.0/NEWS 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/NEWS 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,19 @@ Unreleased ========== +0.38.2 +====== + +* [fix] fix the monkey patching with the asyncio hub +* [feature] introduce the unmonkeypatching feature + +0.38.1 +====== + +* [fix] Python 3.13: Use greenthread's dead state where possible (#1000) +* [env] bump github Actions (#996) +* [fix] Fix bug where asyncio hub didn't support multiple os threads (#995) + 0.38.0 ====== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/PKG-INFO new/eventlet-0.38.2/PKG-INFO --- old/eventlet-0.38.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.3 Name: eventlet -Version: 0.38.0 +Version: 0.38.2 Summary: Highly concurrent networking library Project-URL: Homepage, https://github.com/eventlet/eventlet Project-URL: History, https://github.com/eventlet/eventlet/blob/master/NEWS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/eventlet/_version.py new/eventlet-0.38.2/eventlet/_version.py --- old/eventlet-0.38.0/eventlet/_version.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/eventlet/_version.py 2020-02-02 01:00:00.000000000 +0100 @@ -12,5 +12,5 @@ __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE -__version__ = version = '0.38.0' -__version_tuple__ = version_tuple = (0, 38, 0) +__version__ = version = '0.38.2' +__version_tuple__ = version_tuple = (0, 38, 2) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/eventlet/green/thread.py new/eventlet-0.38.2/eventlet/green/thread.py --- old/eventlet-0.38.0/eventlet/green/thread.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/eventlet/green/thread.py 2020-02-02 01:00:00.000000000 +0100 @@ -59,6 +59,8 @@ self._done = True def is_done(self): + if self._greenthread is not None: + return self._greenthread.dead return self._done @property diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/eventlet/hubs/asyncio.py new/eventlet-0.38.2/eventlet/hubs/asyncio.py --- old/eventlet-0.38.0/eventlet/hubs/asyncio.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/eventlet/hubs/asyncio.py 2020-02-02 01:00:00.000000000 +0100 @@ -2,19 +2,19 @@ Asyncio-based hub, originally implemented by Miguel Grinberg. """ -import asyncio -try: - import concurrent.futures.thread - concurrent_imported = True -except RuntimeError: - # This happens in weird edge cases where asyncio hub is started at - # shutdown. Not much we can do if this happens. - concurrent_imported = False +# The various modules involved in asyncio need to call the original, unpatched +# standard library APIs to work: socket, select, threading, and so on. We +# therefore don't import them on the module level, since that would involve +# their imports getting patched, and instead delay importing them as much as +# possible. Then, we do a little song and dance in Hub.__init__ below so that +# when they're imported they import the original modules (select, socket, etc) +# rather than the patched ones. + import os import sys from eventlet.hubs import hub -from eventlet.patcher import original +from eventlet.patcher import _unmonkey_patch_asyncio_all def is_available(): @@ -32,22 +32,14 @@ def __init__(self): super().__init__() - # Make sure asyncio thread pools use real threads: - if concurrent_imported: - concurrent.futures.thread.threading = original("threading") - concurrent.futures.thread.queue = original("queue") - - # Make sure select/poll/epoll/kqueue are usable by asyncio: - import selectors - selectors.select = original("select") - - # Make sure DNS lookups use normal blocking API (which asyncio will run - # in a thread): - import asyncio.base_events - asyncio.base_events.socket = original("socket") + + # Pre-emptively make sure we're using the right modules: + _unmonkey_patch_asyncio_all() # The presumption is that eventlet is driving the event loop, so we # want a new one we control. + import asyncio + self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.sleep_event = asyncio.Event() @@ -83,7 +75,7 @@ try: os.fstat(fileno) except OSError: - raise ValueError('Invalid file descriptor') + raise ValueError("Invalid file descriptor") already_listening = self.listeners[evtype].get(fileno) is not None listener = super().add(evtype, fileno, cb, tb, mark_as_closed) if not already_listening: @@ -126,6 +118,8 @@ """ Start the ``Hub`` running. See the superclass for details. """ + import asyncio + async def async_run(): if self.running: raise RuntimeError("Already running!") @@ -150,8 +144,7 @@ sleep_time = wakeup_when - self.clock() if sleep_time > 0: try: - await asyncio.wait_for(self.sleep_event.wait(), - sleep_time) + await asyncio.wait_for(self.sleep_event.wait(), sleep_time) except asyncio.TimeoutError: pass self.sleep_event.clear() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/eventlet/patcher.py new/eventlet-0.38.2/eventlet/patcher.py --- old/eventlet-0.38.0/eventlet/patcher.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/eventlet/patcher.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,9 +1,12 @@ from __future__ import annotations + try: import _imp as imp except ImportError: import imp +import importlib import sys + try: # Only for this purpose, it's irrelevant if `os` was already patched. # https://github.com/eventlet/eventlet/pull/661 @@ -14,9 +17,9 @@ import eventlet -__all__ = ['inject', 'import_patched', 'monkey_patch', 'is_monkey_patched'] +__all__ = ["inject", "import_patched", "monkey_patch", "is_monkey_patched"] -__exclude = {'__builtins__', '__file__', '__name__'} +__exclude = {"__builtins__", "__file__", "__name__"} class SysModulesSaver: @@ -70,7 +73,7 @@ name/module pairs is used, which should cover all use cases but may be slower because there are inevitably redundant or unnecessary imports. """ - patched_name = '__patched_module_' + module_name + patched_name = "__patched_module_" + module_name if patched_name in sys.modules: # returning already-patched module so as not to destroy existing # references to patched modules @@ -79,11 +82,12 @@ if not additional_modules: # supply some defaults additional_modules = ( - _green_os_modules() + - _green_select_modules() + - _green_socket_modules() + - _green_thread_modules() + - _green_time_modules()) + _green_os_modules() + + _green_select_modules() + + _green_socket_modules() + + _green_thread_modules() + + _green_time_modules() + ) # _green_MySQLdb()) # enable this after a short baking-in period # after this we are gonna screw with sys.modules, so capture the @@ -103,10 +107,10 @@ # because of the pop operations will change the content of sys.modules # within th loop for imported_module_name in list(sys.modules.keys()): - if imported_module_name.startswith(module_name + '.'): + if imported_module_name.startswith(module_name + "."): sys.modules.pop(imported_module_name, None) try: - module = __import__(module_name, {}, {}, module_name.split('.')[:-1]) + module = __import__(module_name, {}, {}, module_name.split(".")[:-1]) if new_globals is not None: # Update the given globals dictionary with everything from this new module @@ -130,9 +134,8 @@ The only required argument is the name of the module to be imported. """ return inject( - module_name, - None, - *additional_modules + tuple(kw_additional_modules.items())) + module_name, None, *additional_modules + tuple(kw_additional_modules.items()) + ) def patch_function(func, *additional_modules): @@ -144,11 +147,12 @@ if not additional_modules: # supply some defaults additional_modules = ( - _green_os_modules() + - _green_select_modules() + - _green_socket_modules() + - _green_thread_modules() + - _green_time_modules()) + _green_os_modules() + + _green_select_modules() + + _green_socket_modules() + + _green_thread_modules() + + _green_time_modules() + ) def patched(*args, **kw): saver = SysModulesSaver() @@ -159,6 +163,7 @@ return func(*args, **kw) finally: saver.restore() + return patched @@ -169,6 +174,7 @@ patch_function, only the names of the modules need be supplied, and there are no defaults. This is a gross hack; tell your kids not to import inside function bodies!""" + def patched(*args, **kw): saver = SysModulesSaver(module_names) for name in module_names: @@ -177,17 +183,18 @@ return func(*args, **kw) finally: saver.restore() + return patched def original(modname): - """ This returns an unpatched version of a module; this is useful for + """This returns an unpatched version of a module; this is useful for Eventlet itself (i.e. tpool).""" # note that it's not necessary to temporarily install unpatched # versions of all patchable modules during the import of the # module; this is because none of them import each other, except # for threading which imports thread - original_name = '__original_module_' + modname + original_name = "__original_module_" + modname if original_name in sys.modules: return sys.modules.get(original_name) @@ -198,20 +205,20 @@ # some rudimentary dependency checking -- fortunately the modules # we're working on don't have many dependencies so we can just do # some special-casing here - deps = {'threading': '_thread', 'queue': 'threading'} + deps = {"threading": "_thread", "queue": "threading"} if modname in deps: dependency = deps[modname] saver.save(dependency) sys.modules[dependency] = original(dependency) try: - real_mod = __import__(modname, {}, {}, modname.split('.')[:-1]) - if modname in ('Queue', 'queue') and not hasattr(real_mod, '_threading'): + real_mod = __import__(modname, {}, {}, modname.split(".")[:-1]) + if modname in ("Queue", "queue") and not hasattr(real_mod, "_threading"): # tricky hack: Queue's constructor in <2.7 imports # threading on every instantiation; therefore we wrap # it so that it always gets the original threading real_mod.Queue.__init__ = _original_patch_function( - real_mod.Queue.__init__, - 'threading') + real_mod.Queue.__init__, "threading" + ) # save a reference to the unpatched module so it doesn't get lost sys.modules[original_name] = real_mod finally: @@ -223,6 +230,99 @@ already_patched = {} +def _unmonkey_patch_asyncio(unmonkeypatch_refs_to_this_module): + """ + When using asyncio hub, we want the asyncio modules to use the original, + blocking APIs. So un-monkeypatch references to the given module name, e.g. + "select". + """ + to_unpatch = unmonkeypatch_refs_to_this_module + original_module = original(to_unpatch) + + # Lower down for asyncio modules, we will switch their imported modules to + # original ones instead of the green ones they probably have. This won't + # fix "from socket import whatev" but asyncio doesn't seem to do that in + # ways we care about for Python 3.8 to 3.13, with the one exception of + # get_ident() in some older versions. + if to_unpatch == "_thread": + import asyncio.base_futures + + if hasattr(asyncio.base_futures, "get_ident"): + asyncio.base_futures = original_module.get_ident + + # Asyncio uses these for its blocking thread pool: + if to_unpatch in ("threading", "queue"): + try: + import concurrent.futures.thread + except RuntimeError: + # This happens in weird edge cases where asyncio hub is started at + # shutdown. Not much we can do if this happens. + pass + else: + if to_unpatch == "threading": + concurrent.futures.thread.threading = original_module + if to_unpatch == "queue": + concurrent.futures.thread.queue = original_module + + # Patch asyncio modules: + for module_name in [ + "asyncio.base_events", + "asyncio.base_futures", + "asyncio.base_subprocess", + "asyncio.base_tasks", + "asyncio.constants", + "asyncio.coroutines", + "asyncio.events", + "asyncio.exceptions", + "asyncio.format_helpers", + "asyncio.futures", + "asyncio", + "asyncio.locks", + "asyncio.log", + "asyncio.mixins", + "asyncio.protocols", + "asyncio.queues", + "asyncio.runners", + "asyncio.selector_events", + "asyncio.sslproto", + "asyncio.staggered", + "asyncio.streams", + "asyncio.subprocess", + "asyncio.taskgroups", + "asyncio.tasks", + "asyncio.threads", + "asyncio.timeouts", + "asyncio.transports", + "asyncio.trsock", + "asyncio.unix_events", + ]: + try: + module = importlib.import_module(module_name) + except ImportError: + # The list is from Python 3.13, so some modules may not be present + # in older versions of Python: + continue + if getattr(module, to_unpatch, None) is sys.modules[to_unpatch]: + setattr(module, to_unpatch, original_module) + + +def _unmonkey_patch_asyncio_all(): + """ + Unmonkey-patch all referred-to modules in asyncio. + """ + for module_name, _ in sum([ + _green_os_modules(), + _green_select_modules(), + _green_socket_modules(), + _green_thread_modules(), + _green_time_modules(), + _green_builtins(), + _green_subprocess_modules(), + ], []): + _unmonkey_patch_asyncio(module_name) + original("selectors").select = original("select") + + def monkey_patch(**on): """Globally patches certain system modules to be greenthread-friendly. @@ -246,57 +346,68 @@ # the hub calls into monkey-patched modules. eventlet.hubs.get_hub() - accepted_args = {'os', 'select', 'socket', - 'thread', 'time', 'psycopg', 'MySQLdb', - 'builtins', 'subprocess'} + accepted_args = { + "os", + "select", + "socket", + "thread", + "time", + "psycopg", + "MySQLdb", + "builtins", + "subprocess", + } # To make sure only one of them is passed here - assert not ('__builtin__' in on and 'builtins' in on) + assert not ("__builtin__" in on and "builtins" in on) try: - b = on.pop('__builtin__') + b = on.pop("__builtin__") except KeyError: pass else: - on['builtins'] = b + on["builtins"] = b default_on = on.pop("all", None) for k in on.keys(): if k not in accepted_args: - raise TypeError("monkey_patch() got an unexpected " - "keyword argument %r" % k) + raise TypeError( + "monkey_patch() got an unexpected " "keyword argument %r" % k + ) if default_on is None: default_on = True not in on.values() for modname in accepted_args: - if modname == 'MySQLdb': + if modname == "MySQLdb": # MySQLdb is only on when explicitly patched for the moment on.setdefault(modname, False) - if modname == 'builtins': + if modname == "builtins": on.setdefault(modname, False) on.setdefault(modname, default_on) import threading + original_rlock_type = type(threading.RLock()) modules_to_patch = [] for name, modules_function in [ - ('os', _green_os_modules), - ('select', _green_select_modules), - ('socket', _green_socket_modules), - ('thread', _green_thread_modules), - ('time', _green_time_modules), - ('MySQLdb', _green_MySQLdb), - ('builtins', _green_builtins), - ('subprocess', _green_subprocess_modules), + ("os", _green_os_modules), + ("select", _green_select_modules), + ("socket", _green_socket_modules), + ("thread", _green_thread_modules), + ("time", _green_time_modules), + ("MySQLdb", _green_MySQLdb), + ("builtins", _green_builtins), + ("subprocess", _green_subprocess_modules), ]: if on[name] and not already_patched.get(name): modules_to_patch += modules_function() already_patched[name] = True - if on['psycopg'] and not already_patched.get('psycopg'): + if on["psycopg"] and not already_patched.get("psycopg"): try: from eventlet.support import psycopg2_patcher + psycopg2_patcher.make_psycopg_green() - already_patched['psycopg'] = True + already_patched["psycopg"] = True except ImportError: # note that if we get an importerror from trying to # monkeypatch psycopg, we will continually retry it @@ -305,7 +416,7 @@ # tell us whether or not we succeeded pass - _threading = original('threading') + _threading = original("threading") imp.acquire_lock() try: for name, mod in modules_to_patch: @@ -316,13 +427,14 @@ patched_attr = getattr(mod, attr_name, None) if patched_attr is not None: setattr(orig_mod, attr_name, patched_attr) - deleted = getattr(mod, '__deleted__', []) + deleted = getattr(mod, "__deleted__", []) for attr_name in deleted: if hasattr(orig_mod, attr_name): delattr(orig_mod, attr_name) # https://github.com/eventlet/eventlet/issues/592 - if name == 'threading' and register_at_fork: + if name == "threading" and register_at_fork: + def fix_threading_active( _global_dict=_threading.current_thread.__globals__, # alias orig_mod as patched to reflect its new state @@ -332,21 +444,21 @@ _prefork_active = [None] def before_fork(): - _prefork_active[0] = _global_dict['_active'] - _global_dict['_active'] = _patched._active + _prefork_active[0] = _global_dict["_active"] + _global_dict["_active"] = _patched._active def after_fork(): - _global_dict['_active'] = _prefork_active[0] + _global_dict["_active"] = _prefork_active[0] + + register_at_fork(before=before_fork, after_in_parent=after_fork) - register_at_fork( - before=before_fork, - after_in_parent=after_fork) fix_threading_active() finally: imp.release_lock() import importlib._bootstrap - thread = original('_thread') + + thread = original("_thread") # importlib must use real thread locks, not eventlet.Semaphore importlib._bootstrap._thread = thread @@ -355,16 +467,20 @@ # threading.get_ident(). Force the Python implementation of RLock which # calls threading.get_ident() and so is compatible with eventlet. import threading + threading.RLock = threading._PyRLock # Issue #508: Since Python 3.7 queue.SimpleQueue is implemented in C, # causing a deadlock. Replace the C implementation with the Python one. import queue + queue.SimpleQueue = queue._PySimpleQueue # Green existing locks _after_ patching modules, since patching modules # might involve imports that create new locks: - _green_existing_locks(original_rlock_type) + for name, _ in modules_to_patch: + if name == "threading": + _green_existing_locks(original_rlock_type) def is_monkey_patched(module): @@ -375,8 +491,10 @@ module some other way than with the import keyword (including import_patched), this might not be correct about that particular module.""" - return module in already_patched or \ - getattr(module, '__name__', None) in already_patched + return ( + module in already_patched + or getattr(module, "__name__", None) in already_patched + ) def _green_existing_locks(rlock_type): @@ -428,10 +546,13 @@ if remaining_rlocks: import logging + logger = logging.Logger("eventlet") - logger.error("{} RLock(s) were not greened,".format(remaining_rlocks) + - " to fix this error make sure you run eventlet.monkey_patch() " + - "before importing any other modules.") + logger.error( + "{} RLock(s) were not greened,".format(remaining_rlocks) + + " to fix this error make sure you run eventlet.monkey_patch() " + + "before importing any other modules." + ) def _upgrade_instances(container, klass, upgrade, visited=None, old_to_new=None): @@ -492,10 +613,14 @@ setattr(container, k, new) except: import logging + logger = logging.Logger("eventlet") - logger.exception("An exception was thrown while monkey_patching for eventlet. " - "to fix this error make sure you run eventlet.monkey_patch() " - "before importing any other modules.", exc_info=True) + logger.exception( + "An exception was thrown while monkey_patching for eventlet. " + "to fix this error make sure you run eventlet.monkey_patch() " + "before importing any other modules.", + exc_info=True, + ) def _convert_py3_rlock(old, tid): @@ -508,14 +633,16 @@ """ import threading from eventlet.green.thread import allocate_lock + new = threading._PyRLock() if not hasattr(new, "_block") or not hasattr(new, "_owner"): # These will only fail if Python changes its internal implementation of # _PyRLock: raise RuntimeError( - "INTERNAL BUG. Perhaps you are using a major version " + - "of Python that is unsupported by eventlet? Please file a bug " + - "at https://github.com/eventlet/eventlet/issues/new") + "INTERNAL BUG. Perhaps you are using a major version " + + "of Python that is unsupported by eventlet? Please file a bug " + + "at https://github.com/eventlet/eventlet/issues/new" + ) new._block = allocate_lock() acquired = False while old._is_owned(): @@ -532,49 +659,58 @@ def _green_os_modules(): from eventlet.green import os - return [('os', os)] + + return [("os", os)] def _green_select_modules(): from eventlet.green import select - modules = [('select', select)] + + modules = [("select", select)] from eventlet.green import selectors - modules.append(('selectors', selectors)) + + modules.append(("selectors", selectors)) return modules def _green_socket_modules(): from eventlet.green import socket + try: from eventlet.green import ssl - return [('socket', socket), ('ssl', ssl)] + + return [("socket", socket), ("ssl", ssl)] except ImportError: - return [('socket', socket)] + return [("socket", socket)] def _green_subprocess_modules(): from eventlet.green import subprocess - return [('subprocess', subprocess)] + + return [("subprocess", subprocess)] def _green_thread_modules(): from eventlet.green import Queue from eventlet.green import thread from eventlet.green import threading - return [('queue', Queue), ('_thread', thread), ('threading', threading)] + + return [("queue", Queue), ("_thread", thread), ("threading", threading)] def _green_time_modules(): from eventlet.green import time - return [('time', time)] + + return [("time", time)] def _green_MySQLdb(): try: from eventlet.green import MySQLdb - return [('MySQLdb', MySQLdb)] + + return [("MySQLdb", MySQLdb)] except ImportError: return [] @@ -582,7 +718,8 @@ def _green_builtins(): try: from eventlet.green import builtin - return [('builtins', builtin)] + + return [("builtins", builtin)] except ImportError: return [] @@ -597,16 +734,18 @@ """ if srckeys is None: srckeys = source.__all__ - destination.update({ - name: getattr(source, name) - for name in srckeys - if not (name.startswith('__') or name in ignore) - }) + destination.update( + { + name: getattr(source, name) + for name in srckeys + if not (name.startswith("__") or name in ignore) + } + ) if __name__ == "__main__": sys.argv.pop(0) monkey_patch() with open(sys.argv[0]) as f: - code = compile(f.read(), sys.argv[0], 'exec') + code = compile(f.read(), sys.argv[0], "exec") exec(code) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/tests/asyncio_test.py new/eventlet-0.38.2/tests/asyncio_test.py --- old/eventlet-0.38.0/tests/asyncio_test.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/tests/asyncio_test.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,17 +1,20 @@ """Tests for asyncio integration.""" +import pytest + +import eventlet +from eventlet.hubs import get_hub +from eventlet.hubs.asyncio import Hub as AsyncioHub +if not isinstance(get_hub(), AsyncioHub): + pytest.skip("Only works on asyncio hub", allow_module_level=True) + import asyncio from time import time import socket import sys -import pytest - from greenlet import GreenletExit -import eventlet -from eventlet.hubs import get_hub -from eventlet.hubs.asyncio import Hub as AsyncioHub from eventlet.asyncio import spawn_for_awaitable from eventlet.greenthread import getcurrent from eventlet.support import greendns @@ -19,9 +22,6 @@ import tests -if not isinstance(get_hub(), AsyncioHub): - pytest.skip("Only works on asyncio hub", allow_module_level=True) - class CallingAsyncFunctionsFromGreenletsHighLevelTests(_TestBase): """ @@ -298,9 +298,16 @@ tests.run_isolated("asyncio_to_thread.py") -def test_asyncio_does_not_use_greendns(monkeypatch): +def test_asyncio_does_not_use_greendns(): """ ``asyncio`` loops' ``getaddrinfo()`` and ``getnameinfo()`` do not use green DNS. """ tests.run_isolated("asyncio_dns.py") + + +def test_make_sure_monkey_patching_asyncio_is_restricted(): + """ + ``asyncio`` continues to have original, unpatched ``socket`` etc classes. + """ + tests.run_isolated("asyncio_correct_patching.py") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/tests/hub_test.py new/eventlet-0.38.2/tests/hub_test.py --- old/eventlet-0.38.0/tests/hub_test.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/tests/hub_test.py 2020-02-02 01:00:00.000000000 +0100 @@ -326,10 +326,12 @@ once() +@pytest.mark.skipif(sys.platform == "darwin", reason="on macOS using fork() is discouraged") def test_fork(): tests.run_isolated('hub_fork.py') +@pytest.mark.skipif(sys.platform == "darwin", reason="on macOS using fork() is discouraged") def test_fork_simple(): tests.run_isolated('hub_fork_simple.py') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/tests/isolated/asyncio_correct_patching.py new/eventlet-0.38.2/tests/isolated/asyncio_correct_patching.py --- old/eventlet-0.38.0/tests/isolated/asyncio_correct_patching.py 1970-01-01 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/tests/isolated/asyncio_correct_patching.py 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,32 @@ +""" +asyncio submodules continue to have the original, real socket module even after +monkeypatching. +""" + +import sys + + +def assert_correct_patching(): + from eventlet.greenio.base import GreenSocket + import asyncio.selector_events + if asyncio.selector_events.socket.socket is GreenSocket: + raise RuntimeError("Wrong socket class, should've been normal socket.socket") + + import asyncio.selector_events + if asyncio.selector_events.selectors is not sys.modules["__original_module_selectors"]: + raise RuntimeError("Wrong selectors") + + if asyncio.selector_events.selectors.select is not sys.modules["__original_module_select"]: + raise RuntimeError("Wrong select") + + +import eventlet.hubs +eventlet.hubs.get_hub() +assert_correct_patching() + +import eventlet +eventlet.monkey_patch() +assert_correct_patching() + + +print("pass") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/tests/isolated/asyncio_dns.py new/eventlet-0.38.2/tests/isolated/asyncio_dns.py --- old/eventlet-0.38.0/tests/isolated/asyncio_dns.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/tests/isolated/asyncio_dns.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,3 +1,6 @@ +import eventlet +eventlet.monkey_patch() + import asyncio import socket @@ -12,10 +15,6 @@ greendns.resolve = fail greendns.resolver.query = fail -import eventlet - -eventlet.monkey_patch() - async def lookups(): loop = asyncio.get_running_loop() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/tests/isolated/asyncio_to_thread.py new/eventlet-0.38.2/tests/isolated/asyncio_to_thread.py --- old/eventlet-0.38.0/tests/isolated/asyncio_to_thread.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/tests/isolated/asyncio_to_thread.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,8 +1,9 @@ import eventlet +eventlet.monkey_patch() + from eventlet.patcher import original from eventlet.asyncio import spawn_for_awaitable -eventlet.monkey_patch() import asyncio diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/tests/isolated/osthreads.py new/eventlet-0.38.2/tests/isolated/osthreads.py --- old/eventlet-0.38.0/tests/isolated/osthreads.py 1970-01-01 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/tests/isolated/osthreads.py 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,25 @@ +import eventlet +import eventlet.patcher + +eventlet.monkey_patch() + +threading_orig = eventlet.patcher.original("threading") + +EVENTS = [] + + +def os_thread_2(): + eventlet.sleep(0.1) + EVENTS.append(2) + eventlet.sleep(0.1) + EVENTS.append(2) + + +threading_orig.Thread(target=os_thread_2).start() +EVENTS.append(1) +eventlet.sleep(0.05) +EVENTS.append(1) +eventlet.sleep(0.4) +EVENTS.append(3) +if EVENTS == [1, 1, 2, 2, 3]: + print("pass") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/tests/isolated/patcher_existing_locks_preexisting.py new/eventlet-0.38.2/tests/isolated/patcher_existing_locks_preexisting.py --- old/eventlet-0.38.0/tests/isolated/patcher_existing_locks_preexisting.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/tests/isolated/patcher_existing_locks_preexisting.py 2020-02-02 01:00:00.000000000 +0100 @@ -31,6 +31,10 @@ if sys.version_info[:2] > (3, 9): print(unittest.mock.NonCallableMock._lock) print(NS.lock) + # unittest.mock imports asyncio, so clear out asyncio. + for name in list(sys.modules.keys()): + if name.startswith("asyncio"): + del sys.modules[name] eventlet.monkey_patch() ensure_upgraded(NS.lock) ensure_upgraded(NS.NS2.lock) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/tests/isolated/patcher_threading_subclass_done.py new/eventlet-0.38.2/tests/isolated/patcher_threading_subclass_done.py --- old/eventlet-0.38.0/tests/isolated/patcher_threading_subclass_done.py 1970-01-01 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/tests/isolated/patcher_threading_subclass_done.py 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,40 @@ +import queue +import threading + + +class Worker(threading.Thread): + EXIT_SENTINEL = object() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.q = queue.Queue(maxsize=-1) + self.daemon = True + + def run(self): + while True: + task = self.q.get() + if task == self.EXIT_SENTINEL: + break + print(f"Treating task {task}") + # Pretend to work + + def submit(self, job): + self.q.put(job) + + def terminate(self): + self.q.put(self.EXIT_SENTINEL) + self.join() + + +if __name__ == "__main__": + import eventlet + eventlet.patcher.monkey_patch() + + worker = Worker() + assert not worker.is_alive() + worker.start() + assert worker.is_alive() + worker.submit(1) + worker.terminate() + assert not worker.is_alive() + print("pass") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/tests/patcher_test.py new/eventlet-0.38.2/tests/patcher_test.py --- old/eventlet-0.38.0/tests/patcher_test.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/tests/patcher_test.py 2020-02-02 01:00:00.000000000 +0100 @@ -536,3 +536,7 @@ def test_patcher_existing_locks_exception(): tests.run_isolated("patcher_existing_locks_exception.py") + + +def test_patcher_threading_subclass_done(): + tests.run_isolated("patcher_threading_subclass_done.py") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/eventlet-0.38.0/tests/thread_test.py new/eventlet-0.38.2/tests/thread_test.py --- old/eventlet-0.38.0/tests/thread_test.py 2020-02-02 01:00:00.000000000 +0100 +++ new/eventlet-0.38.2/tests/thread_test.py 2020-02-02 01:00:00.000000000 +0100 @@ -8,7 +8,7 @@ from eventlet import patcher from eventlet.green import thread -from tests import LimitedTestCase +from tests import LimitedTestCase, run_isolated class Locals(LimitedTestCase): @@ -122,3 +122,7 @@ lk._at_fork_reinit() assert lk.acquire(blocking=False) assert not lk.acquire(blocking=False) + + +def test_can_use_eventlet_in_os_threads(): + run_isolated("osthreads.py")