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

Reply via email to