Re: [PATCH v3] Use default asyncio event loop implementation in API consumer threads

Zac Medico <[email protected]> Sun, 6 Dec 2020 15:15:55 -0800
Newsgroups gmane.linux.gentoo.portage.devel
Message-ID <[email protected]>
On 12/6/20 2:14 PM, Zac Medico wrote:
> 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 new 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.
> 
> Bug: https://bugs.gentoo.org/758755
> Signed-off-by: Zac Medico <[email protected]>
> ---
> [PATCH v3] fixed AsyncioEventLoop _run_until_complete method to
> handle ValueError from signal.set_wakeup_fd(-1)
> 
>  lib/portage/util/_eventloop/asyncio_event_loop.py | 6 +++++-
>  lib/portage/util/futures/_asyncio/__init__.py     | 3 +--
>  2 files changed, 6 insertions(+), 3 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..12013be00 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 (
> @@ -256,4 +255,4 @@ def _safe_loop():
>  	"""
>  	if portage._internal_caller:
>  		return _global_event_loop()
> -	return _EventLoop(main=False)
> +	return _AsyncioEventLoop()
> 

This fails if an event loop has not been created for the current thread:

  File "/usr/lib/python3.8/asyncio/events.py", line 639, in get_event_loop
    raise RuntimeError('There is no current event loop in thread %r.'
RuntimeError: There is no current event loop in thread 'Thread-1'.

However, if we automatically instantiate a loop for the current thread
then we will be responsible for closing it as well, or else we'll
eventually see a ResourceWarning like this:

/usr/lib/python3.8/asyncio/base_events.py:654: ResourceWarning: unclosed
event loop <_UnixSelectorEventLoop running=False closed=False debug=False>
  _warn(f"unclosed event loop {self!r}", ResourceWarning, source=self)
ResourceWarning: Enable tracemalloc to get the object allocation traceback

So, I think it's probably best if we force the API consumer to manage
the lifecycle of an asyncio loop for each thread that it uses to call
the portage API.
-- 
Thanks,
Zac
signature.asc (application/pgp-signature, 981 B)
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQKTBAEBCgB9FiEE8OgXaltWzqgSupCu0HX7jBBKPSAFAl/NZitfFIAAAAAALgAo
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldEYw
RTgxNzZBNUI1NkNFQTgxMkJBOTBBRUQwNzVGQjhDMTA0QTNEMjAACgkQ0HX7jBBK
PSBRwA//ezke21VGTxzMC9nGILMTohnoxCz8zhkFgcC+jDCq73SAhpZK9qZFLKYb
oxDdjZidXvmxHDuQOhtQmwq98B3LyygxPSyF05LLNhV1zPqBdW/VfQt2shnbvNeU
cFxNX/BhB7Ia/WRSkQvyPzdZSQylZvWWJ8m2XtyxJYiiSiTZCNG6Xe/2sxFe5tyN
8w7jMA7o9SwycTtlQI2foaLGJVfPH81L601r4SnYuljpGfM5BUaa3mESFWtCu83A
igEvXpzO5ZhPTqYkoatAPvkrafemcLTRIuO1uHoa6HN47XwpX0TA6V2nBU12fckK
K49aLOmZml7y6lDNCmXdebXkrd/M1VHRB14s5v1U8nJjP6nPK3MJbNxFn0Xq/gYS
5+en6d55nfeQQfDNIWvAIeyDoVh31utc4Ln2am/oclyImfrKPY4HOBdZ2QPVe/7G
UC+Qow2XBVwm6VdoTp/g+RwYSPLnfneyNfLZ5167dngbU3QVCgN6s2YB1EaIrqUX
TXLkduNYCQFVq/iS4gy03qPrAECz4KYGdf3UIpARjb0Ub8Waph50sMMmvozuKdm3
7ZxD9HtVKDbNf/BlAEujMyyBB5q7t7j899unGAIp/POIHjU+rHp+X8wnxBgIjkOM
k9KigYYwEdn6H5ljZYXQpJKsl4DiaX4mUP2pK3KGVKJqVGaRy4E=
=xuNp
-----END PGP SIGNATURE-----