commit:     fbaaa4a733aaadc2744b656527756ac4e2b7ab58
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Thu Feb 22 06:47:33 2024 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Thu Feb 22 07:28:38 2024 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=fbaaa4a7

socks5: Use real asyncio.run

Use real asyncio.run to demonstrate that it is compatible with
portage internals. Since the socks5 ProxyManager uses the
process.spawn function, the internal _running_loop function
needs to return the correct loop for use in the wait method of
MultiprocessingProcess, or else it will lead to Future
"attached to a different loop" errors.

Bug: https://bugs.gentoo.org/761538
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/portage/tests/util/test_socks5.py | 45 +++++++++++++++++++----------------
 lib/portage/util/socks5.py            | 30 ++++++++++++++++-------
 2 files changed, 46 insertions(+), 29 deletions(-)

diff --git a/lib/portage/tests/util/test_socks5.py 
b/lib/portage/tests/util/test_socks5.py
index 987b41af29..7b33cb3f6b 100644
--- a/lib/portage/tests/util/test_socks5.py
+++ b/lib/portage/tests/util/test_socks5.py
@@ -1,6 +1,7 @@
-# Copyright 2019-2021 Gentoo Authors
+# Copyright 2019-2024 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
+import asyncio
 import functools
 import shutil
 import socket
@@ -10,7 +11,6 @@ import time
 
 import portage
 from portage.tests import TestCase
-from portage.util._eventloop.global_event_loop import global_event_loop
 from portage.util import socks5
 from portage.const import PORTAGE_BIN_PATH
 
@@ -88,18 +88,20 @@ class AsyncHTTPServerTestCase(TestCase):
             if f is not None:
                 f.close()
 
-    def test_http_server(self):
+    async def _test_http_server(self):
+        asyncio.run(self._test_http_server())
+
+    async def _test_http_server(self):
         host = "127.0.0.1"
         content = b"Hello World!\n"
         path = "/index.html"
-        loop = global_event_loop()
+
+        loop = asyncio.get_running_loop()
         for i in range(2):
             with AsyncHTTPServer(host, {path: content}, loop) as server:
                 for j in range(2):
-                    result = loop.run_until_complete(
-                        loop.run_in_executor(
-                            None, self._fetch_directly, host, 
server.server_port, path
-                        )
+                    result = await loop.run_in_executor(
+                        None, self._fetch_directly, host, server.server_port, 
path
                     )
                     self.assertEqual(result, content)
 
@@ -177,7 +179,10 @@ class Socks5ServerTestCase(TestCase):
             return f.read()
 
     def test_socks5_proxy(self):
-        loop = global_event_loop()
+        asyncio.run(self._test_socks5_proxy())
+
+    async def _test_socks5_proxy(self):
+        loop = asyncio.get_running_loop()
 
         host = "127.0.0.1"
         content = b"Hello World!"
@@ -193,20 +198,18 @@ class Socks5ServerTestCase(TestCase):
                 }
 
                 proxy = socks5.get_socks5_proxy(settings)
-                loop.run_until_complete(socks5.proxy.ready())
-
-                result = loop.run_until_complete(
-                    loop.run_in_executor(
-                        None,
-                        self._fetch_via_proxy,
-                        proxy,
-                        host,
-                        server.server_port,
-                        path,
-                    )
+                await socks5.proxy.ready()
+
+                result = await loop.run_in_executor(
+                    None,
+                    self._fetch_via_proxy,
+                    proxy,
+                    host,
+                    server.server_port,
+                    path,
                 )
 
                 self.assertEqual(result, content)
         finally:
-            socks5.proxy.stop()
+            await socks5.proxy.stop()
             shutil.rmtree(tempdir)

diff --git a/lib/portage/util/socks5.py b/lib/portage/util/socks5.py
index 6c68ff4106..74592aeefe 100644
--- a/lib/portage/util/socks5.py
+++ b/lib/portage/util/socks5.py
@@ -2,15 +2,16 @@
 # Copyright 2015-2024 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
+import asyncio
 import errno
 import os
 import socket
+from typing import Union
 
 import portage.data
 from portage import _python_interpreter
 from portage.data import portage_gid, portage_uid, userpriv_groups
 from portage.process import atexit_register, spawn
-from portage.util.futures import asyncio
 
 
 class ProxyManager:
@@ -57,23 +58,36 @@ class ProxyManager:
             **spawn_kwargs,
         )
 
-    def stop(self):
+    def stop(self) -> Union[None, asyncio.Future]:
         """
         Stop the SOCKSv5 server.
+
+        If there is a running asyncio event loop then asyncio.Future is
+        returned which should be used to wait for the server process
+        to exit.
         """
+        future = None
+        try:
+            loop = asyncio.get_running_loop()
+        except RuntimeError:
+            loop = None
         if self._proc is not None:
             self._proc.terminate()
-            loop = asyncio.get_event_loop()
-            if self._proc_waiter is None:
-                self._proc_waiter = asyncio.ensure_future(self._proc.wait(), 
loop)
-            if loop.is_running():
-                self._proc_waiter.add_done_callback(lambda future: 
future.result())
+            if loop is None:
+                asyncio.run(self._proc.wait())
             else:
-                loop.run_until_complete(self._proc_waiter)
+                if self._proc_waiter is None:
+                    self._proc_waiter = 
asyncio.ensure_future(self._proc.wait(), loop)
+                future = asyncio.shield(self._proc_waiter)
+
+        if loop is not None and future is None:
+            future = loop.create_future()
+            future.set_result(None)
 
         self.socket_path = None
         self._proc = None
         self._proc_waiter = None
+        return future
 
     def is_running(self):
         """

Reply via email to