1 |
Since asyncio.AbstractEventLoop has no equivalent to the idle |
2 |
callbacks implemented by the EventLoop.idle_add method, it is |
3 |
necessary to implement the AbstractEventLoop.call_soon and |
4 |
call_soon_threadsafe methods, so that idle_add usage can |
5 |
eventually be eliminated. |
6 |
|
7 |
X-Gentoo-bug: 591760 |
8 |
X-Gentoo-bug-url: https://bugs.gentoo.org/show_bug.cgi?id=591760 |
9 |
--- |
10 |
.../tests/util/eventloop/test_call_soon_fifo.py | 30 ++++++++++ |
11 |
pym/portage/util/_async/SchedulerInterface.py | 5 +- |
12 |
pym/portage/util/_eventloop/EventLoop.py | 67 +++++++++++++++++++++- |
13 |
3 files changed, 99 insertions(+), 3 deletions(-) |
14 |
create mode 100644 pym/portage/tests/util/eventloop/test_call_soon_fifo.py |
15 |
|
16 |
diff --git a/pym/portage/tests/util/eventloop/test_call_soon_fifo.py b/pym/portage/tests/util/eventloop/test_call_soon_fifo.py |
17 |
new file mode 100644 |
18 |
index 0000000..5ecc13f |
19 |
--- /dev/null |
20 |
+++ b/pym/portage/tests/util/eventloop/test_call_soon_fifo.py |
21 |
@@ -0,0 +1,30 @@ |
22 |
+# Copyright 2017 Gentoo Foundation |
23 |
+# Distributed under the terms of the GNU General Public License v2 |
24 |
+ |
25 |
+import functools |
26 |
+import random |
27 |
+ |
28 |
+from portage import os |
29 |
+from portage.tests import TestCase |
30 |
+from portage.util._eventloop.global_event_loop import global_event_loop |
31 |
+from portage.util.futures.futures import Future |
32 |
+ |
33 |
+class CallSoonFifoTestCase(TestCase): |
34 |
+ |
35 |
+ def testCallSoonFifo(self): |
36 |
+ |
37 |
+ inputs = [random.random() for index in range(10)] |
38 |
+ outputs = [] |
39 |
+ finished = Future() |
40 |
+ |
41 |
+ def add_output(value): |
42 |
+ outputs.append(value) |
43 |
+ if len(outputs) == len(inputs): |
44 |
+ finished.set_result(True) |
45 |
+ |
46 |
+ event_loop = global_event_loop() |
47 |
+ for value in inputs: |
48 |
+ event_loop.call_soon(functools.partial(add_output, value)) |
49 |
+ |
50 |
+ event_loop.run_until_complete(finished) |
51 |
+ self.assertEqual(inputs, outputs) |
52 |
diff --git a/pym/portage/util/_async/SchedulerInterface.py b/pym/portage/util/_async/SchedulerInterface.py |
53 |
index 2ab668e..6028fd9 100644 |
54 |
--- a/pym/portage/util/_async/SchedulerInterface.py |
55 |
+++ b/pym/portage/util/_async/SchedulerInterface.py |
56 |
@@ -13,8 +13,9 @@ class SchedulerInterface(SlotObject): |
57 |
|
58 |
_event_loop_attrs = ("IO_ERR", "IO_HUP", "IO_IN", |
59 |
"IO_NVAL", "IO_OUT", "IO_PRI", |
60 |
- "child_watch_add", "idle_add", "io_add_watch", |
61 |
- "iteration", "source_remove", "timeout_add") |
62 |
+ "call_soon", "call_soon_threadsafe", "child_watch_add", |
63 |
+ "idle_add", "io_add_watch", "iteration", "run_until_complete", |
64 |
+ "source_remove", "timeout_add") |
65 |
|
66 |
__slots__ = _event_loop_attrs + ("_event_loop", "_is_background") |
67 |
|
68 |
diff --git a/pym/portage/util/_eventloop/EventLoop.py b/pym/portage/util/_eventloop/EventLoop.py |
69 |
index 8f13de3..308157b 100644 |
70 |
--- a/pym/portage/util/_eventloop/EventLoop.py |
71 |
+++ b/pym/portage/util/_eventloop/EventLoop.py |
72 |
@@ -22,6 +22,7 @@ try: |
73 |
except ImportError: |
74 |
import dummy_threading as threading |
75 |
|
76 |
+from portage import OrderedDict |
77 |
from portage.util import writemsg_level |
78 |
from ..SlotObject import SlotObject |
79 |
from .PollConstants import PollConstants |
80 |
@@ -54,6 +55,38 @@ class EventLoop(object): |
81 |
__slots__ = ("args", "function", "calling", "interval", "source_id", |
82 |
"timestamp") |
83 |
|
84 |
+ class _handle(object): |
85 |
+ """ |
86 |
+ A callback wrapper object, compatible with asyncio.Handle. |
87 |
+ """ |
88 |
+ __slots__ = ("_callback_id", "_loop") |
89 |
+ |
90 |
+ def __init__(self, callback_id, loop): |
91 |
+ self._callback_id = callback_id |
92 |
+ self._loop = loop |
93 |
+ |
94 |
+ def cancel(self): |
95 |
+ """ |
96 |
+ Cancel the call. If the callback is already canceled or executed, |
97 |
+ this method has no effect. |
98 |
+ """ |
99 |
+ self._loop.source_remove(self._callback_id) |
100 |
+ |
101 |
+ class _call_soon_callback(object): |
102 |
+ """ |
103 |
+ Wraps a call_soon callback, and always returns False, since these |
104 |
+ callbacks are only supposed to run once. |
105 |
+ """ |
106 |
+ __slots__ = ("_args", "_callback") |
107 |
+ |
108 |
+ def __init__(self, callback, args): |
109 |
+ self._callback = callback |
110 |
+ self._args = args |
111 |
+ |
112 |
+ def __call__(self): |
113 |
+ self._callback(*self._args) |
114 |
+ return False |
115 |
+ |
116 |
def __init__(self, main=True): |
117 |
""" |
118 |
@param main: If True then this is a singleton instance for use |
119 |
@@ -70,7 +103,9 @@ class EventLoop(object): |
120 |
self._poll_event_handler_ids = {} |
121 |
# Increment id for each new handler. |
122 |
self._event_handler_id = 0 |
123 |
- self._idle_callbacks = {} |
124 |
+ # Use OrderedDict in order to emulate the FIFO queue behavior |
125 |
+ # of the AbstractEventLoop.call_soon method. |
126 |
+ self._idle_callbacks = OrderedDict() |
127 |
self._timeout_handlers = {} |
128 |
self._timeout_interval = None |
129 |
|
130 |
@@ -399,6 +434,9 @@ class EventLoop(object): |
131 |
automatically removed from the list of event sources and will |
132 |
not be called again. This method is thread-safe. |
133 |
|
134 |
+ The idle_add method is deprecated. Use the call_soon and |
135 |
+ call_soon_threadsafe methods instead. |
136 |
+ |
137 |
@type callback: callable |
138 |
@param callback: a function to call |
139 |
@rtype: int |
140 |
@@ -592,6 +630,33 @@ class EventLoop(object): |
141 |
|
142 |
return future.result() |
143 |
|
144 |
+ def call_soon(self, callback, *args): |
145 |
+ """ |
146 |
+ Arrange for a callback to be called as soon as possible. The callback |
147 |
+ is called after call_soon() returns, when control returns to the event |
148 |
+ loop. |
149 |
+ |
150 |
+ This operates as a FIFO queue, callbacks are called in the order in |
151 |
+ which they are registered. Each callback will be called exactly once. |
152 |
+ |
153 |
+ Any positional arguments after the callback will be passed to the |
154 |
+ callback when it is called. |
155 |
+ |
156 |
+ An object compatible with asyncio.Handle is returned, which can |
157 |
+ be used to cancel the callback. |
158 |
+ |
159 |
+ @type callback: callable |
160 |
+ @param callback: a function to call |
161 |
+ @return: a handle which can be used to cancel the callback |
162 |
+ @rtype: asyncio.Handle (or compatible) |
163 |
+ """ |
164 |
+ return self._handle(self.idle_add( |
165 |
+ self._call_soon_callback(callback, args)), self) |
166 |
+ |
167 |
+ # The call_soon method inherits thread safety from the idle_add method. |
168 |
+ call_soon_threadsafe = call_soon |
169 |
+ |
170 |
+ |
171 |
_can_poll_device = None |
172 |
|
173 |
def can_poll_device(): |
174 |
-- |
175 |
2.10.2 |