1 |
On Sun, 26 Mar 2017 03:13:11 -0700 |
2 |
Zac Medico <zmedico@g.o> wrote: |
3 |
|
4 |
> Implement the add_done_callback and remove_done_callback methods, |
5 |
> since they are required in order to make further progress toward |
6 |
> asyncio compatibility. |
7 |
> |
8 |
> Also implement the AbstractEventLoop create_future method for the |
9 |
> EventLoop class, so that it returns an instance of _EventLoopFuture. |
10 |
> EventLoop currently does not implement some of the |
11 |
> asyncio.AbstractEventLoop methods that asyncio.Future requires for |
12 |
> its add_done_callback implementation, and the create_future method |
13 |
> conveniently solves this problem. |
14 |
> |
15 |
> X-Gentoo-bug: 591760 |
16 |
> X-Gentoo-bug-url: https://bugs.gentoo.org/show_bug.cgi?id=591760 |
17 |
> --- |
18 |
> pym/portage/tests/ebuild/test_ipc_daemon.py | 3 +- |
19 |
> .../tests/util/eventloop/test_call_soon_fifo.py | 6 +- |
20 |
> pym/portage/tests/util/futures/__init__.py | 0 |
21 |
> pym/portage/tests/util/futures/__test__.py | 0 |
22 |
> .../tests/util/futures/test_done_callback.py | 35 +++++++++ |
23 |
> pym/portage/util/_async/SchedulerInterface.py | 3 +- |
24 |
> pym/portage/util/_eventloop/EventLoop.py | 14 ++++ |
25 |
> pym/portage/util/futures/futures.py | 82 |
26 |
> ++++++++++++++++++++-- 8 files changed, 132 insertions(+), 11 |
27 |
> deletions(-) create mode 100644 |
28 |
> pym/portage/tests/util/futures/__init__.py create mode 100644 |
29 |
> pym/portage/tests/util/futures/__test__.py create mode 100644 |
30 |
> pym/portage/tests/util/futures/test_done_callback.py |
31 |
> |
32 |
> diff --git a/pym/portage/tests/ebuild/test_ipc_daemon.py |
33 |
> b/pym/portage/tests/ebuild/test_ipc_daemon.py index 68f139a..fc79165 |
34 |
> 100644 --- a/pym/portage/tests/ebuild/test_ipc_daemon.py |
35 |
> +++ b/pym/portage/tests/ebuild/test_ipc_daemon.py |
36 |
> @@ -16,7 +16,6 @@ from portage.util import ensure_dirs |
37 |
> from portage.util._async.ForkProcess import ForkProcess |
38 |
> from portage.util._async.TaskScheduler import TaskScheduler |
39 |
> from portage.util._eventloop.global_event_loop import |
40 |
> global_event_loop -from portage.util.futures.futures import Future |
41 |
> from _emerge.SpawnProcess import SpawnProcess |
42 |
> from _emerge.EbuildBuildDir import EbuildBuildDir |
43 |
> from _emerge.EbuildIpcDaemon import EbuildIpcDaemon |
44 |
> @@ -150,7 +149,7 @@ class IpcDaemonTestCase(TestCase): |
45 |
> self._run_done.set_result(True) |
46 |
> |
47 |
> def _run(self, event_loop, task_scheduler, timeout): |
48 |
> - self._run_done = Future() |
49 |
> + self._run_done = event_loop.create_future() |
50 |
> timeout_id = event_loop.timeout_add(timeout, |
51 |
> self._timeout_callback, task_scheduler) |
52 |
> task_scheduler.addExitListener(self._exit_callback) |
53 |
> diff --git a/pym/portage/tests/util/eventloop/test_call_soon_fifo.py |
54 |
> b/pym/portage/tests/util/eventloop/test_call_soon_fifo.py index |
55 |
> 5ecc13f..f970c67 100644 --- |
56 |
> a/pym/portage/tests/util/eventloop/test_call_soon_fifo.py +++ |
57 |
> b/pym/portage/tests/util/eventloop/test_call_soon_fifo.py @@ -7,22 |
58 |
> +7,22 @@ import random from portage import os |
59 |
> from portage.tests import TestCase |
60 |
> from portage.util._eventloop.global_event_loop import |
61 |
> global_event_loop -from portage.util.futures.futures import Future |
62 |
> + |
63 |
> |
64 |
> class CallSoonFifoTestCase(TestCase): |
65 |
> |
66 |
> def testCallSoonFifo(self): |
67 |
> |
68 |
> + event_loop = global_event_loop() |
69 |
> inputs = [random.random() for index in range(10)] |
70 |
> outputs = [] |
71 |
> - finished = Future() |
72 |
> + finished = event_loop.create_future() |
73 |
> |
74 |
> def add_output(value): |
75 |
> outputs.append(value) |
76 |
> if len(outputs) == len(inputs): |
77 |
> finished.set_result(True) |
78 |
> |
79 |
> - event_loop = global_event_loop() |
80 |
> for value in inputs: |
81 |
> event_loop.call_soon(functools.partial(add_output, |
82 |
> value)) |
83 |
> diff --git a/pym/portage/tests/util/futures/__init__.py |
84 |
> b/pym/portage/tests/util/futures/__init__.py new file mode 100644 |
85 |
> index 0000000..e69de29 |
86 |
> diff --git a/pym/portage/tests/util/futures/__test__.py |
87 |
> b/pym/portage/tests/util/futures/__test__.py new file mode 100644 |
88 |
> index 0000000..e69de29 |
89 |
> diff --git a/pym/portage/tests/util/futures/test_done_callback.py |
90 |
> b/pym/portage/tests/util/futures/test_done_callback.py new file mode |
91 |
> 100644 index 0000000..76b727b |
92 |
> --- /dev/null |
93 |
> +++ b/pym/portage/tests/util/futures/test_done_callback.py |
94 |
> @@ -0,0 +1,35 @@ |
95 |
> +# Copyright 2017 Gentoo Foundation |
96 |
> +# Distributed under the terms of the GNU General Public License v2 |
97 |
> + |
98 |
> +from portage.tests import TestCase |
99 |
> +from portage.util._eventloop.global_event_loop import |
100 |
> global_event_loop + |
101 |
> + |
102 |
> +class FutureDoneCallbackTestCase(TestCase): |
103 |
> + |
104 |
> + def testFutureDoneCallback(self): |
105 |
> + |
106 |
> + event_loop = global_event_loop() |
107 |
> + |
108 |
> + def done_callback(finished): |
109 |
> + done_callback_called.set_result(True) |
110 |
> + |
111 |
> + done_callback_called = event_loop.create_future() |
112 |
> + finished = event_loop.create_future() |
113 |
> + finished.add_done_callback(done_callback) |
114 |
> + event_loop.call_soon(finished.set_result, True) |
115 |
> + event_loop.run_until_complete(done_callback_called) |
116 |
> + |
117 |
> + def done_callback2(finished): |
118 |
> + done_callback2_called.set_result(True) |
119 |
> + |
120 |
> + done_callback_called = event_loop.create_future() |
121 |
> + done_callback2_called = event_loop.create_future() |
122 |
> + finished = event_loop.create_future() |
123 |
> + finished.add_done_callback(done_callback) |
124 |
> + finished.add_done_callback(done_callback2) |
125 |
> + finished.remove_done_callback(done_callback) |
126 |
> + event_loop.call_soon(finished.set_result, True) |
127 |
> + event_loop.run_until_complete(done_callback2_called) |
128 |
> + |
129 |
> + self.assertFalse(done_callback_called.done()) |
130 |
> diff --git a/pym/portage/util/_async/SchedulerInterface.py |
131 |
> b/pym/portage/util/_async/SchedulerInterface.py index |
132 |
> 6028fd9..21420ae 100644 --- |
133 |
> a/pym/portage/util/_async/SchedulerInterface.py +++ |
134 |
> b/pym/portage/util/_async/SchedulerInterface.py @@ -13,7 +13,8 @@ |
135 |
> class SchedulerInterface(SlotObject): |
136 |
> _event_loop_attrs = ("IO_ERR", "IO_HUP", "IO_IN", |
137 |
> "IO_NVAL", "IO_OUT", "IO_PRI", |
138 |
> - "call_soon", "call_soon_threadsafe", |
139 |
> "child_watch_add", |
140 |
> + "call_soon", "call_soon_threadsafe", |
141 |
> + "child_watch_add", "create_future", |
142 |
> "idle_add", "io_add_watch", "iteration", |
143 |
> "run_until_complete", "source_remove", "timeout_add") |
144 |
> |
145 |
> diff --git a/pym/portage/util/_eventloop/EventLoop.py |
146 |
> b/pym/portage/util/_eventloop/EventLoop.py index 308157b..712838e |
147 |
> 100644 --- a/pym/portage/util/_eventloop/EventLoop.py |
148 |
> +++ b/pym/portage/util/_eventloop/EventLoop.py |
149 |
> @@ -22,6 +22,11 @@ try: |
150 |
> except ImportError: |
151 |
> import dummy_threading as threading |
152 |
> |
153 |
> +import portage |
154 |
> +portage.proxy.lazyimport.lazyimport(globals(), |
155 |
> + 'portage.util.futures.futures:_EventLoopFuture', |
156 |
> +) |
157 |
> + |
158 |
> from portage import OrderedDict |
159 |
> from portage.util import writemsg_level |
160 |
> from ..SlotObject import SlotObject |
161 |
> @@ -157,6 +162,15 @@ class EventLoop(object): |
162 |
> self._sigchld_src_id = None |
163 |
> self._pid = os.getpid() |
164 |
> |
165 |
> + def create_future(self): |
166 |
> + """ |
167 |
> + Create a Future object attached to the loop. This |
168 |
> returns |
169 |
> + an instance of _EventLoopFuture, because EventLoop |
170 |
> is currently |
171 |
> + missing some of the asyncio.AbstractEventLoop |
172 |
> methods that |
173 |
> + asyncio.Future requires. |
174 |
> + """ |
175 |
> + return _EventLoopFuture(loop=self) |
176 |
> + |
177 |
> def _new_source_id(self): |
178 |
> """ |
179 |
> Generate a new source id. This method is thread-safe. |
180 |
> diff --git a/pym/portage/util/futures/futures.py |
181 |
> b/pym/portage/util/futures/futures.py index c648f10..dd913a1 100644 |
182 |
> --- a/pym/portage/util/futures/futures.py |
183 |
> +++ b/pym/portage/util/futures/futures.py |
184 |
> @@ -23,10 +23,6 @@ except ImportError: |
185 |
> |
186 |
> from portage.exception import PortageException |
187 |
> |
188 |
> - _PENDING = 'PENDING' |
189 |
> - _CANCELLED = 'CANCELLED' |
190 |
> - _FINISHED = 'FINISHED' |
191 |
> - |
192 |
> class Error(PortageException): |
193 |
> pass |
194 |
> |
195 |
> @@ -37,12 +33,40 @@ except ImportError: |
196 |
> class InvalidStateError(Error): |
197 |
> pass |
198 |
> |
199 |
> - class Future(object): |
200 |
> + Future = None |
201 |
> + |
202 |
> +from portage.util._eventloop.global_event_loop import |
203 |
> global_event_loop + |
204 |
> +_PENDING = 'PENDING' |
205 |
> +_CANCELLED = 'CANCELLED' |
206 |
> +_FINISHED = 'FINISHED' |
207 |
> + |
208 |
> +class _EventLoopFuture(object): |
209 |
> + """ |
210 |
> + This class provides (a subset of) the asyncio.Future |
211 |
> interface, for |
212 |
> + use with the EventLoop class, because EventLoop is |
213 |
> currently |
214 |
> + missing some of the asyncio.AbstractEventLoop |
215 |
> methods that |
216 |
> + asyncio.Future requires. |
217 |
> + """ |
218 |
> |
219 |
> # Class variables serving as defaults for instance |
220 |
> variables. _state = _PENDING |
221 |
> _result = None |
222 |
> _exception = None |
223 |
> + _loop = None |
224 |
> + |
225 |
> + def __init__(self, loop=None): |
226 |
> + """Initialize the future. |
227 |
> + |
228 |
> + The optional loop argument allows explicitly |
229 |
> setting the event |
230 |
> + loop object used by the future. If it's not |
231 |
> provided, the future uses |
232 |
> + the default event loop. |
233 |
> + """ |
234 |
> + if loop is None: |
235 |
> + self._loop = global_event_loop() |
236 |
> + else: |
237 |
> + self._loop = loop |
238 |
> + self._callbacks = [] |
239 |
> |
240 |
> def cancel(self): |
241 |
> """Cancel the future and schedule callbacks. |
242 |
> @@ -54,8 +78,27 @@ except ImportError: |
243 |
> if self._state != _PENDING: |
244 |
> return False |
245 |
> self._state = _CANCELLED |
246 |
> + self._schedule_callbacks() |
247 |
> return True |
248 |
> |
249 |
> + def _schedule_callbacks(self): |
250 |
> + """Internal: Ask the event loop to call all |
251 |
> callbacks. + |
252 |
> + The callbacks are scheduled to be called as |
253 |
> soon as possible. Also |
254 |
> + clears the callback list. |
255 |
> + """ |
256 |
> + callbacks = self._callbacks[:] |
257 |
> + if not callbacks: |
258 |
> + return |
259 |
> + |
260 |
> + self._callbacks[:] = [] |
261 |
> + for callback in callbacks: |
262 |
> + self._loop.call_soon(callback, self) |
263 |
> + |
264 |
> + def cancelled(self): |
265 |
> + """Return True if the future was |
266 |
> cancelled.""" |
267 |
> + return self._state == _CANCELLED |
268 |
> + |
269 |
> def done(self): |
270 |
> """Return True if the future is done. |
271 |
> |
272 |
> @@ -93,6 +136,29 @@ except ImportError: |
273 |
> raise InvalidStateError('Exception |
274 |
> is not set.') return self._exception |
275 |
> |
276 |
> + def add_done_callback(self, fn): |
277 |
> + """Add a callback to be run when the future |
278 |
> becomes done. + |
279 |
> + The callback is called with a single |
280 |
> argument - the future object. If |
281 |
> + the future is already done when this is |
282 |
> called, the callback is |
283 |
> + scheduled with call_soon. |
284 |
> + """ |
285 |
> + if self._state != _PENDING: |
286 |
> + self._loop.call_soon(fn, self) |
287 |
> + else: |
288 |
> + self._callbacks.append(fn) |
289 |
> + |
290 |
> + def remove_done_callback(self, fn): |
291 |
> + """Remove all instances of a callback from |
292 |
> the "call when done" list. + |
293 |
> + Returns the number of callbacks removed. |
294 |
> + """ |
295 |
> + filtered_callbacks = [f for f in |
296 |
> self._callbacks if f != fn] |
297 |
> + removed_count = len(self._callbacks) - |
298 |
> len(filtered_callbacks) |
299 |
> + if removed_count: |
300 |
> + self._callbacks[:] = |
301 |
> filtered_callbacks |
302 |
> + return removed_count |
303 |
> + |
304 |
> def set_result(self, result): |
305 |
> """Mark the future done and set its result. |
306 |
> |
307 |
> @@ -103,6 +169,7 @@ except ImportError: |
308 |
> raise InvalidStateError('{}: |
309 |
> {!r}'.format(self._state, self)) self._result = result |
310 |
> self._state = _FINISHED |
311 |
> + self._schedule_callbacks() |
312 |
> |
313 |
> def set_exception(self, exception): |
314 |
> """Mark the future done and set an exception. |
315 |
> @@ -116,3 +183,8 @@ except ImportError: |
316 |
> exception = exception() |
317 |
> self._exception = exception |
318 |
> self._state = _FINISHED |
319 |
> + self._schedule_callbacks() |
320 |
> + |
321 |
> + |
322 |
> +if Future is None: |
323 |
> + Future = _EventLoopFuture |
324 |
|
325 |
looks fine... /me ignoring the lack of parameters descriptions in the |
326 |
docstrings |
327 |
|
328 |
-- |
329 |
Brian Dolbec <dolsen> |