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 |