[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/, lib/portage/util/, lib/portage/tests/util/
commit: 11c65496bd951c3b7778a3c1ea240e347b1f4c38 Author: Zac Medico gentoo org> AuthorDate: Sun Mar 3 21:06:13 2024 + Commit: Zac Medico gentoo org> CommitDate: Sun Mar 3 21:10:49 2024 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=11c65496 socks5: Use run_coroutine_exitfuncs() Since commit c3ebdbb42e72 the atexit_register(proxy.stop) call in the get_socks5_proxy function can accept a coroutine function to execute in run_coroutine_exitfuncs(), so convert the ProxyManager stop method to a coroutine and use run_coroutine_exitfuncs(). Bug: https://bugs.gentoo.org/925240 Signed-off-by: Zac Medico gentoo.org> lib/portage/tests/util/test_socks5.py | 5 ++- lib/portage/util/_eventloop/asyncio_event_loop.py | 12 --- lib/portage/util/socks5.py| 39 +++ 3 files changed, 7 insertions(+), 49 deletions(-) diff --git a/lib/portage/tests/util/test_socks5.py b/lib/portage/tests/util/test_socks5.py index 4a6d08169d..a8cd0c46c4 100644 --- a/lib/portage/tests/util/test_socks5.py +++ b/lib/portage/tests/util/test_socks5.py @@ -216,10 +216,9 @@ class Socks5ServerTestCase(TestCase): self.assertEqual(result, content) finally: try: -# Also run_exitfuncs to test atexit hook cleanup. -await socks5.proxy.stop() +# Also run_coroutine_exitfuncs to test atexit hook cleanup. self.assertNotEqual(portage.process._exithandlers, []) -portage.process.run_exitfuncs() +await portage.process.run_coroutine_exitfuncs() self.assertEqual(portage.process._exithandlers, []) finally: portage.process._exithandlers = previous_exithandlers diff --git a/lib/portage/util/_eventloop/asyncio_event_loop.py b/lib/portage/util/_eventloop/asyncio_event_loop.py index a598b1b516..821cc7f102 100644 --- a/lib/portage/util/_eventloop/asyncio_event_loop.py +++ b/lib/portage/util/_eventloop/asyncio_event_loop.py @@ -15,7 +15,6 @@ except ImportError: PidfdChildWatcher = None import portage -from portage.util import socks5 class AsyncioEventLoop(_AbstractEventLoop): @@ -75,17 +74,6 @@ class AsyncioEventLoop(_AbstractEventLoop): self._closing = False async def _close_main(self): -# Even though this has an exit hook, invoke it here so that -# we can properly wait for it and avoid messages like this: -# [ERROR] Task was destroyed but it is pending! -if socks5.proxy.is_running(): -# TODO: Convert socks5.proxy.stop() to a regular coroutine -# function so that it doesn't need to be wrapped like this. -async def stop_socks5_proxy(): -await socks5.proxy.stop() - -portage.process.atexit_register(stop_socks5_proxy) - await portage.process.run_coroutine_exitfuncs() portage.process.run_exitfuncs() diff --git a/lib/portage/util/socks5.py b/lib/portage/util/socks5.py index f8fcdf9fca..c32ba77674 100644 --- a/lib/portage/util/socks5.py +++ b/lib/portage/util/socks5.py @@ -6,15 +6,8 @@ import asyncio import errno import os import socket -from typing import Union import portage - -portage.proxy.lazyimport.lazyimport( -globals(), -"portage.util._eventloop.global_event_loop:global_event_loop", -) - import portage.data from portage import _python_interpreter from portage.data import portage_gid, portage_uid, userpriv_groups @@ -65,41 +58,19 @@ class ProxyManager: **spawn_kwargs, ) -def stop(self) -> Union[None, asyncio.Future]: +async def stop(self): """ -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. +Stop the SOCKSv5 server. This method is a coroutine. """ -future = None -try: -loop = asyncio.get_running_loop() -except RuntimeError: -loop = None if self._proc is not None: self._proc.terminate() -if loop is None: -# In this case spawn internals would have used -# portage's global loop when attaching a waiter to -# self._proc, so we are obligated to use that. -global_event_loop().run_until_complete(self._proc.wait()) -else: -if self._proc_waiter is None: -self._proc_waiter = asyncio.ensure_future( -self._proc.wait(), loop=loop -) -future = asyncio.shield(self._proc_waiter) - -if loop is not None and future is None: -future = loop.create_future() -future.set_result(None) +if self._proc_waiter is None: +self._proc_waiter
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/
commit: c98240fba21b319e27fe595d70c671c9ddfb7fd0 Author: Zac Medico gentoo org> AuthorDate: Thu Oct 5 08:20:14 2023 + Commit: Zac Medico gentoo org> CommitDate: Thu Oct 5 08:21:47 2023 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=c98240fb AsyncioEventLoop: Put back _ChildWatcherThreadSafetyWrapper In case there are multiple loops running in different threads, use _ChildWatcherThreadSafetyWrapper. This partially reverts commit 690ac6e78c4099e83b84ef11c0b4064b077a8ef0. Bug: https://bugs.gentoo.org/914873 Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/asyncio_event_loop.py | 30 ++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/portage/util/_eventloop/asyncio_event_loop.py b/lib/portage/util/_eventloop/asyncio_event_loop.py index fe941b420a..2b21e6dfaf 100644 --- a/lib/portage/util/_eventloop/asyncio_event_loop.py +++ b/lib/portage/util/_eventloop/asyncio_event_loop.py @@ -112,7 +112,7 @@ class AsyncioEventLoop(_AbstractEventLoop): watcher = ThreadedChildWatcher() watcher.attach_loop(self._loop) -self._child_watcher = watcher +self._child_watcher = _ChildWatcherThreadSafetyWrapper(self, watcher) return self._child_watcher @@ -153,3 +153,31 @@ class AsyncioEventLoop(_AbstractEventLoop): except ValueError: # This is intended to fail when not called in the main thread. pass + + +class _ChildWatcherThreadSafetyWrapper: +""" +This class provides safety if multiple loops are running in different threads. +""" + +def __init__(self, loop, real_watcher): +self._loop = loop +self._real_watcher = real_watcher + +def close(self): +pass + +def __enter__(self): +return self + +def __exit__(self, a, b, c): +pass + +def _child_exit(self, pid, status, callback, *args): +self._loop.call_soon_threadsafe(callback, pid, status, *args) + +def add_child_handler(self, pid, callback, *args): +self._real_watcher.add_child_handler(pid, self._child_exit, callback, *args) + +def remove_child_handler(self, pid): +return self._real_watcher.remove_child_handler(pid)
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/
commit: 690ac6e78c4099e83b84ef11c0b4064b077a8ef0 Author: Zac Medico gentoo org> AuthorDate: Thu Oct 5 06:27:27 2023 + Commit: Zac Medico gentoo org> CommitDate: Thu Oct 5 06:57:11 2023 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=690ac6e7 Eliminate deprecated asyncio.get_child_watcher() usage Use PidfdChildWatcher if os.pidfd_open is available and works, and otherwise use ThreadedChildWatcher which should work in any case. The _ChildWatcherThreadSafetyWrapper class is not needed because both PidfdChildWatcher and ThreadedChildWatcher use the running event loop to invoke the child handler callback, and it is safe to assume that the current AsyncioEventLoop instance is the running loop when it is used to obtain a child watcher instance. Bug: https://bugs.gentoo.org/914873 Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/asyncio_event_loop.py | 51 +++ 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/lib/portage/util/_eventloop/asyncio_event_loop.py b/lib/portage/util/_eventloop/asyncio_event_loop.py index 37b5b2706c..fe941b420a 100644 --- a/lib/portage/util/_eventloop/asyncio_event_loop.py +++ b/lib/portage/util/_eventloop/asyncio_event_loop.py @@ -6,6 +6,12 @@ import signal import asyncio as _real_asyncio from asyncio.events import AbstractEventLoop as _AbstractEventLoop +from asyncio.unix_events import ThreadedChildWatcher + +try: +from asyncio.unix_events import PidfdChildWatcher +except ImportError: +PidfdChildWatcher = None import portage @@ -90,9 +96,24 @@ class AsyncioEventLoop(_AbstractEventLoop): @return: the internal event loop's AbstractChildWatcher interface """ if self._child_watcher is None: -self._child_watcher = _ChildWatcherThreadSafetyWrapper( -self, _real_asyncio.get_child_watcher() -) +pidfd_works = False +if PidfdChildWatcher is not None and hasattr(os, "pidfd_open"): +try: +fd = os.pidfd_open(portage.getpid()) +except Exception: +pass +else: +os.close(fd) +pidfd_works = True + +if pidfd_works: +watcher = PidfdChildWatcher() +else: +watcher = ThreadedChildWatcher() + +watcher.attach_loop(self._loop) +self._child_watcher = watcher + return self._child_watcher @property @@ -132,27 +153,3 @@ class AsyncioEventLoop(_AbstractEventLoop): except ValueError: # This is intended to fail when not called in the main thread. pass - - -class _ChildWatcherThreadSafetyWrapper: -def __init__(self, loop, real_watcher): -self._loop = loop -self._real_watcher = real_watcher - -def close(self): -pass - -def __enter__(self): -return self - -def __exit__(self, a, b, c): -pass - -def _child_exit(self, pid, status, callback, *args): -self._loop.call_soon_threadsafe(callback, pid, status, *args) - -def add_child_handler(self, pid, callback, *args): -self._real_watcher.add_child_handler(pid, self._child_exit, callback, *args) - -def remove_child_handler(self, pid): -return self._real_watcher.remove_child_handler(pid)
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/
commit: 34e4d5655360d135dbf2cd3a073d07d96e23cf74 Author: Zac Medico gentoo org> AuthorDate: Tue Sep 26 05:09:53 2023 + Commit: Zac Medico gentoo org> CommitDate: Tue Sep 26 05:14:21 2023 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=34e4d565 Remove deprecated AbstractChildWatcher usage Fixes this DeprecationWarning triggered by lib/portage/tests/util/futures/asyncio/test_event_loop_in_fork.py: lib/portage/util/_eventloop/asyncio_event_loop.py:138: DeprecationWarning: 'AbstractChildWatcher' is deprecated as of Python 3.12 and will be removed in Python 3.14. class _ChildWatcherThreadSafetyWrapper(_AbstractChildWatcher): Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/asyncio_event_loop.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/portage/util/_eventloop/asyncio_event_loop.py b/lib/portage/util/_eventloop/asyncio_event_loop.py index 88933af9d2..37b5b2706c 100644 --- a/lib/portage/util/_eventloop/asyncio_event_loop.py +++ b/lib/portage/util/_eventloop/asyncio_event_loop.py @@ -1,4 +1,4 @@ -# Copyright 2018-2021 Gentoo Authors +# Copyright 2018-2023 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import os @@ -6,7 +6,6 @@ import signal import asyncio as _real_asyncio from asyncio.events import AbstractEventLoop as _AbstractEventLoop -from asyncio.unix_events import AbstractChildWatcher as _AbstractChildWatcher import portage @@ -135,7 +134,7 @@ class AsyncioEventLoop(_AbstractEventLoop): pass -class _ChildWatcherThreadSafetyWrapper(_AbstractChildWatcher): +class _ChildWatcherThreadSafetyWrapper: def __init__(self, loop, real_watcher): self._loop = loop self._real_watcher = real_watcher
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/
commit: e0b6fbdf6543c19c63bbca007278d48003b807ad Author: Zac Medico gentoo org> AuthorDate: Sun Mar 7 09:28:08 2021 + Commit: Zac Medico gentoo org> CommitDate: Sun Mar 7 09:28:37 2021 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=e0b6fbdf AsyncioEventLoop: remove obsolete supports_multiprocessing attribute Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/asyncio_event_loop.py | 7 +-- 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/portage/util/_eventloop/asyncio_event_loop.py b/lib/portage/util/_eventloop/asyncio_event_loop.py index b77728088..6dfac3569 100644 --- a/lib/portage/util/_eventloop/asyncio_event_loop.py +++ b/lib/portage/util/_eventloop/asyncio_event_loop.py @@ -1,4 +1,4 @@ -# Copyright 2018-2020 Gentoo Authors +# Copyright 2018-2021 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import os @@ -17,11 +17,6 @@ class AsyncioEventLoop(_AbstractEventLoop): event loop and is minimally compatible with _PortageEventLoop. """ - # Use portage's internal event loop in subprocesses, as a workaround - # for https://bugs.python.org/issue22087, and also - # https://bugs.python.org/issue29703 which affects pypy3-5.10.1. - supports_multiprocessing = False - def __init__(self, loop=None): loop = loop or _real_asyncio.get_event_loop() self._loop = loop
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/
commit: 4cf221840bb980305b48a329c693dd1a310d5248 Author: Zac Medico gentoo org> AuthorDate: Sun Mar 7 02:48:57 2021 + Commit: Zac Medico gentoo org> CommitDate: Sun Mar 7 02:49:34 2021 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=4cf22184 Remove unused PollSelectAdapter and PollConstants classes Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/PollConstants.py | 17 -- lib/portage/util/_eventloop/PollSelectAdapter.py | 74 2 files changed, 91 deletions(-) diff --git a/lib/portage/util/_eventloop/PollConstants.py b/lib/portage/util/_eventloop/PollConstants.py deleted file mode 100644 index c5700d108..0 --- a/lib/portage/util/_eventloop/PollConstants.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 1999-2009 Gentoo Foundation -# Distributed under the terms of the GNU General Public License v2 - -import select -class PollConstants: - - """ - Provides POLL* constants that are equivalent to those from the - select module, for use by PollSelectAdapter. - """ - - names = ("POLLIN", "POLLPRI", "POLLOUT", "POLLERR", "POLLHUP", "POLLNVAL") - v = 1 - for k in names: - locals()[k] = getattr(select, k, v) - v *= 2 - del k, v diff --git a/lib/portage/util/_eventloop/PollSelectAdapter.py b/lib/portage/util/_eventloop/PollSelectAdapter.py deleted file mode 100644 index c4637a352..0 --- a/lib/portage/util/_eventloop/PollSelectAdapter.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 1999-2020 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -import select - -from .PollConstants import PollConstants - -class PollSelectAdapter: - - """ - Use select to emulate a poll object, for - systems that don't support poll(). - """ - - def __init__(self): - self._registered = {} - self._select_args = [[], [], []] - - def register(self, fd, *args): - """ - Only POLLIN is currently supported! - """ - if len(args) > 1: - raise TypeError( - "register expected at most 2 arguments, got " + \ - repr(1 + len(args))) - - eventmask = PollConstants.POLLIN | \ - PollConstants.POLLPRI | PollConstants.POLLOUT - if args: - eventmask = args[0] - - self._registered[fd] = eventmask - self._select_args = None - - def unregister(self, fd): - self._select_args = None - del self._registered[fd] - - def poll(self, *args): - if len(args) > 1: - raise TypeError( - "poll expected at most 2 arguments, got " + \ - repr(1 + len(args))) - - timeout = None - if args: - timeout = args[0] - - select_args = self._select_args - if select_args is None: - select_args = [list(self._registered), [], []] - - if timeout is not None: - select_args = select_args[:] - # Translate poll() timeout args to select() timeout args: - # - # | units| value(s) for indefinite block - # -|--|-- - # poll | milliseconds | omitted, negative, or None - # -|--|-- - # select | seconds | omitted - # -|--|-- - - if timeout is not None and timeout < 0: - timeout = None - if timeout is not None: - select_args.append(timeout / 1000) - - select_events = select.select(*select_args) - poll_events = [] - for fd in select_events[0]: - poll_events.append((fd, PollConstants.POLLIN)) - return poll_events
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/
commit: 05e9b76523569f17c3cbf7465758656c3beb0be8 Author: Zac Medico gentoo org> AuthorDate: Sat Mar 6 10:39:31 2021 + Commit: Zac Medico gentoo org> CommitDate: Sat Mar 6 10:41:06 2021 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=05e9b765 Remove unused EventLoop class Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/EventLoop.py | 1153 -- 1 file changed, 1153 deletions(-) diff --git a/lib/portage/util/_eventloop/EventLoop.py b/lib/portage/util/_eventloop/EventLoop.py deleted file mode 100644 index ff2b73255..0 --- a/lib/portage/util/_eventloop/EventLoop.py +++ /dev/null @@ -1,1153 +0,0 @@ -# Copyright 1999-2020 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -import asyncio as _real_asyncio -import collections -import errno -import functools -import logging -import os -import select -import signal -import time -import traceback - -try: - import fcntl -except ImportError: - # http://bugs.jython.org/issue1074 - fcntl = None - -try: - import threading -except ImportError: - import dummy_threading as threading - -import portage -portage.proxy.lazyimport.lazyimport(globals(), - 'portage.util.futures:asyncio', - 'portage.util.futures.executor.fork:ForkExecutor', - 'portage.util.futures.unix_events:_PortageEventLoop,_PortageChildWatcher', -) - -from portage.util import writemsg_level -from ..SlotObject import SlotObject -from .PollConstants import PollConstants -from .PollSelectAdapter import PollSelectAdapter - -class EventLoop: - """ - An event loop, intended to be compatible with the GLib event loop. - Call the iteration method in order to execute one iteration of the - loop. The idle_add and timeout_add methods serve as thread-safe - means to interact with the loop's thread. - """ - - supports_multiprocessing = True - - # TODO: Find out why SIGCHLD signals aren't delivered during poll - # calls, forcing us to wakeup in order to receive them. - _sigchld_interval = 250 - - class _child_callback_class(SlotObject): - __slots__ = ("callback", "data", "pid", "source_id") - - class _idle_callback_class(SlotObject): - __slots__ = ("_args", "_callback", "_cancelled") - - class _io_handler_class(SlotObject): - __slots__ = ("args", "callback", "f", "source_id") - - class _timeout_handler_class(SlotObject): - __slots__ = ("args", "function", "calling", "interval", "source_id", - "timestamp") - - class _handle: - """ - A callback wrapper object, compatible with asyncio.Handle. - """ - __slots__ = ("_callback_id", "_loop") - - def __init__(self, callback_id, loop): - self._callback_id = callback_id - self._loop = loop - - def cancel(self): - """ - Cancel the call. If the callback is already canceled or executed, - this method has no effect. - """ - self._loop.source_remove(self._callback_id) - - class _call_soon_callback: - """ - Wraps a call_soon callback, and always returns False, since these - callbacks are only supposed to run once. - """ - __slots__ = ("_args", "_callback") - - def __init__(self, callback, args): - self._callback = callback - self._args = args - - def __call__(self): - self._callback(*self._args) - return False - - class _selector_callback: - """ - Wraps an callback, and always returns True, for callbacks that - are supposed to run repeatedly. - """ - __slots__ = ("_args", "_callbacks") - - def __init__(self, callbacks): - self._callbacks = callbacks - - def __call__(self, fd, event): - for callback, mask in self._callbacks: - if event & mask: - callback() - return True - - def __init__(self, main=True): - """ - @param main: If True then this is a singleton instance for use - in the main thread, otherwise it is a local instance which - can safely be use in a non-main thread (default is True, so - that global_event_loop does not need constructor arguments) - @type main: bool - """ - self._use_signal = main and fcntl is not None -
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/, lib/portage/util/futures/_asyncio/
commit: 386178481eb86ac603cd90ef1bb6ac6b68e51c50 Author: Zac Medico gentoo org> AuthorDate: Mon Jan 4 09:14:36 2021 + Commit: Zac Medico gentoo org> CommitDate: Mon Jan 11 09:36:48 2021 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=38617848 global_event_loop: return running loop for current thread Like asyncio.get_event_loop(), return the running loop for the current thread if there is one, and otherwise construct a new one if needed. This allows the _safe_loop function to become synonymous with the global_event_loop function. For the case of "loop running in non-main thread" of API consumer, this change makes portage compatible with PEP 492 coroutines with async and await syntax. Portage internals can safely begin using async / await syntax instead of compat_coroutine. Bug: https://bugs.gentoo.org/763339 Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/global_event_loop.py | 28 +++--- lib/portage/util/futures/_asyncio/__init__.py| 30 +--- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/lib/portage/util/_eventloop/global_event_loop.py b/lib/portage/util/_eventloop/global_event_loop.py index 413011178..cb7a13078 100644 --- a/lib/portage/util/_eventloop/global_event_loop.py +++ b/lib/portage/util/_eventloop/global_event_loop.py @@ -1,28 +1,6 @@ -# Copyright 2012-2020 Gentoo Authors +# Copyright 2012-2021 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 -import portage -from portage.util._eventloop.asyncio_event_loop import AsyncioEventLoop +__all__ = ('global_event_loop',) -_instances = {} - - -def global_event_loop(): - """ - Get a global EventLoop (or compatible object) instance which - belongs exclusively to the current process. - """ - - pid = portage.getpid() - instance = _instances.get(pid) - if instance is not None: - return instance - - constructor = AsyncioEventLoop - - # Use the _asyncio_wrapper attribute, so that unit tests can compare - # the reference to one retured from _wrap_loop(), since they should - # not close the loop if it refers to a global event loop. - instance = constructor()._asyncio_wrapper - _instances[pid] = instance - return instance +from portage.util.futures._asyncio import _safe_loop as global_event_loop diff --git a/lib/portage/util/futures/_asyncio/__init__.py b/lib/portage/util/futures/_asyncio/__init__.py index d39f31786..5590963f1 100644 --- a/lib/portage/util/futures/_asyncio/__init__.py +++ b/lib/portage/util/futures/_asyncio/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2018-2020 Gentoo Authors +# Copyright 2018-2021 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 __all__ = ( @@ -37,9 +37,6 @@ portage.proxy.lazyimport.lazyimport(globals(), 'portage.util.futures:compat_coroutine@_compat_coroutine', ) from portage.util._eventloop.asyncio_event_loop import AsyncioEventLoop as _AsyncioEventLoop -from portage.util._eventloop.global_event_loop import ( - global_event_loop as _global_event_loop, -) # pylint: disable=redefined-builtin from portage.util.futures.futures import ( CancelledError, @@ -238,7 +235,7 @@ def _wrap_loop(loop=None): # The default loop returned by _wrap_loop should be consistent # with global_event_loop, in order to avoid accidental registration # of callbacks with a loop that is not intended to run. - loop = loop or _global_event_loop() + loop = loop or _safe_loop() return (loop if hasattr(loop, '_asyncio_wrapper') else _AsyncioEventLoop(loop=loop)) @@ -267,13 +264,15 @@ def _safe_loop(): @rtype: asyncio.AbstractEventLoop (or compatible) @return: event loop instance """ - if portage._internal_caller or threading.current_thread() is threading.main_thread(): - return _global_event_loop() + loop = _get_running_loop() + if loop is not None: + return loop thread_key = threading.get_ident() with _thread_weakrefs.lock: if _thread_weakrefs.pid != portage.getpid(): _thread_weakrefs.pid = portage.getpid() + _thread_weakrefs.mainloop = None _thread_weakrefs.loops = weakref.WeakValueDictionary() try: loop = _thread_weakrefs.loops[thread_key] @@ -283,9 +282,23 @@ def _safe_loop(): except RuntimeError: _real_asyncio.set_event_loop(_real_asyncio.new_event_loop()) loop = _thread_weakrefs.loops[thread_key] = _AsyncioEventLoop() + + if _thread_weakrefs.mainloop is None and threading.current_thread() is threading.main_thread(): + _thread_weakrefs.mainloop = loop +
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/
commit: eeeb70bf50994c08ebcddac94474105f0635360c Author: Zac Medico gentoo org> AuthorDate: Mon Jan 11 06:46:49 2021 + Commit: Zac Medico gentoo org> CommitDate: Mon Jan 11 07:28:28 2021 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=eeeb70bf AsyncioEventLoop: wrap child watcher for thread safety (bug 764905) Use a child watcher wrapper to deliver the callbacks via the call_soon_threadsafe method, since documentation for the asycio AbstractChildWatcher class says that callbacks must be thread safe. Bug: https://bugs.gentoo.org/764905 Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/asyncio_event_loop.py | 30 ++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/portage/util/_eventloop/asyncio_event_loop.py b/lib/portage/util/_eventloop/asyncio_event_loop.py index 4d7047ae8..b77728088 100644 --- a/lib/portage/util/_eventloop/asyncio_event_loop.py +++ b/lib/portage/util/_eventloop/asyncio_event_loop.py @@ -6,6 +6,7 @@ import signal import asyncio as _real_asyncio from asyncio.events import AbstractEventLoop as _AbstractEventLoop +from asyncio.unix_events import AbstractChildWatcher as _AbstractChildWatcher import portage @@ -47,6 +48,7 @@ class AsyncioEventLoop(_AbstractEventLoop): self.set_debug = loop.set_debug self.get_debug = loop.get_debug self._wakeup_fd = -1 + self._child_watcher = None if portage._internal_caller: loop.set_exception_handler(self._internal_caller_exception_handler) @@ -87,7 +89,9 @@ class AsyncioEventLoop(_AbstractEventLoop): @rtype: asyncio.AbstractChildWatcher @return: the internal event loop's AbstractChildWatcher interface """ - return _real_asyncio.get_child_watcher() + if self._child_watcher is None: + self._child_watcher = _ChildWatcherThreadSafetyWrapper(self, _real_asyncio.get_child_watcher()) + return self._child_watcher @property def _asyncio_wrapper(self): @@ -126,3 +130,27 @@ class AsyncioEventLoop(_AbstractEventLoop): except ValueError: # This is intended to fail when not called in the main thread. pass + + +class _ChildWatcherThreadSafetyWrapper(_AbstractChildWatcher): + def __init__(self, loop, real_watcher): + self._loop = loop + self._real_watcher = real_watcher + + def close(self): + pass + + def __enter__(self): + return self + + def __exit__(self, a, b, c): + pass + + def _child_exit(self, pid, status, callback, *args): + self._loop.call_soon_threadsafe(callback, pid, status, *args) + + def add_child_handler(self, pid, callback, *args): + self._real_watcher.add_child_handler(pid, self._child_exit, callback, *args) + + def remove_child_handler(self, pid): + return self._real_watcher.remove_child_handler(pid)
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/, lib/portage/util/futures/_asyncio/
commit: cecd2f8a259cf2991f2324c9a14e26170ba0ddcf Author: Zac Medico gentoo org> AuthorDate: Sun Dec 6 09:25:17 2020 + Commit: Zac Medico gentoo org> CommitDate: Mon Dec 7 02:32:27 2020 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=cecd2f8a Use default asyncio event loop implementation in API consumer threads Make the _safe_loop function return an AsyncioEventLoop instance, so that the default asyncio event loop implementation will be used in API consumer threads. This is possible because the underlying asyncio.get_event_loop() function returns a separate event loop for each thread. The AsyncioEventLoop _run_until_complete method will now appropriately handle a ValueError from signal.set_wakeup_fd(-1) if it is not called in the main thread. For external API consumers calling from a non-main thread, an asyncio loop must be registered for the current thread, or else an error will be raised like this: RuntimeError: There is no current event loop in thread 'Thread-1'. In order to avoid this RuntimeError, the external API consumer is responsible for setting an event loop and managing its lifecycle. For example, this code will set an event loop for the current thread: asyncio.set_event_loop(asyncio.new_event_loop()) In order to avoid a ResourceWarning, the caller should also close the corresponding loop before the current thread terminates. Bug: https://bugs.gentoo.org/758755 Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/asyncio_event_loop.py | 6 +- lib/portage/util/futures/_asyncio/__init__.py | 26 +-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/portage/util/_eventloop/asyncio_event_loop.py b/lib/portage/util/_eventloop/asyncio_event_loop.py index 836f1c30a..4d7047ae8 100644 --- a/lib/portage/util/_eventloop/asyncio_event_loop.py +++ b/lib/portage/util/_eventloop/asyncio_event_loop.py @@ -121,4 +121,8 @@ class AsyncioEventLoop(_AbstractEventLoop): try: return self._loop.run_until_complete(future) finally: - self._wakeup_fd = signal.set_wakeup_fd(-1) + try: + self._wakeup_fd = signal.set_wakeup_fd(-1) + except ValueError: + # This is intended to fail when not called in the main thread. + pass diff --git a/lib/portage/util/futures/_asyncio/__init__.py b/lib/portage/util/futures/_asyncio/__init__.py index a902ad895..6f3395a91 100644 --- a/lib/portage/util/futures/_asyncio/__init__.py +++ b/lib/portage/util/futures/_asyncio/__init__.py @@ -34,7 +34,6 @@ import portage portage.proxy.lazyimport.lazyimport(globals(), 'portage.util.futures.unix_events:_PortageEventLoopPolicy', 'portage.util.futures:compat_coroutine@_compat_coroutine', - 'portage.util._eventloop.EventLoop:EventLoop@_EventLoop', ) from portage.util._eventloop.asyncio_event_loop import AsyncioEventLoop as _AsyncioEventLoop from portage.util._eventloop.global_event_loop import ( @@ -246,14 +245,27 @@ def _wrap_loop(loop=None): def _safe_loop(): """ Return an event loop that's safe to use within the current context. - For portage internal callers, this returns a globally shared event - loop instance. For external API consumers, this constructs a - temporary event loop instance that's safe to use in a non-main - thread (it does not override the global SIGCHLD handler). + For portage internal callers or external API consumers calling from + the main thread, this returns a globally shared event loop instance. + + For external API consumers calling from a non-main thread, an + asyncio loop must be registered for the current thread, or else an + error will be raised like this: + + RuntimeError: There is no current event loop in thread 'Thread-1'. + + In order to avoid this RuntimeError, the external API consumer + is responsible for setting an event loop and managing its lifecycle. + For example, this code will set an event loop for the current thread: + + asyncio.set_event_loop(asyncio.new_event_loop()) + + In order to avoid a ResourceWarning, the caller should also close the + corresponding loop before the current thread terminates. @rtype: asyncio.AbstractEventLoop (or compatible) @return: event loop instance """ - if portage._internal_caller: + if portage._internal_caller or threading.current_thread() is threading.main_thread(): return _global_event_loop() - return _EventLoop(main=False) + return _AsyncioEventLoop()
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/, lib/portage/, lib/portage/util/futures/, ...
commit: 070b5268486d5a1443a0dc6c1317c704c1298218 Author: Michał Górny gentoo org> AuthorDate: Fri Jul 17 04:38:42 2020 + Commit: Michał Górny gentoo org> CommitDate: Fri Jul 17 06:36:19 2020 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=070b5268 Remove support code for Python < 3.4 Reviewed-by: Zac Medico gentoo.org> Closes: https://github.com/gentoo/portage/pull/576 Signed-off-by: Michał Górny gentoo.org> lib/_emerge/AsynchronousLock.py | 12 +- lib/_emerge/EbuildMetadataPhase.py | 12 +- lib/_emerge/FifoIpcDaemon.py | 30 +--- lib/_emerge/PipeReader.py| 12 +- lib/_emerge/SpawnProcess.py | 16 - lib/portage/dbapi/_MergeProcess.py | 10 lib/portage/locks.py | 11 - lib/portage/process.py | 2 +- lib/portage/util/_async/PipeLogger.py| 10 lib/portage/util/_eventloop/EventLoop.py | 23 -- lib/portage/util/_eventloop/global_event_loop.py | 15 +--- lib/portage/util/futures/_asyncio/__init__.py| 26 +++- lib/portage/util/futures/unix_events.py | 4 +--- 13 files changed, 20 insertions(+), 163 deletions(-) diff --git a/lib/_emerge/AsynchronousLock.py b/lib/_emerge/AsynchronousLock.py index aed1bcb15..d2a6773ff 100644 --- a/lib/_emerge/AsynchronousLock.py +++ b/lib/_emerge/AsynchronousLock.py @@ -1,4 +1,4 @@ -# Copyright 2010-2018 Gentoo Foundation +# Copyright 2010-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import fcntl @@ -192,16 +192,6 @@ class _LockProcess(AbstractPollTask): fcntl.fcntl(in_pr, fcntl.F_SETFL, fcntl.fcntl(in_pr, fcntl.F_GETFL) | os.O_NONBLOCK) - # FD_CLOEXEC is enabled by default in Python >=3.4. - if sys.hexversion < 0x304: - try: - fcntl.FD_CLOEXEC - except AttributeError: - pass - else: - fcntl.fcntl(in_pr, fcntl.F_SETFD, - fcntl.fcntl(in_pr, fcntl.F_GETFD) | fcntl.FD_CLOEXEC) - self.scheduler.add_reader(in_pr, self._output_handler) self._registered = True self._proc = SpawnProcess( diff --git a/lib/_emerge/EbuildMetadataPhase.py b/lib/_emerge/EbuildMetadataPhase.py index efe71892c..d00f194c2 100644 --- a/lib/_emerge/EbuildMetadataPhase.py +++ b/lib/_emerge/EbuildMetadataPhase.py @@ -1,4 +1,4 @@ -# Copyright 1999-2018 Gentoo Foundation +# Copyright 1999-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 from _emerge.SubProcess import SubProcess @@ -93,16 +93,6 @@ class EbuildMetadataPhase(SubProcess): fcntl.fcntl(master_fd, fcntl.F_SETFL, fcntl.fcntl(master_fd, fcntl.F_GETFL) | os.O_NONBLOCK) - # FD_CLOEXEC is enabled by default in Python >=3.4. - if sys.hexversion < 0x304: - try: - fcntl.FD_CLOEXEC - except AttributeError: - pass - else: - fcntl.fcntl(master_fd, fcntl.F_SETFD, - fcntl.fcntl(master_fd, fcntl.F_GETFD) | fcntl.FD_CLOEXEC) - fd_pipes[slave_fd] = slave_fd settings["PORTAGE_PIPE_FD"] = str(slave_fd) diff --git a/lib/_emerge/FifoIpcDaemon.py b/lib/_emerge/FifoIpcDaemon.py index 2ec69d1cb..ab1fdb572 100644 --- a/lib/_emerge/FifoIpcDaemon.py +++ b/lib/_emerge/FifoIpcDaemon.py @@ -1,14 +1,8 @@ -# Copyright 2010-2018 Gentoo Foundation +# Copyright 2010-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import sys -try: - import fcntl -except ImportError: - # http://bugs.jython.org/issue1074 - fcntl = None - from portage import os from _emerge.AbstractPollTask import AbstractPollTask from portage.cache.mappings import slot_dict_class @@ -28,17 +22,6 @@ class FifoIpcDaemon(AbstractPollTask): self._files.pipe_in = \ os.open(self.input_fifo, os.O_RDONLY|os.O_NONBLOCK) - # FD_CLOEXEC is enabled by default in Python >=3.4. - if sys.hexversion < 0x304 and fcntl is not None: - try: - fcntl.FD_CLOEXEC - except AttributeError: - pass - else: - fcntl.fcntl(self._files.pipe_in, fcntl.F_SETFD, -
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/, lib/portage/tests/util/futures/, ...
commit: 4501ca0e3dd4b2924c2ee13b6203e2ad094557e9 Author: Michał Górny gentoo org> AuthorDate: Tue Jul 14 18:39:58 2020 + Commit: Michał Górny gentoo org> CommitDate: Tue Jul 14 19:41:35 2020 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=4501ca0e Remove unnecessary time.monotonic() compat time.monotonic() is available since py3.3, so there's no need for the compat anymore. Reviewed-by: Zac Medico gentoo.org> Closes: https://github.com/gentoo/portage/pull/569 Signed-off-by: Michał Górny gentoo.org> lib/portage/dbapi/vartree.py | 7 +++--- lib/portage/tests/util/futures/test_retry.py | 8 +++ lib/portage/util/_eventloop/EventLoop.py | 6 ++--- lib/portage/util/monotonic.py| 34 4 files changed, 10 insertions(+), 45 deletions(-) diff --git a/lib/portage/dbapi/vartree.py b/lib/portage/dbapi/vartree.py index 3687b471b..80ca0ab80 100644 --- a/lib/portage/dbapi/vartree.py +++ b/lib/portage/dbapi/vartree.py @@ -1,4 +1,4 @@ -# Copyright 1998-2019 Gentoo Authors +# Copyright 1998-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 from __future__ import division, unicode_literals @@ -36,7 +36,6 @@ portage.proxy.lazyimport.lazyimport(globals(), 'portage.util.install_mask:install_mask_dir,InstallMask,_raise_exc', 'portage.util.listdir:dircache,listdir', 'portage.util.movefile:movefile', - 'portage.util.monotonic:monotonic', 'portage.util.path:first_existing,iter_parents', 'portage.util.writeable_check:get_ro_checker', 'portage.util._xattr:xattr', @@ -3616,7 +3615,7 @@ class dblink(object): symlink_collisions = [] destroot = self.settings['ROOT'] totfiles = len(file_list) + len(symlink_list) - previous = monotonic() + previous = time.monotonic() progress_shown = False report_interval = 1.7 # seconds falign = len("%d" % totfiles) @@ -3625,7 +3624,7 @@ class dblink(object): for i, (f, f_type) in enumerate(chain( ((f, "reg") for f in file_list), ((f, "sym") for f in symlink_list))): - current = monotonic() + current = time.monotonic() if current - previous > report_interval: showMessage(_("%3d%% done, %*d files remaining ...\n") % (i * 100 / totfiles, falign, totfiles - i)) diff --git a/lib/portage/tests/util/futures/test_retry.py b/lib/portage/tests/util/futures/test_retry.py index 7a1e76280..4530bba83 100644 --- a/lib/portage/tests/util/futures/test_retry.py +++ b/lib/portage/tests/util/futures/test_retry.py @@ -1,4 +1,4 @@ -# Copyright 2018 Gentoo Foundation +# Copyright 2018-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 try: @@ -12,6 +12,7 @@ except ImportError: import dummy_threading as threading import sys +import time from portage.tests import TestCase from portage.util._eventloop.global_event_loop import global_event_loop @@ -19,7 +20,6 @@ from portage.util.backoff import RandomExponentialBackoff from portage.util.futures import asyncio from portage.util.futures.retry import retry from portage.util.futures.executor.fork import ForkExecutor -from portage.util.monotonic import monotonic class SucceedLaterException(Exception): @@ -31,12 +31,12 @@ class SucceedLater(object): A callable object that succeeds some duration of time has passed. """ def __init__(self, duration): - self._succeed_time = monotonic() + duration + self._succeed_time = time.monotonic() + duration def __call__(self): loop = global_event_loop() result = loop.create_future() - remaining = self._succeed_time - monotonic() + remaining = self._succeed_time - time.monotonic() if remaining > 0: loop.call_soon_threadsafe(lambda: None if result.done() else result.set_exception(SucceedLaterException( diff --git a/lib/portage/util/_eventloop/EventLoop.py b/lib/portage/util/_eventloop/EventLoop.py index ffd12cff9..a3bea97aa 100644 --- a/lib/portage/util/_eventloop/EventLoop.py +++ b/lib/portage/util/_eventloop/EventLoop.py @@ -1,4 +1,4 @@ -# Copyright 1999-2018 Gentoo Foundation +# Copyright 1999-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 from __future__ import division @@ -11,6 +11,7 @@ import os import select import signal import sys +import time import traceback try:
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/
commit: 30150206fb0b3e013ef5b163b8d2f24c70a9d977 Author: Zac Medico gentoo org> AuthorDate: Mon Mar 2 04:56:49 2020 + Commit: Zac Medico gentoo org> CommitDate: Mon Mar 2 05:01:06 2020 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=30150206 AsyncioEventLoop: always die with SIGTERM in exception handler (bug 705910) Remove call to pdb.set_trace() in exception handler, since it's not very useful, and always die with a SIGTERM for unexpected exceptions here. Bug: https://bugs.gentoo.org/705910 Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/asyncio_event_loop.py | 31 +++ 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/lib/portage/util/_eventloop/asyncio_event_loop.py b/lib/portage/util/_eventloop/asyncio_event_loop.py index a11a10205..ce7e06923 100644 --- a/lib/portage/util/_eventloop/asyncio_event_loop.py +++ b/lib/portage/util/_eventloop/asyncio_event_loop.py @@ -1,10 +1,8 @@ -# Copyright 2018 Gentoo Foundation +# Copyright 2018-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import os -import pdb import signal -import sys try: import asyncio as _real_asyncio @@ -69,25 +67,14 @@ class AsyncioEventLoop(_AbstractEventLoop): """ loop.default_exception_handler(context) if 'exception' in context: - # If we have a tty then start the debugger, since in might - # aid in diagnosis of the problem. If there's no tty, then - # exit immediately. - if all(s.isatty() for s in (sys.stdout, sys.stderr, sys.stdin)): - # Restore default SIGINT handler, since emerge's Scheduler - # has a SIGINT handler which delays exit until after - # cleanup, and cleanup cannot occur here since the event - # loop is suspended (see bug 672540). - signal.signal(signal.SIGINT, signal.SIG_DFL) - pdb.set_trace() - else: - # Normally emerge will wait for all coroutines to complete - # after SIGTERM has been received. However, an unhandled - # exception will prevent the interrupted coroutine from - # completing, therefore use the default SIGTERM handler - # in order to ensure that emerge exits immediately (though - # uncleanly). - signal.signal(signal.SIGTERM, signal.SIG_DFL) - os.kill(os.getpid(), signal.SIGTERM) + # Normally emerge will wait for all coroutines to complete + # after SIGTERM has been received. However, an unhandled + # exception will prevent the interrupted coroutine from + # completing, therefore use the default SIGTERM handler + # in order to ensure that emerge exits immediately (though + # uncleanly). + signal.signal(signal.SIGTERM, signal.SIG_DFL) + os.kill(os.getpid(), signal.SIGTERM) def _create_future(self): """
[gentoo-commits] proj/portage:master commit in: lib/portage/util/_eventloop/
commit: ee1f6ff69887dfa02d5c3a3ab3d61163fa504d15 Author: Zac Medico gentoo org> AuthorDate: Tue Apr 16 01:33:29 2019 + Commit: Zac Medico gentoo org> CommitDate: Tue Apr 16 01:40:29 2019 + URL:https://gitweb.gentoo.org/proj/portage.git/commit/?id=ee1f6ff6 AsyncioEventLoop: enable SIGINT in exception handler (bug 672540) Before the exception handler invokes the pdb shell, enable SIGINT so that the user can exit with Control-C (otherwise SIGKILL is needed). Bug: https://bugs.gentoo.org/672540 Signed-off-by: Zac Medico gentoo.org> lib/portage/util/_eventloop/asyncio_event_loop.py | 5 + 1 file changed, 5 insertions(+) diff --git a/lib/portage/util/_eventloop/asyncio_event_loop.py b/lib/portage/util/_eventloop/asyncio_event_loop.py index ea0e03b23..a11a10205 100644 --- a/lib/portage/util/_eventloop/asyncio_event_loop.py +++ b/lib/portage/util/_eventloop/asyncio_event_loop.py @@ -73,6 +73,11 @@ class AsyncioEventLoop(_AbstractEventLoop): # aid in diagnosis of the problem. If there's no tty, then # exit immediately. if all(s.isatty() for s in (sys.stdout, sys.stderr, sys.stdin)): + # Restore default SIGINT handler, since emerge's Scheduler + # has a SIGINT handler which delays exit until after + # cleanup, and cleanup cannot occur here since the event + # loop is suspended (see bug 672540). + signal.signal(signal.SIGINT, signal.SIG_DFL) pdb.set_trace() else: # Normally emerge will wait for all coroutines to complete