Skip to content

Commit 8d1d3fa

Browse files
committed
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. 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>
1 parent ff270ae commit 8d1d3fa

2 files changed

Lines changed: 24 additions & 8 deletions

File tree

lib/portage/util/_eventloop/asyncio_event_loop.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,8 @@ def _run_until_complete(self, future):
121121
try:
122122
return self._loop.run_until_complete(future)
123123
finally:
124-
self._wakeup_fd = signal.set_wakeup_fd(-1)
124+
try:
125+
self._wakeup_fd = signal.set_wakeup_fd(-1)
126+
except ValueError:
127+
# This is intended to fail when not called in the main thread.
128+
pass

lib/portage/util/futures/_asyncio/__init__.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
portage.proxy.lazyimport.lazyimport(globals(),
3535
'portage.util.futures.unix_events:_PortageEventLoopPolicy',
3636
'portage.util.futures:compat_coroutine@_compat_coroutine',
37-
'portage.util._eventloop.EventLoop:EventLoop@_EventLoop',
3837
)
3938
from portage.util._eventloop.asyncio_event_loop import AsyncioEventLoop as _AsyncioEventLoop
4039
from portage.util._eventloop.global_event_loop import (
@@ -246,14 +245,27 @@ def _wrap_loop(loop=None):
246245
def _safe_loop():
247246
"""
248247
Return an event loop that's safe to use within the current context.
249-
For portage internal callers, this returns a globally shared event
250-
loop instance. For external API consumers, this constructs a
251-
temporary event loop instance that's safe to use in a non-main
252-
thread (it does not override the global SIGCHLD handler).
248+
For portage internal callers or external API consumers calling from
249+
the main thread, this returns a globally shared event loop instance.
250+
251+
For external API consumers calling from a non-main thread, an
252+
asyncio loop must be registered for the current thread, or else an
253+
error will be raised like this:
254+
255+
RuntimeError: There is no current event loop in thread 'Thread-1'.
256+
257+
In order to avoid this RuntimeError, the external API consumer
258+
is responsible for setting an event loop and managing its lifecycle.
259+
This code will set an event loop for the current thread:
260+
261+
asyncio.set_event_loop(asyncio.new_event_loop())
262+
263+
In order to avoid a ResourceWarning, the caller should also close the
264+
corresponding loop before the current thread terminates.
253265
254266
@rtype: asyncio.AbstractEventLoop (or compatible)
255267
@return: event loop instance
256268
"""
257-
if portage._internal_caller:
269+
if portage._internal_caller or threading.current_thread() is threading.main_thread():
258270
return _global_event_loop()
259-
return _EventLoop(main=False)
271+
return _AsyncioEventLoop()

0 commit comments

Comments
 (0)