Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-aiohappyeyeballs for 
openSUSE:Factory checked in at 2025-03-05 13:39:29
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-aiohappyeyeballs (Old)
 and      /work/SRC/openSUSE:Factory/.python-aiohappyeyeballs.new.19136 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-aiohappyeyeballs"

Wed Mar  5 13:39:29 2025 rev:6 rq:1250024 version:2.4.8

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-aiohappyeyeballs/python-aiohappyeyeballs.changes
  2025-02-11 21:20:47.900765945 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-aiohappyeyeballs.new.19136/python-aiohappyeyeballs.changes
       2025-03-05 13:39:58.627183084 +0100
@@ -1,0 +2,13 @@
+Tue Mar  4 10:07:59 UTC 2025 - Martin Hauke <mar...@gmx.de>
+
+- Update to version 2.4.8
+  Bug Fixes
+  * Close runner up sockets in the event there are multiple winners
+- Update to version 2.4.7
+  Bug Fixes
+  * Resolve warnings when running tests.
+  * Instead of raising SystemExit which causes a RuntimeError,
+    mock out SystemExit to a new exception.
+  * Make sure the event loop is closed in tests.
+
+-------------------------------------------------------------------

Old:
----
  aiohappyeyeballs-2.4.6.tar.gz

New:
----
  aiohappyeyeballs-2.4.8.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-aiohappyeyeballs.spec ++++++
--- /var/tmp/diff_new_pack.X0FVam/_old  2025-03-05 13:39:59.207207416 +0100
+++ /var/tmp/diff_new_pack.X0FVam/_new  2025-03-05 13:39:59.211207584 +0100
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-aiohappyeyeballs
-Version:        2.4.6
+Version:        2.4.8
 Release:        0
 Summary:        Happy Eyeballs for asyncio
 License:        Python-2.0

++++++ aiohappyeyeballs-2.4.6.tar.gz -> aiohappyeyeballs-2.4.8.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiohappyeyeballs-2.4.6/PKG-INFO 
new/aiohappyeyeballs-2.4.8/PKG-INFO
--- old/aiohappyeyeballs-2.4.6/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
+++ new/aiohappyeyeballs-2.4.8/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.3
 Name: aiohappyeyeballs
-Version: 2.4.6
+Version: 2.4.8
 Summary: Happy Eyeballs for asyncio
 License: PSF-2.0
 Author: J. Nick Koston
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiohappyeyeballs-2.4.6/pyproject.toml 
new/aiohappyeyeballs-2.4.8/pyproject.toml
--- old/aiohappyeyeballs-2.4.6/pyproject.toml   1970-01-01 01:00:00.000000000 
+0100
+++ new/aiohappyeyeballs-2.4.8/pyproject.toml   1970-01-01 01:00:00.000000000 
+0100
@@ -1,6 +1,6 @@
 [project]
 name = "aiohappyeyeballs"
-version = "2.4.6"
+version = "2.4.8"
 description = "Happy Eyeballs for asyncio"
 authors = [{ name = "J. Nick Koston", email = "n...@koston.org" }]
 readme = "README.md"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/aiohappyeyeballs-2.4.6/src/aiohappyeyeballs/__init__.py 
new/aiohappyeyeballs-2.4.8/src/aiohappyeyeballs/__init__.py
--- old/aiohappyeyeballs-2.4.6/src/aiohappyeyeballs/__init__.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/aiohappyeyeballs-2.4.8/src/aiohappyeyeballs/__init__.py 1970-01-01 
01:00:00.000000000 +0100
@@ -1,4 +1,4 @@
-__version__ = "2.4.6"
+__version__ = "2.4.8"
 
 from .impl import start_connection
 from .types import AddrInfoType
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/aiohappyeyeballs-2.4.6/src/aiohappyeyeballs/_staggered.py 
new/aiohappyeyeballs-2.4.8/src/aiohappyeyeballs/_staggered.py
--- old/aiohappyeyeballs-2.4.6/src/aiohappyeyeballs/_staggered.py       
1970-01-01 01:00:00.000000000 +0100
+++ new/aiohappyeyeballs-2.4.8/src/aiohappyeyeballs/_staggered.py       
1970-01-01 01:00:00.000000000 +0100
@@ -16,6 +16,8 @@
 
 _T = TypeVar("_T")
 
+RE_RAISE_EXCEPTIONS = (SystemExit, KeyboardInterrupt)
+
 
 def _set_result(wait_next: "asyncio.Future[None]") -> None:
     """Set the result of a future if it is not already done."""
@@ -125,7 +127,7 @@
         """
         try:
             result = await coro_fn()
-        except (SystemExit, KeyboardInterrupt):
+        except RE_RAISE_EXCEPTIONS:
             raise
         except BaseException as e:
             exceptions[this_index] = e
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiohappyeyeballs-2.4.6/src/aiohappyeyeballs/impl.py 
new/aiohappyeyeballs-2.4.8/src/aiohappyeyeballs/impl.py
--- old/aiohappyeyeballs-2.4.6/src/aiohappyeyeballs/impl.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/aiohappyeyeballs-2.4.8/src/aiohappyeyeballs/impl.py     1970-01-01 
01:00:00.000000000 +0100
@@ -2,10 +2,11 @@
 
 import asyncio
 import collections
+import contextlib
 import functools
 import itertools
 import socket
-from typing import List, Optional, Sequence, Union
+from typing import List, Optional, Sequence, Set, Union
 
 from . import _staggered
 from .types import AddrInfoType
@@ -75,15 +76,36 @@
             except (RuntimeError, OSError):
                 continue
     else:  # using happy eyeballs
-        sock, _, _ = await _staggered.staggered_race(
-            (
-                functools.partial(
-                    _connect_sock, current_loop, exceptions, addrinfo, 
local_addr_infos
-                )
-                for addrinfo in addr_infos
-            ),
-            happy_eyeballs_delay,
-        )
+        open_sockets: Set[socket.socket] = set()
+        try:
+            sock, _, _ = await _staggered.staggered_race(
+                (
+                    functools.partial(
+                        _connect_sock,
+                        current_loop,
+                        exceptions,
+                        addrinfo,
+                        local_addr_infos,
+                        open_sockets,
+                    )
+                    for addrinfo in addr_infos
+                ),
+                happy_eyeballs_delay,
+            )
+        finally:
+            # If we have a winner, staggered_race will
+            # cancel the other tasks, however there is a
+            # small race window where any of the other tasks
+            # can be done before they are cancelled which
+            # will leave the socket open. To avoid this problem
+            # we pass a set to _connect_sock to keep track of
+            # the open sockets and close them here if there
+            # are any "runner up" sockets.
+            for s in open_sockets:
+                if s is not sock:
+                    with contextlib.suppress(OSError):
+                        s.close()
+            open_sockets = None  # type: ignore[assignment]
 
     if sock is None:
         all_exceptions = [exc for sub in exceptions for exc in sub]
@@ -130,14 +152,26 @@
     exceptions: List[List[Union[OSError, RuntimeError]]],
     addr_info: AddrInfoType,
     local_addr_infos: Optional[Sequence[AddrInfoType]] = None,
+    open_sockets: Optional[Set[socket.socket]] = None,
 ) -> socket.socket:
-    """Create, bind and connect one socket."""
+    """
+    Create, bind and connect one socket.
+
+    If open_sockets is passed, add the socket to the set of open sockets.
+    Any failure caught here will remove the socket from the set and close it.
+
+    Callers can use this set to close any sockets that are not the winner
+    of all staggered tasks in the result there are runner up sockets aka
+    multiple winners.
+    """
     my_exceptions: List[Union[OSError, RuntimeError]] = []
     exceptions.append(my_exceptions)
     family, type_, proto, _, address = addr_info
     sock = None
     try:
         sock = socket.socket(family=family, type=type_, proto=proto)
+        if open_sockets is not None:
+            open_sockets.add(sock)
         sock.setblocking(False)
         if local_addr_infos is not None:
             for lfamily, _, _, _, laddr in local_addr_infos:
@@ -165,6 +199,8 @@
     except (RuntimeError, OSError) as exc:
         my_exceptions.append(exc)
         if sock is not None:
+            if open_sockets is not None:
+                open_sockets.remove(sock)
             try:
                 sock.close()
             except OSError as e:
@@ -173,6 +209,8 @@
         raise
     except:
         if sock is not None:
+            if open_sockets is not None:
+                open_sockets.remove(sock)
             try:
                 sock.close()
             except OSError as e:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiohappyeyeballs-2.4.6/tests/test_impl.py 
new/aiohappyeyeballs-2.4.8/tests/test_impl.py
--- old/aiohappyeyeballs-2.4.6/tests/test_impl.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/aiohappyeyeballs-2.4.8/tests/test_impl.py       1970-01-01 
01:00:00.000000000 +0100
@@ -1,12 +1,12 @@
 import asyncio
 import socket
 from types import ModuleType
-from typing import Tuple
+from typing import List, Optional, Sequence, Set, Tuple, Union
 from unittest import mock
 
 import pytest
 
-from aiohappyeyeballs import start_connection
+from aiohappyeyeballs import AddrInfoType, _staggered, impl, start_connection
 
 
 def mock_socket_module():
@@ -181,6 +181,75 @@
 
 @pytest.mark.asyncio
 @patch_socket
+async def test_multiple_winners_cleaned_up(
+    m_socket: ModuleType,
+) -> None:
+    loop = asyncio.get_running_loop()
+    finish = loop.create_future()
+
+    def _socket(*args, **kw):
+        return mock.MagicMock(
+            family=socket.AF_INET,
+            type=socket.SOCK_STREAM,
+            proto=socket.IPPROTO_TCP,
+            fileno=mock.MagicMock(return_value=1),
+        )
+
+    async def _connect_sock(
+        loop: asyncio.AbstractEventLoop,
+        exceptions: List[List[Union[OSError, RuntimeError]]],
+        addr_info: AddrInfoType,
+        local_addr_infos: Optional[Sequence[AddrInfoType]] = None,
+        sockets: Optional[Set[socket.socket]] = None,
+    ) -> socket.socket:
+        await finish
+        sock = _socket()
+        assert sockets is not None
+        sockets.add(sock)
+        return sock
+
+    m_socket.socket = _socket  # type: ignore
+    addr_info = [
+        (
+            socket.AF_INET,
+            socket.SOCK_STREAM,
+            socket.IPPROTO_TCP,
+            "",
+            ("107.6.106.82", 80),
+        ),
+        (
+            socket.AF_INET,
+            socket.SOCK_STREAM,
+            socket.IPPROTO_TCP,
+            "",
+            ("107.6.106.83", 80),
+        ),
+        (
+            socket.AF_INET,
+            socket.SOCK_STREAM,
+            socket.IPPROTO_TCP,
+            "",
+            ("107.6.106.84", 80),
+        ),
+        (
+            socket.AF_INET,
+            socket.SOCK_STREAM,
+            socket.IPPROTO_TCP,
+            "",
+            ("107.6.106.85", 80),
+        ),
+    ]
+    with mock.patch.object(impl, "_connect_sock", _connect_sock):
+        task = loop.create_task(
+            start_connection(addr_info, happy_eyeballs_delay=0.0001, 
interleave=0)
+        )
+        await asyncio.sleep(0.1)
+        loop.call_soon(finish.set_result, None)
+        await task
+
+
+@pytest.mark.asyncio
+@patch_socket
 async def test_multiple_addr_success_second_one_happy_eyeballs(
     m_socket: ModuleType,
 ) -> None:
@@ -1652,11 +1721,14 @@
 
 @patch_socket
 @pytest.mark.asyncio
-@pytest.mark.xfail(reason="raises RuntimeError: coroutine ignored 
GeneratorExit")
 async def test_handling_system_exit(
     m_socket: ModuleType,
 ) -> None:
     """Test handling SystemExit."""
+
+    class MockSystemExit(BaseException):
+        """Mock SystemExit."""
+
     mock_socket = mock.MagicMock(
         family=socket.AF_INET,
         type=socket.SOCK_STREAM,
@@ -1674,7 +1746,7 @@
         sock: socket.socket, address: Tuple[str, int, int, int]
     ) -> None:
         create_calls.append(address)
-        raise SystemExit
+        raise MockSystemExit
 
     m_socket.socket = _socket  # type: ignore
     ipv6_addr_info = (
@@ -1716,9 +1788,9 @@
         ),
     ]
     loop = asyncio.get_running_loop()
-    with pytest.raises(SystemExit), mock.patch.object(
+    with pytest.raises(MockSystemExit), mock.patch.object(
         loop, "sock_connect", _sock_connect
-    ):
+    ), mock.patch.object(_staggered, "RE_RAISE_EXCEPTIONS", (MockSystemExit,)):
         await start_connection(
             addr_info,
             happy_eyeballs_delay=0.3,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aiohappyeyeballs-2.4.6/tests/test_staggered.py 
new/aiohappyeyeballs-2.4.8/tests/test_staggered.py
--- old/aiohappyeyeballs-2.4.6/tests/test_staggered.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/aiohappyeyeballs-2.4.8/tests/test_staggered.py  1970-01-01 
01:00:00.000000000 +0100
@@ -84,3 +84,4 @@
         assert excs == [None, None, None, None]
 
     loop.run_until_complete(run())
+    loop.close()

Reply via email to