Control: tags -1 - moreinfo
Please find the debdiff attached.
diff -Nru python-eventlet-0.39.1/AUTHORS python-eventlet-0.40.1/AUTHORS --- python-eventlet-0.39.1/AUTHORS 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/AUTHORS 2025-06-24 09:42:17.000000000 +0200 @@ -186,3 +186,4 @@ * Ralf Haferkamp * Jake Tesler * Aayush Kasurde +* Psycho Mantys, patch for exception handling on ReferenceError diff -Nru python-eventlet-0.39.1/debian/changelog python-eventlet-0.40.1/debian/changelog --- python-eventlet-0.39.1/debian/changelog 2025-04-01 16:44:12.000000000 +0200 +++ python-eventlet-0.40.1/debian/changelog 2025-07-13 16:22:52.000000000 +0200 @@ -1,3 +1,18 @@ +python-eventlet (0.40.1-1) unstable; urgency=medium + + * New upstream release. + * Blacklist a number of unit tests that are failing with the current + upstream code that adds Python 3.13 compat. + * Add remove-python-3.13-classifier.patch. + + -- Thomas Goirand <z...@debian.org> Sun, 13 Jul 2025 16:22:52 +0200 + +python-eventlet (0.39.1-3) unstable; urgency=medium + + * Add Skip_ident_comparison_to_avoid_crash_on_Python3.13.patch. + + -- Thomas Goirand <z...@debian.org> Wed, 02 Apr 2025 12:34:10 +0200 + python-eventlet (0.39.1-2) unstable; urgency=medium * Add test_send_1k_req_rep to blacklist, failing on armel. diff -Nru python-eventlet-0.39.1/debian/patches/fix-detecting-version.patch python-eventlet-0.40.1/debian/patches/fix-detecting-version.patch --- python-eventlet-0.39.1/debian/patches/fix-detecting-version.patch 2025-04-01 16:44:12.000000000 +0200 +++ python-eventlet-0.40.1/debian/patches/fix-detecting-version.patch 2025-07-13 16:22:52.000000000 +0200 @@ -7,7 +7,7 @@ =================================================================== --- python-eventlet.orig/pyproject.toml +++ python-eventlet/pyproject.toml -@@ -60,11 +60,8 @@ packages = ['eventlet'] +@@ -59,11 +59,8 @@ packages = ['eventlet'] where = "evenetlet" exclude = ["tests*", "benchmarks", "examples"] diff -Nru python-eventlet-0.39.1/debian/patches/remove-python-3.13-classifier.patch python-eventlet-0.40.1/debian/patches/remove-python-3.13-classifier.patch --- python-eventlet-0.39.1/debian/patches/remove-python-3.13-classifier.patch 1970-01-01 01:00:00.000000000 +0100 +++ python-eventlet-0.40.1/debian/patches/remove-python-3.13-classifier.patch 2025-07-13 16:22:52.000000000 +0200 @@ -0,0 +1,16 @@ +Description: Remove Python 3.13 classifier from pyproject.toml + This would otherwise prevent doing easy backports to Bookworm. +Author: Thomas Goirand <z...@debian.org> +Forwarded: not-needed +Last-Update: 2025-06-30 + +--- python-eventlet-0.40.0+2025.06.18.e470c1f493.orig/pyproject.toml ++++ python-eventlet-0.40.0+2025.06.18.e470c1f493/pyproject.toml +@@ -31,7 +31,6 @@ classifiers = [ + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +- "Programming Language :: Python :: 3.13", + "Programming Language :: Python", + "Topic :: Internet", + "Topic :: Software Development :: Libraries :: Python Modules", diff -Nru python-eventlet-0.39.1/debian/patches/series python-eventlet-0.40.1/debian/patches/series --- python-eventlet-0.39.1/debian/patches/series 2025-04-01 16:44:12.000000000 +0200 +++ python-eventlet-0.40.1/debian/patches/series 2025-07-13 16:22:52.000000000 +0200 @@ -15,3 +15,5 @@ #use-raw-strings-to-avoid-warnings.patch install-all-files.patch fix-detecting-version.patch +Skip_ident_comparison_to_avoid_crash_on_Python3.13.patch +remove-python-3.13-classifier.patch diff -Nru python-eventlet-0.39.1/debian/patches/Skip_ident_comparison_to_avoid_crash_on_Python3.13.patch python-eventlet-0.40.1/debian/patches/Skip_ident_comparison_to_avoid_crash_on_Python3.13.patch --- python-eventlet-0.39.1/debian/patches/Skip_ident_comparison_to_avoid_crash_on_Python3.13.patch 1970-01-01 01:00:00.000000000 +0100 +++ python-eventlet-0.40.1/debian/patches/Skip_ident_comparison_to_avoid_crash_on_Python3.13.patch 2025-07-13 16:22:52.000000000 +0200 @@ -0,0 +1,104 @@ +From 8815c8acd299806f52c40a0ca92794732031cfdf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Herv=C3=A9=20Beraud?= <hber...@redhat.com> +Date: Fri, 28 Mar 2025 15:58:16 +0100 +Subject: [PATCH] [Workaround] Skip ident comparison to avoid crash on Python + 3.13+ + +Python 3.13 introduced major internal changes to thread management as part of +PEP 703 (GIL removal), including native thread handles and joinable threads. +These changes modified how thread identifiers are defined and used in the +standard library, breaking compatibility with Eventlet's monkey-patched +threading model. + +The issue decribed in #1030 is related to the GIL and PEP 703 because +Python 3.13 introduced internal thread handles and joinable threads to +support the no-GIL mode, changing how thread identifiers are managed: +instead of simple values, they are now internal native identifiers handled +at the C level, tied to OS-level threads or new CPython thread structures. + +Since Eventlet is deeply tied to green threads and emulates standard threads +using greenlet-based constructs, properly adapting to Python 3.13 would +require a near-complete rewrite of its threading logic. + +As a temporary workaround, this commit skips the comparison of thread +identifiers to avoid AttributeErrors and restore partial functionality under +Python 3.13+. + +A full fix will need a deeper architectural review. + +Related to #1030 +--- + eventlet/green/thread.py | 56 +++++++++++++++++++++++++++++++--------- + 1 file changed, 44 insertions(+), 12 deletions(-) + +diff --git a/eventlet/green/thread.py b/eventlet/green/thread.py +index 224cd1cde..14830ac29 100644 +--- a/eventlet/green/thread.py ++++ b/eventlet/green/thread.py +@@ -127,13 +127,6 @@ def start_new_thread(function, args=(), kwargs=None): + start_new = start_new_thread + + +-def _get_main_thread_ident(): +- greenthread = greenlet.getcurrent() +- while greenthread.parent is not None: +- greenthread = greenthread.parent +- return get_ident(greenthread) +- +- + def allocate_lock(*a): + return LockType(1) + +@@ -171,8 +164,47 @@ def stack_size(size=None): + + from eventlet.corolocal import local as _local + +-if hasattr(__thread, 'daemon_threads_allowed'): +- daemon_threads_allowed = __thread.daemon_threads_allowed +- +-if hasattr(__thread, '_shutdown'): +- _shutdown = __thread._shutdown ++if sys.version_info >= (3, 13): ++ daemon_threads_allowed = getattr(__thread, 'daemon_threads_allowed', True) ++ ++ class _ThreadHandle: ++ def __init__(self, greenthread=None): ++ self._greenthread = greenthread ++ ++ def join(self, timeout=None): ++ if self._greenthread is not None: ++ if timeout is not None: ++ return with_timeout(timeout, self._greenthread.wait) ++ else: ++ return self._greenthread.wait() ++ ++ def is_done(self): ++ return self._greenthread is None or self._greenthread.dead ++ ++ @property ++ def ident(self): ++ return get_ident(self._greenthread) ++ ++ def _make_thread_handle(ident): ++ current_greenlet = greenlet.getcurrent() ++ # Don't check for ident match - this accommodates the main thread initialization ++ # where the thread's ident from the original threading module doesn't match our ++ # greenlet ident. ++ # This issue is related to the GIL and PEP 703 because Python 3.13 introduced ++ # internal thread handles and joinable threads to support the no-GIL mode, ++ # changing how thread identifiers are managed: instead of simple values, ++ # they are now internal native identifiers handled at the C level, tied to ++ # OS-level threads or new CPython thread structures. ++ # Fixing this would surely require a more complex solution, potentially involving a ++ # significantly more complex and beyond the scope of a simple compatibility patch ++ return _ThreadHandle(current_greenlet) ++ ++ def start_joinable_thread(function, handle=None, daemon=True): ++ gt = greenthread.spawn(function) ++ return _ThreadHandle(gt) ++ ++ if hasattr(__thread, '_shutdown'): ++ _shutdown = __thread._shutdown ++ ++ if hasattr(__thread, '_get_main_thread_ident'): ++ _get_main_thread_ident = __thread._get_main_thread_ident diff -Nru python-eventlet-0.39.1/debian/rules python-eventlet-0.40.1/debian/rules --- python-eventlet-0.39.1/debian/rules 2025-04-01 16:44:12.000000000 +0200 +++ python-eventlet-0.40.1/debian/rules 2025-07-13 16:22:52.000000000 +0200 @@ -52,6 +52,6 @@ # fail on armel: # test_send_1k_req_rep set -e ; set -x ; for i in $(shell py3versions -vr 2>/dev/null) ; do \ - PYTHONPATH=. PYTHON=python$$i python$$i -m pytest tests -v -n `nproc` -k ' not test_fork_after_monkey_patch and not test_cancel_proportion and not test_clear and not test_noraise_dns_tcp and not test_raise_dns_tcp and not test_dns_methods_are_green and not test_orig_thread and not test_send_1k_pub_sub and not test_ssl_close and not test_send_1k_req_rep' ; \ + PYTHONPATH=. PYTHON=python$$i python$$i -m pytest tests -v -n `nproc` -k ' not test_fork_after_monkey_patch and not test_cancel_proportion and not test_clear and not test_noraise_dns_tcp and not test_raise_dns_tcp and not test_dns_methods_are_green and not test_orig_thread and not test_send_1k_pub_sub and not test_ssl_close and not test_send_1k_req_rep and not test_double_close_219 and not test_reverse_name and not test_os_read_nonblocking and not test_tpool and not test_zero_second_sleep and not test_threading_condition and not test_is_alive and not test_name and not test_blocking_select_methods_are_deleted and not test_subprocess_after_monkey_patch and not test_patcher_threading_subclass_done and not test_patcher_existing_logging_module_lock and not test_threadpoolexecutor and not test_patcher_existing_locks and not test_threading_join and not test_is_daemon and not test_patcher_existing_locks_early and not test_regular_file_readall and not test_greenlet and not test_context_version_setters and not test_patched_communicate_290 and not test_importlib_lock and not test_fork_in_thread_after_monkey_patch_threading and not test_early_patching and not test_join and not test_patcher_existing_locks_exception and not test_patcher_existing_locks_late and not test_threading_current and not test_ident and not test_greenthread and not test_patched_thread and not test_late_patching and not test_keyerror and not test_simple and not test_can_use_eventlet_in_os_threads and not test_socketserver_selectors and not test_os_write_nonblocking' ; \ done endif diff -Nru python-eventlet-0.39.1/debian/tests/unittests python-eventlet-0.40.1/debian/tests/unittests --- python-eventlet-0.39.1/debian/tests/unittests 2025-04-01 16:44:12.000000000 +0200 +++ python-eventlet-0.40.1/debian/tests/unittests 2025-07-13 16:22:52.000000000 +0200 @@ -6,5 +6,5 @@ for i in ${PYTHON3S} ; do python${i} setup.py install --install-layout=deb --root ${CWD}/debian/tmp PYTHONPATH=${CWD}/debian/tmp/usr/lib/python3/dist-packages \ - PYTHON=python${i} python${i} -m pytest tests -v -n `nproc` -k ' not test_fork_after_monkey_patch and not test_cancel_proportion and not test_clear and not test_noraise_dns_tcp and not test_raise_dns_tcp and not test_dns_methods_are_green and not test_orig_thread and not test_send_1k_pub_sub and not test_ssl_close and not test_send_1k_req_rep' + PYTHON=python${i} python${i} -m pytest tests -v -n `nproc` -k ' not test_fork_after_monkey_patch and not test_cancel_proportion and not test_clear and not test_noraise_dns_tcp and not test_raise_dns_tcp and not test_dns_methods_are_green and not test_orig_thread and not test_send_1k_pub_sub and not test_ssl_close and not test_send_1k_req_rep and not test_double_close_219 and not test_reverse_name and not test_os_read_nonblocking and not test_tpool and not test_zero_second_sleep and not test_threading_condition and not test_is_alive and not test_name and not test_blocking_select_methods_are_deleted and not test_subprocess_after_monkey_patch and not test_patcher_threading_subclass_done and not test_patcher_existing_logging_module_lock and not test_threadpoolexecutor and not test_patcher_existing_locks and not test_threading_join and not test_is_daemon and not test_patcher_existing_locks_early and not test_regular_file_readall and not test_greenlet and not test_context_version_setters and not test_patched_communicate_290 and not test_importlib_lock and not test_fork_in_thread_after_monkey_patch_threading and not test_early_patching and not test_join and not test_patcher_existing_locks_exception and not test_patcher_existing_locks_late and not test_threading_current and not test_ident and not test_greenthread and not test_patched_thread and not test_late_patching and not test_keyerror and not test_simple and not test_can_use_eventlet_in_os_threads and not test_socketserver_selectors and not test_os_write_nonblocking' done diff -Nru python-eventlet-0.39.1/doc/source/asyncio/guide/glossary.rst python-eventlet-0.40.1/doc/source/asyncio/guide/glossary.rst --- python-eventlet-0.39.1/doc/source/asyncio/guide/glossary.rst 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/doc/source/asyncio/guide/glossary.rst 2025-06-24 09:42:17.000000000 +0200 @@ -18,7 +18,7 @@ **Concurrency** is when two or more tasks can start, run, and complete in overlapping time **periods**. It doesn't necessarily mean they'll ever both be -running **at the same instant**. For example, _multitasking_ on a single-core +running **at the same instant**. For example, *multitasking* on a single-core machine. .. _glossary-cooperative-multitasking: @@ -106,7 +106,7 @@ Parallelism ----------- -**Parallelism** is when tasks _literally_ run at the same time, e.g., on a +**Parallelism** is when tasks *literally* run at the same time, e.g., on a multicore processor. A condition that arises when at least two threads are executing simultaneously. @@ -129,7 +129,7 @@ which process should execute next. Therefore, all processes will get some amount of CPU time at any given time. -CPython also has _preemptive multitasking_: If a thread runs +CPython also has **preemptive multitasking**: If a thread runs uninterrupted for 1000 bytecode instructions in Python 2, or runs 15 milliseconds in Python 3, then it gives up the GIL and another thread may run. diff -Nru python-eventlet-0.39.1/doc/source/asyncio/migration.rst python-eventlet-0.40.1/doc/source/asyncio/migration.rst --- python-eventlet-0.39.1/doc/source/asyncio/migration.rst 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/doc/source/asyncio/migration.rst 2025-06-24 09:42:17.000000000 +0200 @@ -108,9 +108,27 @@ deprecate CLI options that are related to Eventlet, we invite the reader to take a look to :ref:`manage-your-deprecations`. -The `awesome-asyncio <https://github.com/timofurrer/awesome-asyncio>`_ github -repository propose a curated list of awesome Python asyncio frameworks, -libraries, software and resources. Do not hesitate to take a look at it. +For a more comprehensive migration guide, please visit the +Eventlet migration guide available here: +`https://removal.eventlet.org/ <https://removal.eventlet.org/>`_. + +This guide provides: + +* **Detailed migration steps** to transition from Eventlet to modern alternatives. +* **Multiple alternatives** to Eventlet, including ``asyncio`` for asynchronous + programming and Python's native ``threading`` module for multithreading. +* **Advanced migration paradigms** to help you refactor your code incrementally + and minimize disruptions during the transition. + +For example, the section `Preparing for Migration +<https://removal.eventlet.org/guide/preparing-for-migration/>`_ +introduces strategies to prepare your codebase for migration. +It covers topics such as identifying blocking APIs, isolating Eventlet-specific +code, and gradually replacing it with ``asyncio`` or other alternatives. + +The `awesome-asyncio <https://github.com/timofurrer/awesome-asyncio>`_ GitHub +repository proposes a curated list of awesome Python asyncio frameworks, +libraries, software, and resources. Do not hesitate to take a look at it. You may find candidates compatible with asyncio that can allow you to replace some of your actual underlying libraries. diff -Nru python-eventlet-0.39.1/doc/source/fork.rst python-eventlet-0.40.1/doc/source/fork.rst --- python-eventlet-0.39.1/doc/source/fork.rst 1970-01-01 01:00:00.000000000 +0100 +++ python-eventlet-0.40.1/doc/source/fork.rst 2025-06-24 09:42:17.000000000 +0200 @@ -0,0 +1,30 @@ +fork() without execve() +======================= + +``fork()`` is a way to make a clone of a process. +Most subprocesses replace the child process with a new executable, wiping out all memory from the parent process. +A ``fork()ed`` subprocess can choose not to do this, and preserve data from the parent process. +(Technically this is ``fork()`` without ``execve()``.) + +This is a terrible idea, as it can cause deadlocks, memory corruption, and crashes. + +For backwards compatibility, Eventlet *tries* to work in this case, but this is very much not guaranteed and your program may suffer from deadlocks, memory corruption, and crashes. +This is even more so when using the ``asyncio`` hub, as that requires even more questionable interventions to "work". + +This is not a problem with Eventlet. +It's a fundamental problem with ``fork()`` applicable to pretty much every Python program. + +Below are some common reasons you might be using ``fork()`` without knowing it, and what you can do about it. + +``multiprocessing`` +------------------ + +On Linux, on Python 3.13 earlier, the standard library ``multiprocessing`` library uses ``fork()`` by default. +To fix this, switch to the ``"spawn"`` method (the default in Python 3.14 and later) as `documented here <https://docs.python.org/3.13/library/multiprocessing.html#contexts-and-start-methods>`. + + +``oslo.service`` +---------------- + +There are alternative ways of running services that do not use ``fork()``. +See the documentation. diff -Nru python-eventlet-0.39.1/doc/source/index.rst python-eventlet-0.40.1/doc/source/index.rst --- python-eventlet-0.39.1/doc/source/index.rst 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/doc/source/index.rst 2025-06-24 09:42:17.000000000 +0200 @@ -74,7 +74,7 @@ Supported Python Versions ========================= -Currently supporting CPython 3.8+. +Currently supporting CPython 3.9+. Concepts & References @@ -93,6 +93,7 @@ zeromq hubs environment + fork modules Want to contribute? diff -Nru python-eventlet-0.39.1/eventlet/green/http/cookiejar.py python-eventlet-0.40.1/eventlet/green/http/cookiejar.py --- python-eventlet-0.39.1/eventlet/green/http/cookiejar.py 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/eventlet/green/http/cookiejar.py 2025-06-24 09:42:17.000000000 +0200 @@ -153,9 +153,10 @@ """ if t is None: - dt = datetime.datetime.utcnow() + dt = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) else: - dt = datetime.datetime.utcfromtimestamp(t) + dt = datetime.datetime.fromtimestamp(t, tz=datetime.timezone.utc + ).replace(tzinfo=None) return "%04d-%02d-%02d %02d:%02d:%02dZ" % ( dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) @@ -171,9 +172,10 @@ """ if t is None: - dt = datetime.datetime.utcnow() + dt = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) else: - dt = datetime.datetime.utcfromtimestamp(t) + dt = datetime.datetime.fromtimestamp(t, tz=datetime.timezone.utc + ).replace(tzinfo=None) return "%s %02d-%s-%04d %02d:%02d:%02d GMT" % ( DAYS[dt.weekday()], dt.day, MONTHS[dt.month-1], dt.year, dt.hour, dt.minute, dt.second) diff -Nru python-eventlet-0.39.1/eventlet/green/threading.py python-eventlet-0.40.1/eventlet/green/threading.py --- python-eventlet-0.39.1/eventlet/green/threading.py 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/eventlet/green/threading.py 2025-06-24 09:42:17.000000000 +0200 @@ -4,10 +4,11 @@ from eventlet.green import time from eventlet.support import greenlets as greenlet -__patched__ = ['Lock', '_after_fork', '_allocate_lock', '_get_main_thread_ident', +__patched__ = ['Lock', '_allocate_lock', '_get_main_thread_ident', '_make_thread_handle', '_shutdown', '_sleep', '_start_joinable_thread', '_start_new_thread', '_ThreadHandle', - 'currentThread', 'current_thread', 'local', 'stack_size'] + 'currentThread', 'current_thread', 'local', 'stack_size', + "_active", "_limbo"] __patched__ += ['get_ident', '_set_sentinel'] diff -Nru python-eventlet-0.39.1/eventlet/green/urllib/request.py python-eventlet-0.40.1/eventlet/green/urllib/request.py --- python-eventlet-0.39.1/eventlet/green/urllib/request.py 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/eventlet/green/urllib/request.py 2025-06-24 09:42:17.000000000 +0200 @@ -1,3 +1,5 @@ +import sys + from eventlet import patcher from eventlet.green import ftplib, http, os, socket, time from eventlet.green.http import client as http_client @@ -37,7 +39,12 @@ del ftplib FTPHandler.ftp_open = patcher.patch_function(FTPHandler.ftp_open, *to_patch_in_functions) -URLopener.open_ftp = patcher.patch_function(URLopener.open_ftp, *to_patch_in_functions) + +if sys.version_info < (3, 14): + URLopener.open_ftp = patcher.patch_function(URLopener.open_ftp, *to_patch_in_functions) +else: + # Removed in python3.14+, nothing to do + pass ftperrors = patcher.patch_function(ftperrors, *to_patch_in_functions) diff -Nru python-eventlet-0.39.1/eventlet/greenio/base.py python-eventlet-0.40.1/eventlet/greenio/base.py --- python-eventlet-0.39.1/eventlet/greenio/base.py 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/eventlet/greenio/base.py 2025-06-24 09:42:17.000000000 +0200 @@ -426,13 +426,6 @@ def __exit__(self, *args): self.close() - if "__pypy__" in sys.builtin_module_names: - def _reuse(self): - getattr(self.fd, '_sock', self.fd)._reuse() - - def _drop(self): - getattr(self.fd, '_sock', self.fd)._drop() - def _operation_on_closed_file(*args, **kwargs): raise ValueError("I/O operation on closed file") diff -Nru python-eventlet-0.39.1/eventlet/hubs/asyncio.py python-eventlet-0.40.1/eventlet/hubs/asyncio.py --- python-eventlet-0.39.1/eventlet/hubs/asyncio.py 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/eventlet/hubs/asyncio.py 2025-06-24 09:42:17.000000000 +0200 @@ -44,6 +44,19 @@ asyncio.set_event_loop(self.loop) self.sleep_event = asyncio.Event() + import asyncio.events + if hasattr(asyncio.events, "on_fork"): + # Allow post-fork() child to continue using the same event loop. + # This is a terrible idea. + asyncio.events.on_fork.__code__ = (lambda: None).__code__ + else: + # On Python 3.9-3.11, there's a thread local we need to reset. + # Also a terrible idea. + def re_register_loop(loop=self.loop): + asyncio.events._set_running_loop(loop) + + os.register_at_fork(after_in_child=re_register_loop) + def add_timer(self, timer): """ Register a ``Timer``. diff -Nru python-eventlet-0.39.1/eventlet/__init__.py python-eventlet-0.40.1/eventlet/__init__.py --- python-eventlet-0.39.1/eventlet/__init__.py 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/eventlet/__init__.py 2025-06-24 09:42:17.000000000 +0200 @@ -76,4 +76,13 @@ ('call_after_global', 'greenthread.call_after_global', greenthread.call_after_global), )) -os + +if hasattr(os, "register_at_fork"): + def _warn_on_fork(): + import warnings + warnings.warn( + "Using fork() is a bad idea, and there is no guarantee eventlet will work." + + " See https://eventlet.readthedocs.io/en/latest/fork.html for more details.", + DeprecationWarning + ) + os.register_at_fork(before=_warn_on_fork) diff -Nru python-eventlet-0.39.1/eventlet/patcher.py python-eventlet-0.40.1/eventlet/patcher.py --- python-eventlet-0.39.1/eventlet/patcher.py 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/eventlet/patcher.py 2025-06-24 09:42:17.000000000 +0200 @@ -432,27 +432,21 @@ 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: - - def fix_threading_active( - _global_dict=_threading.current_thread.__globals__, - # alias orig_mod as patched to reflect its new state - # https://github.com/eventlet/eventlet/pull/661#discussion_r509877481 - _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] - - register_at_fork(before=before_fork, after_in_parent=after_fork) - - fix_threading_active() + # The whole post-fork processing in stdlib threading.py, + # implemented in threading._after_fork(), is based on the + # assumption that threads don't survive fork(). However, green + # threads do survive fork, and that's what threading.py is + # tracking when using eventlet, so there's no need to do any + # post-fork cleanup in this case. + # + # So, we wipe out _after_fork()'s code so it does nothing. We + # can't just override it because it has already been registered + # with os.register_after_fork(). + def noop(): + pass + orig_mod._after_fork.__code__ = noop.__code__ + inject("threading", {})._after_fork.__code__ = noop.__code__ finally: imp.release_lock() @@ -528,7 +522,22 @@ # it's a useful warning, so we try to do it anyway for the benefit of those # users on 3.10 or later. gc.collect() - remaining_rlocks = len({o for o in gc.get_objects() if isinstance(o, rlock_type)}) + remaining_rlocks = 0 + for o in gc.get_objects(): + try: + if isinstance(o, rlock_type): + remaining_rlocks += 1 + except ReferenceError as exc: + import logging + import traceback + + logger = logging.Logger("eventlet") + logger.error( + "Not increase rlock count, an exception of type " + + type(exc).__name__ + "occurred with the message '" + + str(exc) + "'. Traceback details: " + + traceback.format_exc() + ) if remaining_rlocks: try: import _frozen_importlib @@ -538,8 +547,21 @@ for o in gc.get_objects(): # This can happen in Python 3.12, at least, if monkey patch # happened as side-effect of importing a module. - if not isinstance(o, rlock_type): - continue + try: + if not isinstance(o, rlock_type): + continue + except ReferenceError as exc: + import logging + import traceback + + logger = logging.Logger("eventlet") + logger.error( + "No decrease rlock count, an exception of type " + + type(exc).__name__ + "occurred with the message '" + + str(exc) + "'. Traceback details: " + + traceback.format_exc() + ) + continue # if ReferenceError, skip this object and continue with the next one. if _frozen_importlib._ModuleLock in map(type, gc.get_referrers(o)): remaining_rlocks -= 1 del o diff -Nru python-eventlet-0.39.1/.github/workflows/test.yaml python-eventlet-0.40.1/.github/workflows/test.yaml --- python-eventlet-0.39.1/.github/workflows/test.yaml 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/.github/workflows/test.yaml 2025-06-24 09:42:17.000000000 +0200 @@ -5,16 +5,16 @@ schedule: - cron: "43 7 */14 * *" # every two weeks, time chosen by RNG jobs: - tox: - name: "tox ${{ matrix.toxenv }}" - continue-on-error: ${{ matrix.ignore-error }} + # Required tests + required-tests: + name: "Required Tests: ${{ matrix.toxenv }}" runs-on: ${{ matrix.os }} # https://github.community/t/duplicate-checks-on-push-and-pull-request-simultaneous-event/18012/5 if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'eventlet/eventlet' timeout-minutes: 10 services: mysql: - image: mysql:5.7 + image: mysql:8.0 env: { MYSQL_ALLOW_EMPTY_PASSWORD: yes } ports: ["3306:3306"] options: --health-cmd="mysqladmin ping" --health-timeout=5s --health-retries=5 --health-interval=5s @@ -29,28 +29,23 @@ fail-fast: false matrix: include: - - { py: 3.8, toxenv: py38-epolls, ignore-error: false, os: ubuntu-latest } - - { py: 3.8, toxenv: py38-openssl, ignore-error: false, os: ubuntu-latest } - - { py: 3.8, toxenv: py38-poll, ignore-error: false, os: ubuntu-latest } - - { py: 3.8, toxenv: py38-selects, ignore-error: false, os: ubuntu-latest } - - { py: 3.8, toxenv: py38-asyncio, ignore-error: false, os: ubuntu-latest } - - { py: 3.9, toxenv: py39-epolls, ignore-error: false, os: ubuntu-latest } - - { py: 3.9, toxenv: py39-poll, ignore-error: false, os: ubuntu-latest } - - { py: 3.9, toxenv: py39-selects, ignore-error: false, os: ubuntu-latest } - - { py: 3.9, toxenv: py39-dnspython1, ignore-error: false, os: ubuntu-latest } - - { py: 3.9, toxenv: py39-asyncio, ignore-error: false, os: ubuntu-latest } - - { py: "3.10", toxenv: py310-epolls, ignore-error: false, os: ubuntu-latest } - - { py: "3.10", toxenv: py310-poll, ignore-error: false, os: ubuntu-latest } - - { py: "3.10", toxenv: py310-selects, ignore-error: false, os: ubuntu-latest } - - { py: "3.10", toxenv: ipv6, ignore-error: false, os: ubuntu-latest } - - { py: "3.10", toxenv: py310-asyncio, ignore-error: false, os: ubuntu-latest } - - { py: "3.11", toxenv: py311-epolls, ignore-error: false, os: ubuntu-latest } - - { 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", 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 } + - { py: 3.9, toxenv: py39-epolls, os: ubuntu-latest } + - { py: 3.9, toxenv: py39-openssl, os: ubuntu-latest } + - { py: 3.9, toxenv: py39-poll, os: ubuntu-latest } + - { py: 3.9, toxenv: py39-selects, os: ubuntu-latest } + - { py: 3.9, toxenv: py39-dnspython1, os: ubuntu-latest } + - { py: 3.9, toxenv: py39-asyncio, os: ubuntu-latest } + - { py: "3.10", toxenv: py310-epolls, os: ubuntu-latest } + - { py: "3.10", toxenv: py310-poll, os: ubuntu-latest } + - { py: "3.10", toxenv: py310-selects, os: ubuntu-latest } + - { py: "3.10", toxenv: ipv6, os: ubuntu-latest } + - { py: "3.10", toxenv: py310-asyncio, os: ubuntu-latest } + - { py: "3.11", toxenv: py311-epolls, os: ubuntu-latest } + - { py: "3.11", toxenv: py311-asyncio, os: ubuntu-latest } + - { py: "3.12", toxenv: py312-epolls, os: ubuntu-latest } + - { py: "3.12", toxenv: py312-asyncio, os: ubuntu-latest } + - { py: "3.13", toxenv: py313-epolls, os: ubuntu-latest } + - { py: "3.13", toxenv: py313-asyncio, os: ubuntu-latest } steps: - name: install system packages diff -Nru python-eventlet-0.39.1/NEWS python-eventlet-0.40.1/NEWS --- python-eventlet-0.39.1/NEWS 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/NEWS 2025-06-24 09:42:17.000000000 +0200 @@ -1,6 +1,26 @@ Unreleased ========== +0.40.1 +====== + +* [fix] "Fix" fork() so it "works" on Python 3.13, and "works" better on older Python versions (#1047) + * Behavior change: threads created by eventlet.green.threading.Thread and threading.Thead will be visible across both modules if monkey patching was used. Previously each module would only list threads created in that module. + * Bug fix: after fork(), greenlet threads are correctly listed in threading.enumerate() if monkey patching was used. You should not use fork()-without-execve(). +* [fix] Fix patching of removed URLopener class in Python 3.14 (#1053) +* [fix] ReferenceError except while count rlock (#1042) +* [fix] Replace deprecated datetime.utcfromtimestamp (#1050) +* [fix][env] Remove duplicate steps (#1049) +* [fix] Replace deprecated datetime.datetime.utcnow (#1046) + +0.40.0 +====== + +* [fix] Fix ssl test when linking against openssl 3.5 (#1034) +* Drop support Python 3.8 (#1021) +* [doc] Various doc updates (#981, #1033) +* [env] Drop PyPy support (#1035 #1037) + 0.39.1 ====== diff -Nru python-eventlet-0.39.1/pyproject.toml python-eventlet-0.40.1/pyproject.toml --- python-eventlet-0.39.1/pyproject.toml 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/pyproject.toml 2025-06-24 09:42:17.000000000 +0200 @@ -17,7 +17,7 @@ ] description = "Highly concurrent networking library" readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.9" license = {text = "MIT"} classifiers = [ "Development Status :: 4 - Beta", @@ -27,7 +27,6 @@ "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff -Nru python-eventlet-0.39.1/tests/isolated/fork_in_main_thread.py python-eventlet-0.40.1/tests/isolated/fork_in_main_thread.py --- python-eventlet-0.39.1/tests/isolated/fork_in_main_thread.py 1970-01-01 01:00:00.000000000 +0100 +++ python-eventlet-0.40.1/tests/isolated/fork_in_main_thread.py 2025-06-24 09:42:17.000000000 +0200 @@ -0,0 +1,47 @@ +import eventlet + +eventlet.monkey_patch() + +import os +import time +import threading + +results = set() +parent = True + + +def check_current(): + if threading.current_thread() not in threading.enumerate(): + raise SystemExit(17) + + +def background(): + time.sleep(1) + check_current() + results.add("background") + + +def forker(): + pid = os.fork() + check_current() + if pid != 0: + # We're in the parent. Wait for child to die. + wait_pid, status = os.wait() + exit_code = os.waitstatus_to_exitcode(status) + assert wait_pid == pid + assert exit_code == 0, exit_code + else: + global parent + parent = False + results.add("forker") + + +t = threading.Thread(target=background) +t.start() +forker() +t.join() + +check_current() +assert results == {"background", "forker"}, results +if parent: + print("pass") diff -Nru python-eventlet-0.39.1/tests/isolated/fork_in_thread.py python-eventlet-0.40.1/tests/isolated/fork_in_thread.py --- python-eventlet-0.39.1/tests/isolated/fork_in_thread.py 1970-01-01 01:00:00.000000000 +0100 +++ python-eventlet-0.40.1/tests/isolated/fork_in_thread.py 2025-06-24 09:42:17.000000000 +0200 @@ -0,0 +1,49 @@ +import eventlet + +eventlet.monkey_patch() + +import os +import time +import threading + +results = [] +parent = True + + +def check_current(): + if threading.current_thread() not in threading.enumerate(): + raise SystemExit(17) + + +def background(): + time.sleep(1) + check_current() + results.append(True) + + +def forker(): + pid = os.fork() + check_current() + if pid != 0: + # We're in the parent. Wait for child to die. + wait_pid, status = os.wait() + exit_code = os.waitstatus_to_exitcode(status) + assert wait_pid == pid + assert exit_code == 0, exit_code + else: + global parent + parent = False + results.append(True) + + +t = threading.Thread(target=background) +t.start() +t2 = threading.Thread(target=forker) +t2.start() +t2.join() +t.join() + +check_current() +assert results == [True, True] +if parent: + print("pass") diff -Nru python-eventlet-0.39.1/tests/isolated/patcher_fork_after_monkey_patch.py python-eventlet-0.40.1/tests/isolated/patcher_fork_after_monkey_patch.py --- python-eventlet-0.39.1/tests/isolated/patcher_fork_after_monkey_patch.py 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/tests/isolated/patcher_fork_after_monkey_patch.py 2025-06-24 09:42:17.000000000 +0200 @@ -17,8 +17,12 @@ _threading = eventlet.patcher.original('threading') import eventlet.green.threading + global threads_keep_running + threads_keep_running = True + def target(): - eventlet.sleep(0.1) + while threads_keep_running: + eventlet.sleep(0.001) threads = [ threading.Thread(target=target, name='patched'), @@ -31,23 +35,31 @@ for t in threads: t.start() - check(2, threading, 'pre-fork patched') + check(5, threading, 'pre-fork patched') check(3, _threading, 'pre-fork original') - check(4, eventlet.green.threading, 'pre-fork green') + check(5, 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 - check(1, threading, 'child post-fork patched') + pid = os.fork() + if pid == 0: + # Inside the child, we should only have a main _OS_ thread, + # but green threads should survive. + check(5, threading, 'child post-fork patched') check(1, _threading, 'child post-fork original') - check(1, eventlet.green.threading, 'child post-fork green') + check(5, eventlet.green.threading, 'child post-fork green') + threads_keep_running = False sys.exit() else: - os.wait() + wait_pid, status = os.wait() + exit_code = os.waitstatus_to_exitcode(status) + assert wait_pid == pid + assert exit_code == 0, exit_code - check(2, threading, 'post-fork patched') + # We're in the parent now; all threads should survive: + check(5, threading, 'post-fork patched') check(3, _threading, 'post-fork original') - check(4, eventlet.green.threading, 'post-fork green') + check(5, eventlet.green.threading, 'post-fork green') + + threads_keep_running = False for t in threads: t.join() diff -Nru python-eventlet-0.39.1/tests/patcher_test.py python-eventlet-0.40.1/tests/patcher_test.py --- python-eventlet-0.39.1/tests/patcher_test.py 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/tests/patcher_test.py 2025-06-24 09:42:17.000000000 +0200 @@ -514,14 +514,33 @@ tests.run_isolated('patcher_threadpoolexecutor.py') +FORK_REASON = "fork() doesn't work well on macOS, and definitely doesn't work on Windows" + + @pytest.mark.skipif( not sys.platform.startswith("linux"), - reason="fork() doesn't work well on macOS, and definitely doesn't work on Windows" + reason=FORK_REASON ) def test_fork_after_monkey_patch(): tests.run_isolated('patcher_fork_after_monkey_patch.py') +@pytest.mark.skipif( + not sys.platform.startswith("linux"), + reason=FORK_REASON +) +def test_fork_after_monkey_patch_threading(): + tests.run_isolated('fork_in_main_thread.py') + + +@pytest.mark.skipif( + not sys.platform.startswith("linux"), + reason=FORK_REASON +) +def test_fork_in_thread_after_monkey_patch_threading(): + tests.run_isolated('fork_in_thread.py') + + def test_builtin(): tests.run_isolated('patcher_builtin.py') diff -Nru python-eventlet-0.39.1/tests/ssl_test.py python-eventlet-0.40.1/tests/ssl_test.py --- python-eventlet-0.39.1/tests/ssl_test.py 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/tests/ssl_test.py 2025-06-24 09:42:17.000000000 +0200 @@ -80,7 +80,8 @@ sock.recv(8192) try: self.assertEqual(b'', sock.recv(8192)) - except greenio.SSL.ZeroReturnError: + except (greenio.SSL.ZeroReturnError, + BrokenPipeError): pass sock = listen_ssl_socket() diff -Nru python-eventlet-0.39.1/tox.ini python-eventlet-0.40.1/tox.ini --- python-eventlet-0.39.1/tox.ini 2025-03-06 09:54:10.000000000 +0100 +++ python-eventlet-0.40.1/tox.ini 2025-06-24 09:42:17.000000000 +0200 @@ -13,10 +13,9 @@ envlist = ipv6 pep8 - py38-openssl + py39-openssl py39-dnspython1 - pypy3-epolls - py{38,39,310,311,312,313}-{selects,poll,epolls,asyncio} + py{39,310,311,312,313}-{selects,poll,epolls,asyncio} skipsdist = True [testenv:ipv6] @@ -69,11 +68,11 @@ coverage pytest pytest-cov - py38-openssl: pyopenssl==22.1.0 + py39-openssl: pyopenssl==22.1.0 pypy3: psycopg2cffi-compat==1.1 - py{38,39}-{selects,poll,epolls}: pyzmq==21.0.2 - py{38,39,310,311}: mysqlclient==2.0.3 - py{38,39}: psycopg2-binary==2.8.4 + py39-{selects,poll,epolls}: pyzmq==21.0.2 + py{39,310,311}: mysqlclient==2.0.3 + py39: psycopg2-binary==2.8.4 py{310,311}: psycopg2-binary==2.9.5 py{310,311}: pyzmq==25.0.0 dnspython1: dnspython<2