Gentoo Archives: gentoo-commits

From: Zac Medico <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: pym/portage/util/_async/, pym/portage/util/_eventloop/, ...
Date: Tue, 08 May 2018 16:57:51
Message-Id: 1525797723.96cc073263910e7c1b0f48cc08a4db4b56ffe408.zmedico@gentoo
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)