Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH 1/5] EventLoop: implement call_soon for asyncio compat (bug 591760)
Date: Fri, 24 Mar 2017 02:55:41
Message-Id: 20170324025500.19518-1-zmedico@gentoo.org
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

Replies