Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH v4] Use default asyncio event loop implementation in API consumer threads
Date: Mon, 07 Dec 2020 00:21:21
Message-Id: 20201207001830.1135311-1-zmedico@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH] Use default asyncio event loop implementation in API consumer threads by Zac Medico
1 Make the _safe_loop function return an AsyncioEventLoop instance,
2 so that the default asyncio event loop implementation will be used
3 in API consumer threads. This is possible because the underlying
4 asyncio.get_event_loop() function returns a separate event loop for
5 each thread. The AsyncioEventLoop _run_until_complete method will
6 now appropriately handle a ValueError from signal.set_wakeup_fd(-1)
7 if it is not called in the main thread.
8
9 For external API consumers calling from a non-main thread, an
10 asyncio loop must be registered for the current thread, or else an
11 error will be raised like this:
12
13 RuntimeError: There is no current event loop in thread 'Thread-1'.
14
15 In order to avoid this RuntimeError, the external API consumer
16 is responsible for setting an event loop and managing its lifecycle.
17 This code will set an event loop for the current thread:
18
19 asyncio.set_event_loop(asyncio.new_event_loop())
20
21 In order to avoid a ResourceWarning, the caller should also close
22 the corresponding loop before the current thread terminates.
23
24 Bug: https://bugs.gentoo.org/758755
25 Signed-off-by: Zac Medico <zmedico@g.o>
26 ---
27 [PATCH v4] treat external API consumers the same as interal callers
28 if they call from the main thread, and document asyncio loop
29 lifecycle management now required for external API consumers
30 calling from a non-main thread
31
32 .../util/_eventloop/asyncio_event_loop.py | 6 ++++-
33 lib/portage/util/futures/_asyncio/__init__.py | 26 ++++++++++++++-----
34 2 files changed, 24 insertions(+), 8 deletions(-)
35
36 diff --git a/lib/portage/util/_eventloop/asyncio_event_loop.py b/lib/portage/util/_eventloop/asyncio_event_loop.py
37 index 836f1c30a..4d7047ae8 100644
38 --- a/lib/portage/util/_eventloop/asyncio_event_loop.py
39 +++ b/lib/portage/util/_eventloop/asyncio_event_loop.py
40 @@ -121,4 +121,8 @@ class AsyncioEventLoop(_AbstractEventLoop):
41 try:
42 return self._loop.run_until_complete(future)
43 finally:
44 - self._wakeup_fd = signal.set_wakeup_fd(-1)
45 + try:
46 + self._wakeup_fd = signal.set_wakeup_fd(-1)
47 + except ValueError:
48 + # This is intended to fail when not called in the main thread.
49 + pass
50 diff --git a/lib/portage/util/futures/_asyncio/__init__.py b/lib/portage/util/futures/_asyncio/__init__.py
51 index a902ad895..0b35c6daf 100644
52 --- a/lib/portage/util/futures/_asyncio/__init__.py
53 +++ b/lib/portage/util/futures/_asyncio/__init__.py
54 @@ -34,7 +34,6 @@ import portage
55 portage.proxy.lazyimport.lazyimport(globals(),
56 'portage.util.futures.unix_events:_PortageEventLoopPolicy',
57 'portage.util.futures:compat_coroutine@_compat_coroutine',
58 - 'portage.util._eventloop.EventLoop:EventLoop@_EventLoop',
59 )
60 from portage.util._eventloop.asyncio_event_loop import AsyncioEventLoop as _AsyncioEventLoop
61 from portage.util._eventloop.global_event_loop import (
62 @@ -246,14 +245,27 @@ def _wrap_loop(loop=None):
63 def _safe_loop():
64 """
65 Return an event loop that's safe to use within the current context.
66 - For portage internal callers, this returns a globally shared event
67 - loop instance. For external API consumers, this constructs a
68 - temporary event loop instance that's safe to use in a non-main
69 - thread (it does not override the global SIGCHLD handler).
70 + For portage internal callers or external API consumers calling from
71 + the main thread, this returns a globally shared event loop instance.
72 +
73 + For external API consumers calling from a non-main thread, an
74 + asyncio loop must be registered for the current thread, or else an
75 + error will be raised like this:
76 +
77 + RuntimeError: There is no current event loop in thread 'Thread-1'.
78 +
79 + In order to avoid this RuntimeError, the external API consumer
80 + is responsible for setting an event loop and managing its lifecycle.
81 + This code will set an event loop for the current thread:
82 +
83 + asyncio.set_event_loop(asyncio.new_event_loop())
84 +
85 + In order to avoid a ResourceWarning, the caller should also close the
86 + corresponding loop before the current thread terminates.
87
88 @rtype: asyncio.AbstractEventLoop (or compatible)
89 @return: event loop instance
90 """
91 - if portage._internal_caller:
92 + if portage._internal_caller or threading.current_thread() is threading.main_thread():
93 return _global_event_loop()
94 - return _EventLoop(main=False)
95 + return _AsyncioEventLoop()
96 --
97 2.26.2