public inbox for gentoo-portage-dev@lists.gentoo.org
 help / color / mirror / Atom feed
From: Zac Medico <zmedico@gentoo.org>
To: gentoo-portage-dev@lists.gentoo.org
Cc: Zac Medico <zmedico@gentoo.org>
Subject: [gentoo-portage-dev] [PATCH v4] Use default asyncio event loop implementation in API consumer threads
Date: Sun,  6 Dec 2020 16:18:30 -0800	[thread overview]
Message-ID: <20201207001830.1135311-1-zmedico@gentoo.org> (raw)
In-Reply-To: <20201206094626.1032508-1-zmedico@gentoo.org>

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.
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 <zmedico@gentoo.org>
---
[PATCH v4] treat external API consumers the same as interal callers
if they call from the main thread, and document asyncio loop
lifecycle management now required for external API consumers
calling from a non-main thread

 .../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..0b35c6daf 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.
+	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()
-- 
2.26.2



      parent reply	other threads:[~2020-12-07  0:21 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-12-06  8:59 [gentoo-portage-dev] [PATCH] Use default asyncio event loop implementation in child processes Zac Medico
2020-12-06  9:46 ` [gentoo-portage-dev] [PATCH] Use default asyncio event loop implementation in API consumer threads Zac Medico
2020-12-06 11:38   ` [gentoo-portage-dev] " Zac Medico
2020-12-06 21:38   ` [gentoo-portage-dev] [PATCH v2] " Zac Medico
2020-12-06 22:14   ` [gentoo-portage-dev] [PATCH v3] " Zac Medico
2020-12-06 23:15     ` [gentoo-portage-dev] " Zac Medico
2020-12-06 23:19     ` Zac Medico
2020-12-07  0:18   ` Zac Medico [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201207001830.1135311-1-zmedico@gentoo.org \
    --to=zmedico@gentoo.org \
    --cc=gentoo-portage-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox