1 |
commit: 96cc073263910e7c1b0f48cc08a4db4b56ffe408 |
2 |
Author: Zac Medico <zmedico <AT> gentoo <DOT> org> |
3 |
AuthorDate: Tue May 1 08:15:39 2018 +0000 |
4 |
Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> |
5 |
CommitDate: Tue May 8 16:42:03 2018 +0000 |
6 |
URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=96cc0732 |
7 |
|
8 |
global_event_loop: use asyncio event loop (bug 654390) |
9 |
|
10 |
For python3.4 and later, in the main process, replace portage's |
11 |
internal event loop with the standard library's asyncio event |
12 |
loop. Continue to use portage's internal event loop in subprocesses, |
13 |
since asyncio's event loop is not guaranteed to work well in |
14 |
subprocesses (see upstream python issues 22087 and 29703). |
15 |
|
16 |
An _AsyncioEventLoopPolicy class, derived from _PortageEventLoopPolicy, |
17 |
is needed for some unit tests that modify asyncio's event loop policy. |
18 |
This policy is not needed for anything other than unit testing. Portage |
19 |
uses asyncio's default event loop policy, and API consumers are free to |
20 |
use any desired event loop policy. |
21 |
|
22 |
Portage's asynchronous functions that accept a 'loop' parameter will |
23 |
work with any compatible asyncio.AbstractEventLoop implementation, since |
24 |
an internal _wrap_loop function automatically adapts the loop for |
25 |
internal use. |
26 |
|
27 |
Bug: https://bugs.gentoo.org/654390 |
28 |
Reviewed-by: Brian Dolbec <dolsen <AT> gentoo.org> |
29 |
Reviewed-by: Alec Warner <antarus <AT> gentoo.org> |
30 |
|
31 |
pym/portage/util/_async/SchedulerInterface.py | 3 + |
32 |
pym/portage/util/_eventloop/asyncio_event_loop.py | 77 +++++++++++++++++++++++ |
33 |
pym/portage/util/_eventloop/global_event_loop.py | 5 +- |
34 |
pym/portage/util/futures/_asyncio/__init__.py | 17 +++++ |
35 |
pym/portage/util/futures/unix_events.py | 43 ++++++++++++- |
36 |
5 files changed, 143 insertions(+), 2 deletions(-) |
37 |
|
38 |
diff --git a/pym/portage/util/_async/SchedulerInterface.py b/pym/portage/util/_async/SchedulerInterface.py |
39 |
index f1a3e9b0b..ec6417da1 100644 |
40 |
--- a/pym/portage/util/_async/SchedulerInterface.py |
41 |
+++ b/pym/portage/util/_async/SchedulerInterface.py |
42 |
@@ -33,6 +33,9 @@ class SchedulerInterface(SlotObject): |
43 |
"time", |
44 |
|
45 |
"_asyncio_child_watcher", |
46 |
+ # This attribute it used by _wrap_loop to detect if the |
47 |
+ # loop already has a suitable wrapper. |
48 |
+ "_asyncio_wrapper", |
49 |
) |
50 |
|
51 |
__slots__ = _event_loop_attrs + ("_event_loop", "_is_background") |
52 |
|
53 |
diff --git a/pym/portage/util/_eventloop/asyncio_event_loop.py b/pym/portage/util/_eventloop/asyncio_event_loop.py |
54 |
new file mode 100644 |
55 |
index 000000000..b365939b0 |
56 |
--- /dev/null |
57 |
+++ b/pym/portage/util/_eventloop/asyncio_event_loop.py |
58 |
@@ -0,0 +1,77 @@ |
59 |
+# Copyright 2018 Gentoo Foundation |
60 |
+# Distributed under the terms of the GNU General Public License v2 |
61 |
+ |
62 |
+try: |
63 |
+ import asyncio as _real_asyncio |
64 |
+ from asyncio.events import AbstractEventLoop as _AbstractEventLoop |
65 |
+except ImportError: |
66 |
+ # Allow ImportModulesTestCase to succeed. |
67 |
+ _real_asyncio = None |
68 |
+ _AbstractEventLoop = object |
69 |
+ |
70 |
+ |
71 |
+class AsyncioEventLoop(_AbstractEventLoop): |
72 |
+ """ |
73 |
+ Implementation of asyncio.AbstractEventLoop which wraps asyncio's |
74 |
+ event loop and is minimally compatible with _PortageEventLoop. |
75 |
+ """ |
76 |
+ |
77 |
+ # Use portage's internal event loop in subprocesses, as a workaround |
78 |
+ # for https://bugs.python.org/issue22087, and also |
79 |
+ # https://bugs.python.org/issue29703 which affects pypy3-5.10.1. |
80 |
+ supports_multiprocessing = False |
81 |
+ |
82 |
+ def __init__(self, loop=None): |
83 |
+ loop = loop or _real_asyncio.get_event_loop() |
84 |
+ self._loop = loop |
85 |
+ self.run_until_complete = loop.run_until_complete |
86 |
+ self.call_soon = loop.call_soon |
87 |
+ self.call_soon_threadsafe = loop.call_soon_threadsafe |
88 |
+ self.call_later = loop.call_later |
89 |
+ self.call_at = loop.call_at |
90 |
+ self.is_running = loop.is_running |
91 |
+ self.is_closed = loop.is_closed |
92 |
+ self.close = loop.close |
93 |
+ self.create_future = (loop.create_future |
94 |
+ if hasattr(loop, 'create_future') else self._create_future) |
95 |
+ self.create_task = loop.create_task |
96 |
+ self.add_reader = loop.add_reader |
97 |
+ self.remove_reader = loop.remove_reader |
98 |
+ self.add_writer = loop.add_writer |
99 |
+ self.remove_writer = loop.remove_writer |
100 |
+ self.run_in_executor = loop.run_in_executor |
101 |
+ self.time = loop.time |
102 |
+ self.default_exception_handler = loop.default_exception_handler |
103 |
+ self.call_exception_handler = loop.call_exception_handler |
104 |
+ self.set_debug = loop.set_debug |
105 |
+ self.get_debug = loop.get_debug |
106 |
+ |
107 |
+ def _create_future(self): |
108 |
+ """ |
109 |
+ Provide AbstractEventLoop.create_future() for python3.4. |
110 |
+ """ |
111 |
+ return _real_asyncio.Future(loop=self._loop) |
112 |
+ |
113 |
+ @property |
114 |
+ def _asyncio_child_watcher(self): |
115 |
+ """ |
116 |
+ Portage internals use this as a layer of indirection for |
117 |
+ asyncio.get_child_watcher(), in order to support versions of |
118 |
+ python where asyncio is not available. |
119 |
+ |
120 |
+ @rtype: asyncio.AbstractChildWatcher |
121 |
+ @return: the internal event loop's AbstractChildWatcher interface |
122 |
+ """ |
123 |
+ return _real_asyncio.get_child_watcher() |
124 |
+ |
125 |
+ @property |
126 |
+ def _asyncio_wrapper(self): |
127 |
+ """ |
128 |
+ Portage internals use this as a layer of indirection in cases |
129 |
+ where a wrapper around an asyncio.AbstractEventLoop implementation |
130 |
+ is needed for purposes of compatiblity. |
131 |
+ |
132 |
+ @rtype: asyncio.AbstractEventLoop |
133 |
+ @return: the internal event loop's AbstractEventLoop interface |
134 |
+ """ |
135 |
+ return self |
136 |
|
137 |
diff --git a/pym/portage/util/_eventloop/global_event_loop.py b/pym/portage/util/_eventloop/global_event_loop.py |
138 |
index a3ee9248d..2f6371dc1 100644 |
139 |
--- a/pym/portage/util/_eventloop/global_event_loop.py |
140 |
+++ b/pym/portage/util/_eventloop/global_event_loop.py |
141 |
@@ -2,10 +2,13 @@ |
142 |
# Distributed under the terms of the GNU General Public License v2 |
143 |
|
144 |
import os |
145 |
+import sys |
146 |
|
147 |
from .EventLoop import EventLoop |
148 |
+from portage.util._eventloop.asyncio_event_loop import AsyncioEventLoop |
149 |
|
150 |
-_default_constructor = EventLoop |
151 |
+_asyncio_enabled = sys.version_info >= (3, 4) |
152 |
+_default_constructor = AsyncioEventLoop if _asyncio_enabled else EventLoop |
153 |
|
154 |
# If _default_constructor doesn't support multiprocessing, |
155 |
# then _multiprocessing_constructor is used in subprocesses. |
156 |
|
157 |
diff --git a/pym/portage/util/futures/_asyncio/__init__.py b/pym/portage/util/futures/_asyncio/__init__.py |
158 |
index e62de7a69..1273afa02 100644 |
159 |
--- a/pym/portage/util/futures/_asyncio/__init__.py |
160 |
+++ b/pym/portage/util/futures/_asyncio/__init__.py |
161 |
@@ -20,6 +20,11 @@ __all__ = ( |
162 |
'wait', |
163 |
) |
164 |
|
165 |
+try: |
166 |
+ import asyncio as _real_asyncio |
167 |
+except ImportError: |
168 |
+ _real_asyncio = None |
169 |
+ |
170 |
try: |
171 |
import threading |
172 |
except ImportError: |
173 |
@@ -29,6 +34,8 @@ import portage |
174 |
portage.proxy.lazyimport.lazyimport(globals(), |
175 |
'portage.util.futures.unix_events:DefaultEventLoopPolicy', |
176 |
) |
177 |
+from portage.util._eventloop.asyncio_event_loop import AsyncioEventLoop as _AsyncioEventLoop |
178 |
+from portage.util._eventloop.global_event_loop import _asyncio_enabled |
179 |
from portage.util.futures.futures import ( |
180 |
CancelledError, |
181 |
Future, |
182 |
@@ -162,3 +169,13 @@ def _wrap_loop(loop=None): |
183 |
@return: event loop |
184 |
""" |
185 |
return loop or get_event_loop() |
186 |
+ |
187 |
+ |
188 |
+if _asyncio_enabled: |
189 |
+ get_event_loop_policy = _real_asyncio.get_event_loop_policy |
190 |
+ set_event_loop_policy = _real_asyncio.set_event_loop_policy |
191 |
+ |
192 |
+ def _wrap_loop(loop=None): |
193 |
+ loop = loop or get_event_loop() |
194 |
+ return (loop if hasattr(loop, '_asyncio_wrapper') |
195 |
+ else _AsyncioEventLoop(loop=loop)) |
196 |
|
197 |
diff --git a/pym/portage/util/futures/unix_events.py b/pym/portage/util/futures/unix_events.py |
198 |
index 00f522b61..ce520db00 100644 |
199 |
--- a/pym/portage/util/futures/unix_events.py |
200 |
+++ b/pym/portage/util/futures/unix_events.py |
201 |
@@ -7,6 +7,7 @@ __all__ = ( |
202 |
) |
203 |
|
204 |
try: |
205 |
+ import asyncio as _real_asyncio |
206 |
from asyncio.base_subprocess import BaseSubprocessTransport as _BaseSubprocessTransport |
207 |
from asyncio.unix_events import AbstractChildWatcher as _AbstractChildWatcher |
208 |
from asyncio.transports import ( |
209 |
@@ -14,6 +15,7 @@ try: |
210 |
WriteTransport as _WriteTransport, |
211 |
) |
212 |
except ImportError: |
213 |
+ _real_asyncio = None |
214 |
_AbstractChildWatcher = object |
215 |
_BaseSubprocessTransport = object |
216 |
_ReadTransport = object |
217 |
@@ -30,6 +32,7 @@ import subprocess |
218 |
import sys |
219 |
|
220 |
from portage.util._eventloop.global_event_loop import ( |
221 |
+ _asyncio_enabled, |
222 |
global_event_loop as _global_event_loop, |
223 |
) |
224 |
from portage.util.futures import ( |
225 |
@@ -678,4 +681,42 @@ class _PortageEventLoopPolicy(events.AbstractEventLoopPolicy): |
226 |
return _global_event_loop()._asyncio_child_watcher |
227 |
|
228 |
|
229 |
-DefaultEventLoopPolicy = _PortageEventLoopPolicy |
230 |
+class _AsyncioEventLoopPolicy(_PortageEventLoopPolicy): |
231 |
+ """ |
232 |
+ Implementation of asyncio.AbstractEventLoopPolicy based on asyncio's |
233 |
+ event loop. This supports running event loops in forks, |
234 |
+ which is not supported by the default asyncio event loop policy, |
235 |
+ see https://bugs.python.org/issue22087 and also |
236 |
+ https://bugs.python.org/issue29703 which affects pypy3-5.10.1. |
237 |
+ """ |
238 |
+ _MAIN_PID = os.getpid() |
239 |
+ |
240 |
+ def __init__(self): |
241 |
+ super(_AsyncioEventLoopPolicy, self).__init__() |
242 |
+ self._default_policy = _real_asyncio.DefaultEventLoopPolicy() |
243 |
+ |
244 |
+ def get_event_loop(self): |
245 |
+ """ |
246 |
+ Get the event loop for the current context. |
247 |
+ |
248 |
+ Returns an event loop object implementing the AbstractEventLoop |
249 |
+ interface. |
250 |
+ |
251 |
+ @rtype: asyncio.AbstractEventLoop (or compatible) |
252 |
+ @return: the current event loop policy |
253 |
+ """ |
254 |
+ if os.getpid() == self._MAIN_PID: |
255 |
+ return self._default_policy.get_event_loop() |
256 |
+ else: |
257 |
+ return super(_AsyncioEventLoopPolicy, self).get_event_loop() |
258 |
+ |
259 |
+ def get_child_watcher(self): |
260 |
+ """Get the watcher for child processes.""" |
261 |
+ if os.getpid() == self._MAIN_PID: |
262 |
+ return self._default_policy.get_child_watcher() |
263 |
+ else: |
264 |
+ return super(_AsyncioEventLoopPolicy, self).get_child_watcher() |
265 |
+ |
266 |
+ |
267 |
+DefaultEventLoopPolicy = (_AsyncioEventLoopPolicy if _asyncio_enabled |
268 |
+ else _PortageEventLoopPolicy) |