├── .hgignore
├── .hgtags
├── COPYING
├── MANIFEST.in
├── Makefile
├── README
├── aioeventlet.py
├── doc
├── Makefile
├── changelog.rst
├── conf.py
├── index.rst
├── make.bat
├── openstack.rst
├── poplar_hawk-moth.jpg
├── status.rst
└── using.rst
├── run_aiotest.py
├── runtests.py
├── setup.py
├── tests
├── __init__.py
├── test_eventlet.py
└── test_greenlet.py
└── tox.ini
/.hgignore:
--------------------------------------------------------------------------------
1 | .*\.py[co]$
2 | .*~$
3 | .*\.orig$
4 | .*\#.*$
5 | .*@.*$
6 | \.coverage$
7 | htmlcov$
8 | \.DS_Store$
9 | venv$
10 | distribute_setup.py$
11 | distribute-\d+.\d+.\d+.tar.gz$
12 | build$
13 | dist$
14 | .*\.egg-info$
15 |
16 | # Directory created by the "tox" command (ex: tox -e py27)
17 | .tox
18 |
--------------------------------------------------------------------------------
/.hgtags:
--------------------------------------------------------------------------------
1 | da02823972c359f120cc6716bf36d4dfce7081d1 0.1
2 | 7fc582ee385b16fc0e34d2fe9b8ebbbe181dcef1 0.2
3 | 2e38117c2b48bed5af449b803a7a424bb2e9f28b 0.3
4 | 0368b4b558fefaf78a9d60b785184f0db6d57593 0.4
5 | 7b01a843558dd9e69bf569fc6fc970d39aa036ec 0.5
6 | 1c5b3f16c94d7649991475aebddd333b7c8fb4dc 0.5.1
7 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include COPYING
2 | include Makefile
3 | include runtests.py
4 | include run_aiotest.py
5 | include tests/*.py
6 | include tox.ini
7 |
8 | include doc/conf.py doc/make.bat doc/Makefile
9 | include doc/*.rst doc/*.jpg
10 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: doc
2 |
3 | test:
4 | tox
5 | doc:
6 | make -C doc html
7 | clean:
8 | rm -rf build dist aioeventlet.egg-info .tox
9 | find -name "*.pyc" -delete
10 | find -name "__pycache__" -exec rm -rf {} \;
11 | make -C doc clean
12 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | aioeventlet implements the asyncio API (PEP 3156) on top of eventlet. It makes
2 | possible to write asyncio code in a project currently written for eventlet.
3 |
4 | aioeventlet allows to use greenthreads in asyncio coroutines, and to use
5 | asyncio coroutines, tasks and futures in greenthreads: see ``link_future()``
6 | and ``wrap_greenthread()`` functions.
7 |
8 | The main visible difference between aioeventlet and trollius is the behaviour
9 | of ``run_forever()``: ``run_forever()`` blocks with trollius, whereas it runs
10 | in a greenthread with aioeventlet. It means that aioeventlet event loop can run
11 | in an greenthread while the Python main thread runs other greenthreads in
12 | parallel.
13 |
14 | * `aioeventlet documentation `_
15 | * `aioeventlet project in the Python Cheeseshop (PyPI)
16 | `_
17 | * `aioeventlet project at Bitbucket `_
18 | * Copyright/license: Open source, Apache 2.0. Enjoy!
19 |
--------------------------------------------------------------------------------
/aioeventlet.py:
--------------------------------------------------------------------------------
1 | import eventlet.hubs.hub
2 | import greenlet
3 | import logging
4 | import signal
5 | import sys
6 | socket = eventlet.patcher.original('socket')
7 | threading = eventlet.patcher.original('threading')
8 |
9 | logger = logging.getLogger('aioeventlet')
10 |
11 | try:
12 | import asyncio
13 |
14 | if sys.platform == 'win32':
15 | from asyncio.windows_utils import socketpair
16 | else:
17 | socketpair = socket.socketpair
18 | except ImportError:
19 | import trollius as asyncio
20 |
21 | if sys.platform == 'win32':
22 | from trollius.windows_utils import socketpair
23 | else:
24 | socketpair = socket.socketpair
25 |
26 | if eventlet.patcher.is_monkey_patched('socket'):
27 | # trollius must use call original socket and threading functions.
28 | # Examples: socket.socket(), socket.socketpair(),
29 | # threading.current_thread().
30 | asyncio.base_events.socket = socket
31 | asyncio.base_events.threading = threading
32 | if hasattr(threading, 'get_ident'):
33 | asyncio.base_events._get_thread_ident = threading.get_ident
34 | else:
35 | # Python 2
36 | asyncio.base_events._get_thread_ident = threading._get_ident
37 | asyncio.events.threading = threading
38 | if sys.platform == 'win32':
39 | asyncio.windows_events.socket = socket
40 | asyncio.windows_utils.socket = socket
41 | else:
42 | asyncio.unix_events.socket = socket
43 | asyncio.unix_events.threading = threading
44 | # FIXME: patch also trollius.py3_ssl
45 |
46 | # BaseDefaultEventLoopPolicy._Local must inherit from threading.local
47 | # of the original threading module, not the patched threading module
48 | class _Local(threading.local):
49 | _loop = None
50 | _set_called = False
51 |
52 | asyncio.events.BaseDefaultEventLoopPolicy._Local = _Local
53 |
54 | _EVENT_READ = asyncio.selectors.EVENT_READ
55 | _EVENT_WRITE = asyncio.selectors.EVENT_WRITE
56 | _HUB_READ = eventlet.hubs.hub.READ
57 | _HUB_WRITE = eventlet.hubs.hub.WRITE
58 |
59 | # Eventlet 0.15 or newer?
60 | _EVENTLET15 = hasattr(eventlet.hubs.hub.noop, 'mark_as_closed')
61 |
62 |
63 | class _TpoolExecutor(object):
64 | def __init__(self, loop):
65 | import eventlet.tpool
66 | self._loop = loop
67 | self._tpool = eventlet.tpool
68 |
69 | def submit(self, fn, *args, **kwargs):
70 | f = asyncio.Future(loop=self._loop)
71 | try:
72 | res = self._tpool.execute(fn, *args, **kwargs)
73 | except Exception as exc:
74 | f.set_exception(exc)
75 | else:
76 | f.set_result(res)
77 | return f
78 |
79 | def shutdown(self, wait=True):
80 | self._tpool.killall()
81 |
82 |
83 | class _Selector(asyncio.selectors._BaseSelectorImpl):
84 | def __init__(self, loop, hub):
85 | super(_Selector, self).__init__()
86 | # fd => events
87 | self._notified = {}
88 | self._loop = loop
89 | self._hub = hub
90 | # eventlet.event.Event() used by FD notifiers to wake up select()
91 | self._event = None
92 |
93 | def close(self):
94 | keys = list(self.get_map().values())
95 | for key in keys:
96 | self.unregister(key.fd)
97 | super(_Selector, self).close()
98 |
99 | def _add(self, fd, event):
100 | if event == _EVENT_READ:
101 | event_type = _HUB_READ
102 | func = self._notify_read
103 | else:
104 | event_type = _HUB_WRITE
105 | func = self._notify_write
106 |
107 | if _EVENTLET15:
108 | self._hub.add(event_type, fd, func, self._throwback, None)
109 | else:
110 | self._hub.add(event_type, fd, func)
111 |
112 | def register(self, fileobj, events, data=None):
113 | key = super(_Selector, self).register(fileobj, events, data)
114 | if events & _EVENT_READ:
115 | self._add(key.fd, _EVENT_READ)
116 | if events & _EVENT_WRITE:
117 | self._add(key.fd, _EVENT_WRITE)
118 | return key
119 |
120 | def _remove(self, fd, event):
121 | if event == _EVENT_READ:
122 | event_type = _HUB_READ
123 | else:
124 | event_type = _HUB_WRITE
125 | try:
126 | listener = self._hub.listeners[event_type][fd]
127 | except KeyError:
128 | pass
129 | else:
130 | self._hub.remove(listener)
131 |
132 | def unregister(self, fileobj):
133 | key = super(_Selector, self).unregister(fileobj)
134 | self._remove(key.fd, _EVENT_READ)
135 | self._remove(key.fd, _EVENT_WRITE)
136 | return key
137 |
138 | def _notify(self, fd, event):
139 | if fd in self._notified:
140 | self._notified[fd] |= event
141 | else:
142 | self._notified[fd] = event
143 | if self._event is not None and not self._event.ready():
144 | # wakeup the select() method
145 | self._event.send("ready")
146 |
147 | def _notify_read(self, fd):
148 | self._notify(fd, _EVENT_READ)
149 |
150 | def _notify_write(self, fd):
151 | self._notify(fd, _EVENT_WRITE)
152 |
153 | def _throwback(self, fd):
154 | # FIXME: do something with the FD in this case?
155 | pass
156 |
157 | def _read_events(self):
158 | notified = self._notified
159 | self._notified = {}
160 | ready = []
161 | for fd, events in notified.items():
162 | key = self.get_key(fd)
163 | ready.append((key, events & key.events))
164 | return ready
165 |
166 | def select(self, timeout):
167 | events = self._read_events()
168 | if events:
169 | return events
170 |
171 | self._event = eventlet.event.Event()
172 | try:
173 | if timeout is not None:
174 | def timeout_cb(event):
175 | if event.ready():
176 | return
177 | event.send('timeout')
178 |
179 | eventlet.spawn_after(timeout, timeout_cb, self._event)
180 |
181 | self._event.wait()
182 | # FIXME: cancel the timeout_cb if wait() returns 'ready'?
183 | else:
184 | # blocking call
185 | self._event.wait()
186 | return self._read_events()
187 | finally:
188 | self._event = None
189 |
190 |
191 | class EventLoop(asyncio.SelectorEventLoop):
192 | def __init__(self):
193 | self._greenthread = None
194 |
195 | # Store a reference to the hub to ensure
196 | # that we always use the same hub
197 | self._hub = eventlet.hubs.get_hub()
198 |
199 | selector = _Selector(self, self._hub)
200 |
201 | super(EventLoop, self).__init__(selector=selector)
202 |
203 | # Force a call to set_debug() to set hub.debug_blocking
204 | self.set_debug(self.get_debug())
205 |
206 | if eventlet.patcher.is_monkey_patched('thread'):
207 | self._default_executor = _TpoolExecutor(self)
208 |
209 | def stop(self):
210 | super(EventLoop, self).stop()
211 | # selector.select() is running: write into the self-pipe to wake up
212 | # the selector
213 | self._write_to_self()
214 |
215 | def call_soon(self, callback, *args):
216 | handle = super(EventLoop, self).call_soon(callback, *args)
217 | if self._selector is not None and self._selector._event:
218 | # selector.select() is running: write into the self-pipe to wake up
219 | # the selector
220 | self._write_to_self()
221 | return handle
222 |
223 | def call_at(self, when, callback, *args):
224 | handle = super(EventLoop, self).call_at(when, callback, *args)
225 | if self._selector is not None and self._selector._event:
226 | # selector.select() is running: write into the self-pipe to wake up
227 | # the selector
228 | self._write_to_self()
229 | return handle
230 |
231 | def set_debug(self, debug):
232 | super(EventLoop, self).set_debug(debug)
233 |
234 | self._hub.debug_exceptions = debug
235 |
236 | # Detect blocking eventlet functions. The feature is implemented with
237 | # signal.alarm() which is is not available on Windows. Signal handlers
238 | # can only be set from the main loop. So detecting blocking functions
239 | # cannot be used on Windows nor from a thread different than the main
240 | # thread.
241 | self._hub.debug_blocking = (
242 | debug
243 | and (sys.platform != 'win32')
244 | and isinstance(threading.current_thread(), threading._MainThread))
245 |
246 | if (self._hub.debug_blocking
247 | and hasattr(self, 'slow_callback_duration')):
248 | self._hub.debug_blocking_resolution = self.slow_callback_duration
249 |
250 | def run_forever(self):
251 | self._greenthread = eventlet.getcurrent()
252 | try:
253 | super(EventLoop, self).run_forever()
254 | finally:
255 | if self._hub.debug_blocking:
256 | # eventlet event loop is still running: cancel the current
257 | # detection of blocking tasks
258 | signal.alarm(0)
259 | self._greenthread = None
260 |
261 | def time(self):
262 | return self._hub.clock()
263 |
264 |
265 | class EventLoopPolicy(asyncio.DefaultEventLoopPolicy):
266 | _loop_factory = EventLoop
267 |
268 |
269 | def wrap_greenthread(gt, loop=None):
270 | """Wrap an eventlet GreenThread, or a greenlet, into a Future object.
271 |
272 | The Future object waits for the completion of a greenthread. The result
273 | or the exception of the greenthread will be stored in the Future object.
274 |
275 | The greenthread must be wrapped before its execution starts. If the
276 | greenthread is running or already finished, an exception is raised.
277 |
278 | For greenlets, the run attribute must be set.
279 | """
280 | if loop is None:
281 | loop = asyncio.get_event_loop()
282 | fut = asyncio.Future(loop=loop)
283 |
284 | if not isinstance(gt, greenlet.greenlet):
285 | raise TypeError("greenthread or greenlet request, not %s"
286 | % type(gt))
287 |
288 | if gt:
289 | raise RuntimeError("wrap_greenthread: the greenthread is running")
290 | if gt.dead:
291 | raise RuntimeError("wrap_greenthread: the greenthread already finished")
292 |
293 | if isinstance(gt, eventlet.greenthread.GreenThread):
294 | orig_main = gt.run
295 | def wrap_func(*args, **kw):
296 | try:
297 | orig_main(*args, **kw)
298 | except Exception as exc:
299 | fut.set_exception(exc)
300 | else:
301 | result = gt.wait()
302 | fut.set_result(result)
303 | gt.run = wrap_func
304 | else:
305 | try:
306 | orig_func = gt.run
307 | except AttributeError:
308 | raise RuntimeError("wrap_greenthread: the run attribute "
309 | "of the greenlet is not set")
310 | def wrap_func(*args, **kw):
311 | try:
312 | result = orig_func(*args, **kw)
313 | except Exception as exc:
314 | fut.set_exception(exc)
315 | else:
316 | fut.set_result(result)
317 | gt.run = wrap_func
318 | return fut
319 |
320 |
321 | def yield_future(future, loop=None):
322 | """Wait for a future, a task, or a coroutine object from a greenthread.
323 |
324 | Yield control other eligible eventlet coroutines until the future is done
325 | (finished successfully or failed with an exception).
326 |
327 | Return the result or raise the exception of the future.
328 |
329 | The function must not be called from the greenthread
330 | running the aioeventlet event loop.
331 | """
332 | future = asyncio.async(future, loop=loop)
333 | if future._loop._greenthread == eventlet.getcurrent():
334 | raise RuntimeError("yield_future() must not be called from "
335 | "the greenthread of the aioeventlet event loop")
336 |
337 | event = eventlet.event.Event()
338 | def done(fut):
339 | try:
340 | result = fut.result()
341 | except Exception as exc:
342 | event.send_exception(exc)
343 | else:
344 | event.send(result)
345 |
346 | future.add_done_callback(done)
347 | return event.wait()
348 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
36 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
38 | @echo " text to make text files"
39 | @echo " man to make manual pages"
40 | @echo " texinfo to make Texinfo files"
41 | @echo " info to make Texinfo files and run them through makeinfo"
42 | @echo " gettext to make PO message catalogs"
43 | @echo " changes to make an overview of all changed/added/deprecated items"
44 | @echo " xml to make Docutils-native XML files"
45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
46 | @echo " linkcheck to check all external links for integrity"
47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
48 |
49 | clean:
50 | rm -rf $(BUILDDIR)/*
51 |
52 | html:
53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
54 | @echo
55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
56 |
57 | dirhtml:
58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
61 |
62 | singlehtml:
63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
64 | @echo
65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
66 |
67 | pickle:
68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
69 | @echo
70 | @echo "Build finished; now you can process the pickle files."
71 |
72 | json:
73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
74 | @echo
75 | @echo "Build finished; now you can process the JSON files."
76 |
77 | htmlhelp:
78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
79 | @echo
80 | @echo "Build finished; now you can run HTML Help Workshop with the" \
81 | ".hhp project file in $(BUILDDIR)/htmlhelp."
82 |
83 | qthelp:
84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
85 | @echo
86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/aioeventlet.qhcp"
89 | @echo "To view the help file:"
90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/aioeventlet.qhc"
91 |
92 | devhelp:
93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
94 | @echo
95 | @echo "Build finished."
96 | @echo "To view the help file:"
97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/aioeventlet"
98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/aioeventlet"
99 | @echo "# devhelp"
100 |
101 | epub:
102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
103 | @echo
104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
105 |
106 | latex:
107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | @echo
109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
111 | "(use \`make latexpdf' here to do that automatically)."
112 |
113 | latexpdf:
114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
115 | @echo "Running LaTeX files through pdflatex..."
116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
118 |
119 | latexpdfja:
120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
121 | @echo "Running LaTeX files through platex and dvipdfmx..."
122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
124 |
125 | text:
126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
127 | @echo
128 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
129 |
130 | man:
131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
132 | @echo
133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
134 |
135 | texinfo:
136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
137 | @echo
138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
139 | @echo "Run \`make' in that directory to run these through makeinfo" \
140 | "(use \`make info' here to do that automatically)."
141 |
142 | info:
143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
144 | @echo "Running Texinfo files through makeinfo..."
145 | make -C $(BUILDDIR)/texinfo info
146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
147 |
148 | gettext:
149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
150 | @echo
151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
152 |
153 | changes:
154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
155 | @echo
156 | @echo "The overview file is in $(BUILDDIR)/changes."
157 |
158 | linkcheck:
159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
160 | @echo
161 | @echo "Link check complete; look for any errors in the above output " \
162 | "or in $(BUILDDIR)/linkcheck/output.txt."
163 |
164 | doctest:
165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
166 | @echo "Testing of doctests in the sources finished, look at the " \
167 | "results in $(BUILDDIR)/doctest/output.txt."
168 |
169 | xml:
170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
171 | @echo
172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
173 |
174 | pseudoxml:
175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
176 | @echo
177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
178 |
--------------------------------------------------------------------------------
/doc/changelog.rst:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | 2016-02-22: Version 0.5.1
5 | -------------------------
6 |
7 | * Fix EventLoop.stop() for the new semantics of stop() introduced
8 | in Python 3.4.4 and Python 3.5.1. The method now writes into the self-pipe
9 | to wake-up the event loop. Otherwise, the stop() may not be taken in account
10 | immediatly, and even block forever.
11 |
12 | 2016-02-22: Version 0.5
13 | -----------------------
14 |
15 | * Unit tests now use the aiotest library.
16 | * Fix for eventlet used with monkey-patching: inject the original threading
17 | module and the original threading.get_ident() function in
18 | asyncio.base_events.
19 | * Drop support for Python 2.6 and Python 3.2. aioeventlet depends on trollius
20 | and pip which don't support these Python versions anymore.
21 |
22 | 2014-12-03: Version 0.4
23 | -----------------------
24 |
25 | * Add run_aiotest.py
26 | * tox now also run the aiotest test suite
27 | * Rename the project from ``aiogreen`` to ``aioeventlet``
28 | * Rename the ``link_future()`` function to :func:`yield_future`
29 | * Running tests with eventlet monkey-patching now works with Python 3.
30 |
31 | 2014-11-23: version 0.3
32 | -----------------------
33 |
34 | * :func:`wrap_greenthread` now raises an exception if the greenthread is
35 | running or already finished. In debug mode, the exception is not more logged
36 | to sys.stderr for greenthreads.
37 | * :func:`link_future` now accepts coroutine objects.
38 | * :func:`link_future` now raises an exception if it is called from the
39 | greenthread of the aiogreen event loop.
40 | * Fix eventlet detection of blocking tasks: cancel the alarm when the aiogreen
41 | event loop stops.
42 | * Fix to run an event loop in a thread different than the main thread in debug
43 | mode: disable eventlet "debug_blocking", it is implemented with the SIGALRM
44 | signal, but signal handlers can only be set in the main thread.
45 |
46 | 2014-11-21: version 0.2
47 | -----------------------
48 |
49 | aiogreen event loop has been rewritten to reuse more asyncio/trollius code. It
50 | now only has a few specific code.
51 |
52 | It is now possible use greenthreads in asyncio coroutines, and to use asyncio
53 | coroutines, tasks and futures in greenthreads: see :func:`link_future` and
54 | :func:`wrap_greenthread` functions.
55 |
56 | All asyncio network are supported: TCP, UCP and UNIX clients and servers.
57 |
58 | Support of pipes, signal handlers and subprocess is still experimental.
59 |
60 | Changes:
61 |
62 | * Add a Sphinx documentation published at http://aiogreen.readthedocs.org/
63 | * Add the :func:`link_future` function: wait for a future from a
64 | greenthread.
65 | * Add the :func:`wrap_greenthread` function: wrap a greenthread into a Future
66 | * Support also eventlet 0.14, not only eventlet 0.15 or newer
67 | * Support eventlet with monkey-patching
68 | * Rewrite the code handling file descriptors to ensure that the listener is
69 | only called once per loop iteration, to respect asyncio specification.
70 | * Simplify the loop iteration: remove custom code to reuse instead the
71 | asyncio/trollius code (_run_once)
72 | * Reuse call_soon, call_soon_threadsafe, call_at, call_later from
73 | asyncio/trollius, remove custom code
74 | * sock_connect() is now asynchronous
75 | * Add a suite of automated unit tests
76 | * Fix EventLoop.stop(): don't stop immediatly, but schedule stopping the event
77 | loop with call_soon()
78 | * Add tox.ini to run tests with tox
79 | * Setting debug mode of the event loop doesn't enable "debug_blocking" of
80 | eventlet on Windows anymore, the feature is not implemented on Windows
81 | in eventlet.
82 | * add_reader() and add_writer() now cancels the previous handle and sets
83 | a new handle
84 | * In debug mode, detect calls to call_soon() from greenthreads which are not
85 | threadsafe (would not wake up the event loop).
86 | * Only set "debug_exceptions" of the eventlet hub when the debug mode of the
87 | event loop is enabled.
88 |
89 | 2014-11-19: version 0.1
90 | -----------------------
91 |
92 | * First public release
93 |
--------------------------------------------------------------------------------
/doc/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # aioeventlet documentation build configuration file, created by
4 | # sphinx-quickstart on Fri Nov 21 04:29:49 2014.
5 | #
6 | # This file is execfile()d with the current directory set to its
7 | # containing dir.
8 | #
9 | # Note that not all possible configuration values are present in this
10 | # autogenerated file.
11 | #
12 | # All configuration values have a default; values that are commented out
13 | # serve to show the default.
14 |
15 | import sys
16 | import os
17 |
18 | # If extensions (or modules to document with autodoc) are in another directory,
19 | # add these directories to sys.path here. If the directory is relative to the
20 | # documentation root, use os.path.abspath to make it absolute, like shown here.
21 | #sys.path.insert(0, os.path.abspath('.'))
22 |
23 | # -- General configuration ------------------------------------------------
24 |
25 | # If your documentation needs a minimal Sphinx version, state it here.
26 | #needs_sphinx = '1.0'
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be
29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
30 | # ones.
31 | extensions = []
32 |
33 | # Add any paths that contain templates here, relative to this directory.
34 | templates_path = ['templates']
35 |
36 | # The suffix of source filenames.
37 | source_suffix = '.rst'
38 |
39 | # The encoding of source files.
40 | #source_encoding = 'utf-8-sig'
41 |
42 | # The master toctree document.
43 | master_doc = 'index'
44 |
45 | # General information about the project.
46 | project = u'aioeventlet'
47 | copyright = u'2014, Victor Stinner'
48 |
49 | # The version info for the project you're documenting, acts as replacement for
50 | # |version| and |release|, also used in various other places throughout the
51 | # built documents.
52 | #
53 | # The short X.Y version.
54 | # The full version, including alpha/beta/rc tags.
55 | version = release = '0.5.1'
56 |
57 | # The language for content autogenerated by Sphinx. Refer to documentation
58 | # for a list of supported languages.
59 | #language = None
60 |
61 | # There are two options for replacing |today|: either, you set today to some
62 | # non-false value, then it is used:
63 | #today = ''
64 | # Else, today_fmt is used as the format for a strftime call.
65 | #today_fmt = '%B %d, %Y'
66 |
67 | # List of patterns, relative to source directory, that match files and
68 | # directories to ignore when looking for source files.
69 | exclude_patterns = ['build']
70 |
71 | # The reST default role (used for this markup: `text`) to use for all
72 | # documents.
73 | #default_role = None
74 |
75 | # If true, '()' will be appended to :func: etc. cross-reference text.
76 | #add_function_parentheses = True
77 |
78 | # If true, the current module name will be prepended to all description
79 | # unit titles (such as .. function::).
80 | #add_module_names = True
81 |
82 | # If true, sectionauthor and moduleauthor directives will be shown in the
83 | # output. They are ignored by default.
84 | #show_authors = False
85 |
86 | # The name of the Pygments (syntax highlighting) style to use.
87 | pygments_style = 'sphinx'
88 |
89 | # A list of ignored prefixes for module index sorting.
90 | #modindex_common_prefix = []
91 |
92 | # If true, keep warnings as "system message" paragraphs in the built documents.
93 | #keep_warnings = False
94 |
95 |
96 | # -- Options for HTML output ----------------------------------------------
97 |
98 | # The theme to use for HTML and HTML Help pages. See the documentation for
99 | # a list of builtin themes.
100 | html_theme = 'default'
101 |
102 | # Theme options are theme-specific and customize the look and feel of a theme
103 | # further. For a list of options available for each theme, see the
104 | # documentation.
105 | #html_theme_options = {}
106 |
107 | # Add any paths that contain custom themes here, relative to this directory.
108 | #html_theme_path = []
109 |
110 | # The name for this set of Sphinx documents. If None, it defaults to
111 | # " v documentation".
112 | #html_title = None
113 |
114 | # A shorter title for the navigation bar. Default is the same as html_title.
115 | #html_short_title = None
116 |
117 | # The name of an image file (relative to this directory) to place at the top
118 | # of the sidebar.
119 | #html_logo = None
120 |
121 | # The name of an image file (within the static path) to use as favicon of the
122 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
123 | # pixels large.
124 | #html_favicon = None
125 |
126 | # Add any paths that contain custom static files (such as style sheets) here,
127 | # relative to this directory. They are copied after the builtin static files,
128 | # so a file named "default.css" will overwrite the builtin "default.css".
129 | html_static_path = ['_static']
130 |
131 | # Add any extra paths that contain custom files (such as robots.txt or
132 | # .htaccess) here, relative to this directory. These files are copied
133 | # directly to the root of the documentation.
134 | #html_extra_path = []
135 |
136 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
137 | # using the given strftime format.
138 | #html_last_updated_fmt = '%b %d, %Y'
139 |
140 | # If true, SmartyPants will be used to convert quotes and dashes to
141 | # typographically correct entities.
142 | #html_use_smartypants = True
143 |
144 | # Custom sidebar templates, maps document names to template names.
145 | #html_sidebars = {}
146 |
147 | # Additional templates that should be rendered to pages, maps page names to
148 | # template names.
149 | #html_additional_pages = {}
150 |
151 | # If false, no module index is generated.
152 | #html_domain_indices = True
153 |
154 | # If false, no index is generated.
155 | #html_use_index = True
156 |
157 | # If true, the index is split into individual pages for each letter.
158 | #html_split_index = False
159 |
160 | # If true, links to the reST sources are added to the pages.
161 | #html_show_sourcelink = True
162 |
163 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
164 | #html_show_sphinx = True
165 |
166 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
167 | #html_show_copyright = True
168 |
169 | # If true, an OpenSearch description file will be output, and all pages will
170 | # contain a tag referring to it. The value of this option must be the
171 | # base URL from which the finished HTML is served.
172 | #html_use_opensearch = ''
173 |
174 | # This is the file name suffix for HTML files (e.g. ".xhtml").
175 | #html_file_suffix = None
176 |
177 | # Output file base name for HTML help builder.
178 | htmlhelp_basename = 'aioeventletdoc'
179 |
180 |
181 | # -- Options for LaTeX output ---------------------------------------------
182 |
183 | latex_elements = {
184 | # The paper size ('letterpaper' or 'a4paper').
185 | #'papersize': 'letterpaper',
186 |
187 | # The font size ('10pt', '11pt' or '12pt').
188 | #'pointsize': '10pt',
189 |
190 | # Additional stuff for the LaTeX preamble.
191 | #'preamble': '',
192 | }
193 |
194 | # Grouping the document tree into LaTeX files. List of tuples
195 | # (source start file, target name, title,
196 | # author, documentclass [howto, manual, or own class]).
197 | latex_documents = [
198 | ('index', 'aioeventlet.tex', u'aioeventlet Documentation',
199 | u'Victor Stinner', 'manual'),
200 | ]
201 |
202 | # The name of an image file (relative to this directory) to place at the top of
203 | # the title page.
204 | #latex_logo = None
205 |
206 | # For "manual" documents, if this is true, then toplevel headings are parts,
207 | # not chapters.
208 | #latex_use_parts = False
209 |
210 | # If true, show page references after internal links.
211 | #latex_show_pagerefs = False
212 |
213 | # If true, show URL addresses after external links.
214 | #latex_show_urls = False
215 |
216 | # Documents to append as an appendix to all manuals.
217 | #latex_appendices = []
218 |
219 | # If false, no module index is generated.
220 | #latex_domain_indices = True
221 |
222 |
223 | # -- Options for manual page output ---------------------------------------
224 |
225 | # One entry per manual page. List of tuples
226 | # (source start file, name, description, authors, manual section).
227 | man_pages = [
228 | ('index', 'aioeventlet', u'aioeventlet Documentation',
229 | [u'Victor Stinner'], 1)
230 | ]
231 |
232 | # If true, show URL addresses after external links.
233 | #man_show_urls = False
234 |
235 |
236 | # -- Options for Texinfo output -------------------------------------------
237 |
238 | # Grouping the document tree into Texinfo files. List of tuples
239 | # (source start file, target name, title, author,
240 | # dir menu entry, description, category)
241 | texinfo_documents = [
242 | ('index', 'aioeventlet', u'aioeventlet Documentation',
243 | u'Victor Stinner', 'aioeventlet', 'One line description of project.',
244 | 'Miscellaneous'),
245 | ]
246 |
247 | # Documents to append as an appendix to all manuals.
248 | #texinfo_appendices = []
249 |
250 | # If false, no module index is generated.
251 | #texinfo_domain_indices = True
252 |
253 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
254 | #texinfo_show_urls = 'footnote'
255 |
256 | # If true, do not generate a @detailmenu in the "Top" node's menu.
257 | #texinfo_no_detailmenu = False
258 |
--------------------------------------------------------------------------------
/doc/index.rst:
--------------------------------------------------------------------------------
1 | aioeventlet
2 | ===========
3 |
4 | .. image:: poplar_hawk-moth.jpg
5 | :alt: Poplar Hawk-moth (Laothoe populi), photo taken in France
6 | :align: right
7 | :target: https://www.flickr.com/photos/haypo/7181768969/in/set-72157629731066236
8 |
9 | aioeventlet implements the asyncio API (`PEP 3156
10 | `_) on top of eventlet. It makes
11 | possible to write asyncio code in a project currently written for eventlet.
12 |
13 | aioeventlet allows to use greenthreads in asyncio coroutines, and to use asyncio
14 | coroutines, tasks and futures in greenthreads: see :func:`yield_future` and
15 | :func:`wrap_greenthread` functions.
16 |
17 | The main visible difference between aioeventlet and asyncio is the behaviour of
18 | ``run_forever()``: ``run_forever()`` blocks with asyncio, whereas it runs in a
19 | greenthread with aioeventlet. It means that aioeventlet event loop can run in an
20 | greenthread while the Python main thread runs other greenthreads in parallel.
21 |
22 | * `aioeventlet documentation `_
23 | * `aioeventlet project in the Python Cheeseshop (PyPI)
24 | `_
25 | * `aioeventlet project at Bitbucket `_
26 | * Copyright/license: Open source, Apache 2.0. Enjoy!
27 |
28 | Table Of Contents
29 | =================
30 |
31 | .. toctree::
32 |
33 | using
34 | status
35 | openstack
36 | changelog
37 |
38 | Event loops
39 | ===========
40 |
41 | Projects used by aioeventlet:
42 |
43 | * `asyncio documentation `_
44 | * `trollius documentation `_
45 | * `tulip project `_
46 | * `eventlet documentation `_
47 | * `eventlet project `_
48 | * `greenlet documentation `_
49 |
50 | See also:
51 |
52 | * `aiogevent `_: asyncio API
53 | implemented on top of gevent
54 | * `geventreactor `_: gevent-powered
55 | Twisted reactor
56 | * `greenio `_: Greenlets support
57 | for asyncio (PEP 3156)
58 | * `tulipcore `_: run gevent code on
59 | top of asyncio
60 |
--------------------------------------------------------------------------------
/doc/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | goto end
41 | )
42 |
43 | if "%1" == "clean" (
44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
45 | del /q /s %BUILDDIR%\*
46 | goto end
47 | )
48 |
49 |
50 | %SPHINXBUILD% 2> nul
51 | if errorlevel 9009 (
52 | echo.
53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
54 | echo.installed, then set the SPHINXBUILD environment variable to point
55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
56 | echo.may add the Sphinx directory to PATH.
57 | echo.
58 | echo.If you don't have Sphinx installed, grab it from
59 | echo.http://sphinx-doc.org/
60 | exit /b 1
61 | )
62 |
63 | if "%1" == "html" (
64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
68 | goto end
69 | )
70 |
71 | if "%1" == "dirhtml" (
72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
76 | goto end
77 | )
78 |
79 | if "%1" == "singlehtml" (
80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
84 | goto end
85 | )
86 |
87 | if "%1" == "pickle" (
88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can process the pickle files.
92 | goto end
93 | )
94 |
95 | if "%1" == "json" (
96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
97 | if errorlevel 1 exit /b 1
98 | echo.
99 | echo.Build finished; now you can process the JSON files.
100 | goto end
101 | )
102 |
103 | if "%1" == "htmlhelp" (
104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
105 | if errorlevel 1 exit /b 1
106 | echo.
107 | echo.Build finished; now you can run HTML Help Workshop with the ^
108 | .hhp project file in %BUILDDIR%/htmlhelp.
109 | goto end
110 | )
111 |
112 | if "%1" == "qthelp" (
113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
114 | if errorlevel 1 exit /b 1
115 | echo.
116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
117 | .qhcp project file in %BUILDDIR%/qthelp, like this:
118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\aioeventlet.qhcp
119 | echo.To view the help file:
120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\aioeventlet.ghc
121 | goto end
122 | )
123 |
124 | if "%1" == "devhelp" (
125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished.
129 | goto end
130 | )
131 |
132 | if "%1" == "epub" (
133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
137 | goto end
138 | )
139 |
140 | if "%1" == "latex" (
141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
145 | goto end
146 | )
147 |
148 | if "%1" == "latexpdf" (
149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
150 | cd %BUILDDIR%/latex
151 | make all-pdf
152 | cd %BUILDDIR%/..
153 | echo.
154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
155 | goto end
156 | )
157 |
158 | if "%1" == "latexpdfja" (
159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
160 | cd %BUILDDIR%/latex
161 | make all-pdf-ja
162 | cd %BUILDDIR%/..
163 | echo.
164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
165 | goto end
166 | )
167 |
168 | if "%1" == "text" (
169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
170 | if errorlevel 1 exit /b 1
171 | echo.
172 | echo.Build finished. The text files are in %BUILDDIR%/text.
173 | goto end
174 | )
175 |
176 | if "%1" == "man" (
177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
178 | if errorlevel 1 exit /b 1
179 | echo.
180 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
181 | goto end
182 | )
183 |
184 | if "%1" == "texinfo" (
185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
186 | if errorlevel 1 exit /b 1
187 | echo.
188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
189 | goto end
190 | )
191 |
192 | if "%1" == "gettext" (
193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
194 | if errorlevel 1 exit /b 1
195 | echo.
196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
197 | goto end
198 | )
199 |
200 | if "%1" == "changes" (
201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
202 | if errorlevel 1 exit /b 1
203 | echo.
204 | echo.The overview file is in %BUILDDIR%/changes.
205 | goto end
206 | )
207 |
208 | if "%1" == "linkcheck" (
209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
210 | if errorlevel 1 exit /b 1
211 | echo.
212 | echo.Link check complete; look for any errors in the above output ^
213 | or in %BUILDDIR%/linkcheck/output.txt.
214 | goto end
215 | )
216 |
217 | if "%1" == "doctest" (
218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
219 | if errorlevel 1 exit /b 1
220 | echo.
221 | echo.Testing of doctests in the sources finished, look at the ^
222 | results in %BUILDDIR%/doctest/output.txt.
223 | goto end
224 | )
225 |
226 | if "%1" == "xml" (
227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
228 | if errorlevel 1 exit /b 1
229 | echo.
230 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
231 | goto end
232 | )
233 |
234 | if "%1" == "pseudoxml" (
235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
236 | if errorlevel 1 exit /b 1
237 | echo.
238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
239 | goto end
240 | )
241 |
242 | :end
243 |
--------------------------------------------------------------------------------
/doc/openstack.rst:
--------------------------------------------------------------------------------
1 | asyncio in OpenStack
2 | ====================
3 |
4 | .. warning::
5 | The project of replacing eventlet with trollius using aioeventlet in
6 | OpenStack is abandonned. It might done "later" when Python 2 support
7 | will be removed from OpenStack which is not going to happen in a near
8 | future.
9 |
10 | First part (in progress): add support for trollius coroutines
11 | -------------------------------------------------------------
12 |
13 | Prepare OpenStack (Oslo Messaging) to support trollius coroutines using
14 | ``yield``: explicit asynchronous programming. Eventlet is still supported,
15 | used by default, and applications and libraries don't need to be modified at
16 | this point.
17 |
18 | Already done:
19 |
20 | * Write the trollius project: port asyncio to Python 2
21 | * Stabilize trollius API
22 | * Add trollius dependency to OpenStack
23 | * Write the aioeventlet project to provide the asyncio API on top of eventlet
24 |
25 | To do:
26 |
27 | * Stabilize aioeventlet API
28 | * Add aioeventlet dependency to OpenStack
29 | * Write an aioeventlet executor for Oslo Messaging: rewrite greenio executor
30 | to replace greenio with aioeventlet
31 |
32 | Second part (to do): rewrite code as trollius coroutines
33 | --------------------------------------------------------
34 |
35 | Switch from implicit asynchronous programming (eventlet using greenthreads) to
36 | explicit asynchronous programming (trollius coroutines using ``yield``). Need
37 | to modify OpenStack Common Libraries and applications. Modifications can be
38 | done step by step, the switch will take more than 6 months.
39 |
40 | The first application candidate is Ceilometer. The Ceilometer project is young,
41 | developers are aware of eventlet issues and like Python 3, and Ceilometer don't
42 | rely so much on asynchronous programming: most time is spent into waiting the
43 | database anyway.
44 |
45 | The goal is to port Ceilometer to explicit asynchronous programming during the
46 | cycle of OpenStack Kilo.
47 |
48 | Some applications may continue to use implicit asynchronous programming. For
49 | example, nova is probably the most complex part beacuse it is and old project
50 | with a lot of legacy code, it has many drivers and the code base is large.
51 |
52 | To do:
53 |
54 | * Ceilometer: add trollius dependency and set the trollius event loop policy to
55 | aioeventlet
56 | * Ceilometer: change Oslo Messaging executor from "eventlet" to "aioeventlet"
57 | * Redesign the service class of Oslo Incubator to support aioeventlet and/or
58 | trollius. Currently, the class is designed for eventlet. The service class
59 | is instanciated before forking, which requires hacks on eventlet to update
60 | file descriptors.
61 | * In Ceilometer and its OpenStack depedencencies: add new functions which
62 | are written with explicit asynchronous programming in mind (ex: trollius
63 | coroutines written with ``yield``).
64 | * Rewrite Ceilometer endpoints (RPC methods) as trollius coroutines.
65 |
66 | Questions:
67 |
68 | * What about WSGI? aiohttp is not compatible with trollius yet.
69 | * The quantity of code which need to be ported to asynchronous programming is
70 | unknown right now.
71 | * We should be prepared to see deadlocks. OpenStack was designed for eventlet
72 | which implicitly switch on blocking operations. Critical sections may not be
73 | protected with locks, or not the right kind of lock.
74 | * For performances, blocking operations can be executed in threads. OpenStack
75 | code is probably not thread-safe, which means new kinds of race conditions.
76 | But the code executed in threads will be explicitly scheduled to be executed
77 | in a thread (with ``loop.run_in_executor()``), so regressions can be easily
78 | identified.
79 | * This part will take a lot of time. We may need to split it into subparts
80 | to have milestones, which is more attractive for developers.
81 |
82 |
83 | Last part (to do): drop eventlet
84 | --------------------------------
85 |
86 | Replace aioeventlet event loop with trollius event loop, drop aioeventlet and drop
87 | eventlet at the end.
88 |
89 | This change will be done on applications one by one. This is no need to port
90 | all applications at once. The work will start on Ceilometer, as a follow up
91 | of the second part.
92 |
93 | To do:
94 |
95 | * Port remaining code to trollius
96 | * Write a "trollius" executor for Oslo Messaging
97 | * Ceilometer: Add a blocking call to ``loop.run_forever()`` in the ``main()``
98 | function
99 | * Ceilometer: Replace "aioeventlet" executor with "trollius" executor
100 | * Ceilometer: Use the standard trollius event loop policy
101 | * Ceilometer: drop the eventlet dependency
102 |
103 | Questions:
104 |
105 | * Putting a blocking call to ``loop.run_forever()`` may need to redesign
106 | Ceilometer, this part is unclear to me right now.
107 |
108 | Optimization, can be done later:
109 |
110 | * Oslo Messaging: watch directly the underlying file descriptor of sockets,
111 | instead of using a busy loop polling the notifier
112 | * Ceilometer: use libraries supporting directly trollius to be able to run
113 | parallel tasks (ex: send multiple requests to a database)
114 |
115 |
116 | openstack-dev mailing list
117 | --------------------------
118 |
119 | * `[oslo] Progress of the port to Python 3
120 | `_
121 | (Victor Stinner, Jan 6 2015)
122 |
123 | * `[oslo] Add a new aiogreen executor for Oslo Messaging
124 | `_
125 | (Victor Stinner, Nov 23 2014)
126 |
127 | * `[oslo] Asyncio and oslo.messaging
128 | `_
129 | (Mark McLoughlin, Jul 3 2014)
130 |
131 | * `SQLAlchemy and asynchronous programming
132 | `_
133 | by Mike Bayer (author and maintainer of SQLAlchemy)
134 |
135 | * `[Solum][Oslo] Next Release of oslo.messaging?
136 | `_
137 | (Victor Stinner, Mar 18 2014)
138 |
139 | * `[solum] async / threading for python 2 and 3
140 | `_
141 | (Victor Stinner, Feb 20 2014)
142 |
143 | * `Asynchrounous programming: replace eventlet with asyncio
144 | `_
145 | (Victor Stinner, Feb 4 2014)
146 |
147 |
148 | History of asyncio in OpenStack
149 | -------------------------------
150 |
151 | Threads and asyncio specs:
152 |
153 | * `Cross-project meeting: 2015-03-02
154 | `_
155 | * `Cross-project meeting: 2015-02-24T21:44:05
156 | `_
157 |
158 | Maybe the good one, aioeventlet project:
159 |
160 | * March 13, 2015: Joshua Harlow wrote the spec `Replace eventlet +
161 | monkey-patching with ?? `_
162 | * February 17, 2015: Joshua Harlow wrote a different spec,
163 | `Sew over eventlet + patching with threads
164 | `_
165 | * February 5, 2015: new cross-project spec `Replace eventlet with asyncio
166 | `_
167 | * December 3, 2014: two patches posted to requirements:
168 | `Add aioeventlet dependency `_
169 | and `Drop greenio dependency `_.
170 | * Novembre 23, 2014: two patches posted to Oslo Messaging:
171 | `Add a new aioeventlet executor `_
172 | and `Add an optional executor callback to dispatcher
173 | `_
174 | * November 19, 2014: First release of the aioeventlet project
175 |
176 | OpenStack Kilo Summit, November 3-7, 2014, at Paris:
177 |
178 | * `Python 3 in Oslo `_:
179 |
180 | * add a new greenio executor to Oslo Messaging
181 | * port eventlet to Python 3 (with monkey-patching): see the :ref:`status of
182 | the eventlet port to Python 3 `
183 |
184 | * `What should we do about oslo.messaging?
185 | `_: add the new
186 | greenio executor
187 |
188 | * `Python 3.4 transition `_
189 |
190 | New try with a greenio executor for Oslo Messaging:
191 |
192 | * July 29, 2014: Doug Hellmann proposed the blueprint
193 | `A 'greenio' executor for oslo.messaging
194 | `_,
195 | approved by Mark McLoughlin.
196 | * July 24, 2014: `Add greenio dependency `_
197 | merged into openstack/requirements
198 | * July 22, 2014: Patch `Add a new greenio executor
199 | `_ proposed to Oslo Messaging
200 | * July 21, 2014: Release of greenio 0.6 which is now compatible with Trollius
201 | * July 21, 2014: Release of Trollius 1.0
202 | * July 14, 2014: Patch `Add a 'greenio' oslo.messaging executor (spec)
203 | `_ merged into openstack/oslo-specs.
204 | * July 7, 2014: Patch `Fix AMQPListener for polling with timeout
205 | `_ merged into Oslo Messaging
206 | * July 2014: greenio executor, `[openstack-dev] [oslo] Asyncio and oslo.messaging
207 | `_
208 |
209 | First try with a trollius executor for Oslo Messaging:
210 |
211 | * June 20, 2014: Patch `Add an optional timeout parameter to Listener.poll
212 | `_ merged into Oslo Messaging
213 | * May 28, 2014: Meeting at OpenStack in action with Doug Hellman, Julien
214 | Danjou, Mehdi Abaakouk, Victor Stinner and Christophe to discuss the plan to
215 | port OpenStack to Python 3 and switch from eventlet to asyncio.
216 | * April 23, 2014: Patch `Allow trollius 0.2
217 | `_ merged into
218 | openstack/requirements
219 | * March 21, 2014: Patch `Replace ad-hoc coroutines with Trollius coroutines
220 | `_ proposed to Heat. Heat coroutines
221 | are close to Trollius coroutines. Patch abandonned, need to be rewritten,
222 | maybe with aioeventlet.
223 | * February 20, 2014: The full specification of the blueprint was written:
224 | `Oslo/blueprints/asyncio
225 | `_
226 | * February 8, 2014: Patch `Add a new dependency: trollius
227 | `_ merged into
228 | openstack/requirements
229 | * February 27, 2014: Article `Use the new asyncio module and Trollius in OpenStack
230 | `_ published
231 | * February 4, 2014: Patch `Add a new asynchronous executor based on Trollius
232 | `_ proposed to Oslo Messaging,
233 | but it was abandonned. Running a classic Trollius event loop in a dedicated
234 | thread doesn't fit well into eventlet event loop.
235 |
236 | First discussion around asyncio and OpenStack:
237 |
238 | * December 19, 2013: Article `Why should OpenStack move to Python 3 right now?
239 | `_ published
240 | * December 4, 2013: Blueprint `Add a asyncio executor to oslo.messaging
241 | `_
242 | proposed by Flavio Percoco and accepted for OpenStack Icehouse by Mark
243 | McLoughlin
244 |
245 |
246 | History of asynchronous programming in OpenStack
247 | ------------------------------------------------
248 |
249 | In the past, the Nova project used Tornado, then Twisted and it is now using
250 | eventlet which also became the defacto standard in OpenStack
251 |
252 |
--------------------------------------------------------------------------------
/doc/poplar_hawk-moth.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openstack-archive/deb-python-aioeventlet/df7b72185c6a937935ac9532c1be54437ff70b6d/doc/poplar_hawk-moth.jpg
--------------------------------------------------------------------------------
/doc/status.rst:
--------------------------------------------------------------------------------
1 | To do
2 | =====
3 |
4 | * support monkey-patching enabled after loading the aioeventlet module
5 | * register signals in eventlet hub, only needed for pyevent hub?
6 | * port greenio examples to aioeventlet
7 | * write unit tests for, and maybe also examples for:
8 |
9 | - TCP server
10 | - UDP socket
11 | - UNIX socket
12 | - pipes
13 | - signals
14 | - subprocesses
15 |
16 | * experiment running an event loop in a thread different than the main thread
17 |
18 |
19 | .. _eventlet-py3:
20 |
21 | eventlet and Python 3
22 | =====================
23 |
24 | eventlet 0.17 or newer is recommanded for Python 3 when monkey-patching is
25 | enabled.
26 |
--------------------------------------------------------------------------------
/doc/using.rst:
--------------------------------------------------------------------------------
1 | Usage
2 | =====
3 |
4 | Use aioeventlet with asyncio
5 | ----------------------------
6 |
7 | aioeventlet can be used with asyncio, coroutines written with ``yield from ...``.
8 | To use aioeventlet with asyncio, set the event loop policy before using an event
9 | loop. Example::
10 |
11 | import aioeventlet
12 | import asyncio
13 |
14 | asyncio.set_event_loop_policy(aioeventlet.EventLoopPolicy())
15 | # ....
16 |
17 | Setting the event loop policy should be enough to examples of the asyncio
18 | documentation with the aioeventlet event loop.
19 |
20 | Hello World::
21 |
22 | import aioeventlet
23 | import asyncio
24 |
25 | def hello_world():
26 | print("Hello World")
27 | loop.stop()
28 |
29 | asyncio.set_event_loop_policy(aioeventlet.EventLoopPolicy())
30 | loop = asyncio.get_event_loop()
31 | loop.call_soon(hello_world)
32 | loop.run_forever()
33 | loop.close()
34 |
35 | .. seealso::
36 | The `asyncio documentation
37 | `_.
38 |
39 |
40 | Use aioeventlet with trollius
41 | -----------------------------
42 |
43 | .. warning::
44 | The `trollius project is now deprecated
45 | `_. It's now recommended to
46 | use aioeventlet with asyncio.
47 |
48 | aioeventlet can be used with trollius, coroutines written with ``yield
49 | From(...)``. Using aioeventlet with trollius is a good start to port project
50 | written for eventlet to trollius.
51 |
52 | To use aioeventlet with trollius, set the event loop policy before using an event
53 | loop, example::
54 |
55 | import aioeventlet
56 | import trollius
57 |
58 | trollius.set_event_loop_policy(aioeventlet.EventLoopPolicy())
59 | # ....
60 |
61 | Hello World::
62 |
63 | import aioeventlet
64 | import trollius as asyncio
65 |
66 | def hello_world():
67 | print("Hello World")
68 | loop.stop()
69 |
70 | asyncio.set_event_loop_policy(aioeventlet.EventLoopPolicy())
71 | loop = asyncio.get_event_loop()
72 | loop.call_soon(hello_world)
73 | loop.run_forever()
74 | loop.close()
75 |
76 | .. seealso::
77 | `Trollius documentation `_.
78 |
79 |
80 | Threads
81 | -------
82 |
83 | Running an event loop in a thread different than the main thread is currently
84 | experimental.
85 |
86 | An eventlet Event object is not thread-safe, it must only be used in the
87 | same thread. Use threading.Event to signal events between threads,
88 | and threading.Queue to pass data between threads.
89 |
90 | Use ``threading = eventlet.patcher.original('threading')`` to get the original
91 | threading instead of ``import threading``.
92 |
93 | It is not possible to run two aioeventlet event loops in the same thread.
94 |
95 |
96 | Debug mode
97 | ----------
98 |
99 | To enable the debug mode globally when using trollius, set the environment
100 | variable ``TROLLIUSDEBUG`` to ``1``. To see debug traces, set the log level of
101 | the trollius logger to ``logging.DEBUG``. The simplest configuration is::
102 |
103 | import logging
104 | # ...
105 | logging.basicConfig(level=logging.DEBUG)
106 |
107 | If you use asyncio, use the ``PYTHONASYNCIODEBUG`` environment variable
108 | instead of the ``TROLLIUSDEBUG`` variable.
109 |
110 | You can also call ``loop.set_debug(True)`` to enable the debug mode of the
111 | event loop, but it enables less debug checks.
112 |
113 | .. seealso::
114 | Read the `Develop with asyncio
115 | `_ section of the
116 | asyncio documentation.
117 |
118 |
119 | API
120 | ===
121 |
122 | aioeventlet specific functions:
123 |
124 | .. warning::
125 | aioeventlet API is not considered as stable yet.
126 |
127 |
128 | yield_future
129 | ------------
130 |
131 | .. function:: yield_future(future, loop=None)
132 |
133 | Wait for a future, a task, or a coroutine object from a greenthread.
134 |
135 | Return the result or raise the exception of the future.
136 |
137 | The function must not be called from the greenthread running the aioeventlet
138 | event loop.
139 |
140 | .. versionchanged:: 0.4
141 |
142 | Rename the function from ``wrap_future()`` to :func:`yield_future`.
143 |
144 | .. versionchanged:: 0.3
145 |
146 | Coroutine objects are also accepted. Added the *loop* parameter.
147 | An exception is raised if it is called from the greenthread of the
148 | aioeventlet event loop.
149 |
150 | Example of greenthread waiting for a trollius task. The ``progress()``
151 | callback is called regulary to see that the event loop in not blocked::
152 |
153 | import aioeventlet
154 | import eventlet
155 | import trollius as asyncio
156 | from trollius import From, Return
157 |
158 | def progress():
159 | print("computation in progress...")
160 | loop.call_later(0.5, progress)
161 |
162 | @asyncio.coroutine
163 | def coro_slow_sum(x, y):
164 | yield From(asyncio.sleep(1.0))
165 | raise Return(x + y)
166 |
167 | def green_sum():
168 | loop.call_soon(progress)
169 |
170 | value = aioeventlet.yield_future(coro_slow_sum(1, 2))
171 | print("1 + 2 = %s" % value)
172 |
173 | loop.stop()
174 |
175 | asyncio.set_event_loop_policy(aioeventlet.EventLoopPolicy())
176 | eventlet.spawn(green_sum)
177 | loop = asyncio.get_event_loop()
178 | loop.run_forever()
179 | loop.close()
180 |
181 | Output::
182 |
183 | computation in progress...
184 | computation in progress...
185 | computation in progress...
186 | 1 + 2 = 3
187 |
188 | wrap_greenthread
189 | ----------------
190 |
191 | .. function:: wrap_greenthread(gt)
192 |
193 | Wrap an eventlet GreenThread, or a greenlet, into a Future object.
194 |
195 | The Future object waits for the completion of a greenthread. The result or
196 | the exception of the greenthread will be stored in the Future object.
197 |
198 | The greenthread must be wrapped before its execution starts. If the
199 | greenthread is running or already finished, an exception is raised.
200 |
201 | For greenlets, the ``run`` attribute must be set.
202 |
203 | .. versionchanged:: 0.3
204 |
205 | An exception is now raised if the greenthread is running or already
206 | finished. In debug mode, the exception is not more logged to sys.stderr
207 | for greenthreads.
208 |
209 | Example of trollius coroutine waiting for a greenthread. The ``progress()``
210 | callback is called regulary to see that the event loop in not blocked::
211 |
212 | import aioeventlet
213 | import eventlet
214 | import trollius as asyncio
215 | from trollius import From, Return
216 |
217 | def progress():
218 | print("computation in progress...")
219 | loop.call_later(0.5, progress)
220 |
221 | def slow_sum(x, y):
222 | eventlet.sleep(1.0)
223 | return x + y
224 |
225 | @asyncio.coroutine
226 | def coro_sum():
227 | loop.call_soon(progress)
228 |
229 | gt = eventlet.spawn(slow_sum, 1, 2)
230 | fut = aioeventlet.wrap_greenthread(gt, loop=loop)
231 |
232 | result = yield From(fut)
233 | print("1 + 2 = %s" % result)
234 |
235 | asyncio.set_event_loop_policy(aioeventlet.EventLoopPolicy())
236 | loop = asyncio.get_event_loop()
237 | loop.run_until_complete(coro_sum())
238 | loop.close()
239 |
240 | Output::
241 |
242 | computation in progress...
243 | computation in progress...
244 | computation in progress...
245 | 1 + 2 = 3
246 |
247 |
248 | Installation
249 | ============
250 |
251 | Install aioeventlet with pip
252 | ----------------------------
253 |
254 | Type::
255 |
256 | pip install aioeventlet
257 |
258 | Install aioeventlet on Windows with pip
259 | ---------------------------------------
260 |
261 | Procedure for Python 2.7:
262 |
263 | * If pip is not installed yet, `install pip
264 | `_: download
265 | ``get-pip.py`` and type::
266 |
267 | \Python27\python.exe get-pip.py
268 |
269 | * Install aioeventlet with pip::
270 |
271 | \Python27\python.exe -m pip install aioeventlet
272 |
273 | * pip also installs dependencies: ``eventlet`` and ``trollius``
274 |
275 | Manual installation of aioeventlet
276 | ----------------------------------
277 |
278 | Requirements:
279 |
280 | - eventlet 0.14 or newer
281 | - asyncio or trollius:
282 |
283 | * Python 3.4 and newer: asyncio is now part of the stdlib (only eventlet is
284 | needed)
285 | * Python 3.3: need Tulip 0.4.1 or newer (``pip install asyncio``),
286 | but Tulip 3.4.1 or newer is recommended
287 | * Python 2.7: need Trollius 0.3 or newer (``pip install trollius``),
288 | but Trollius 1.0 or newer is recommended
289 |
290 | Type::
291 |
292 | python setup.py install
293 |
294 |
295 | Run tests
296 | =========
297 |
298 | Run tests with tox
299 | ------------------
300 |
301 | The `tox project `_ can be used to build a
302 | virtual environment with all runtime and test dependencies and run tests
303 | against different Python versions (2.7, 3.3, 3.4, 3.5).
304 |
305 | To test all Python versions, just type::
306 |
307 | tox
308 |
309 | To run tests with Python 2.7, type::
310 |
311 | tox -e py27
312 |
313 | To run tests against other Python versions:
314 |
315 | * ``py27``: Python 2.7
316 | * ``py27_patch``: Python 2.7 with eventlet monkey patching
317 | * ``py27_old``: Python 2.7 with the oldest supported versions of eventlet and
318 | trollius
319 | * ``py33``: Python 3.3
320 | * ``py3_patch``: Python 3 with eventlet monkey patching
321 | * ``py3_old``: Python 3 with the oldest supported versions of eventlet and
322 | tulip
323 | * ``py34``: Python 3.4
324 | * ``py35``: Python 3.5
325 |
326 | Run tests manually
327 | ------------------
328 |
329 | To run unit tests, the ``mock`` module is need on Python older than 3.3.
330 |
331 | Run the following command::
332 |
333 | python runtests.py -r
334 |
--------------------------------------------------------------------------------
/run_aiotest.py:
--------------------------------------------------------------------------------
1 | import eventlet
2 | import sys
3 |
4 | if '-m' in sys.argv:
5 | print("Enable eventlet monkey patching")
6 | eventlet.monkey_patch()
7 | sys.argv.remove('-m')
8 |
9 | import aioeventlet
10 | import aiotest.run
11 |
12 | config = aiotest.TestConfig()
13 | config.asyncio = aioeventlet.asyncio
14 | config.socket = eventlet.patcher.original('socket')
15 | config.threading = eventlet.patcher.original('threading')
16 | config.sleep = eventlet.sleep
17 | config.socketpair = aioeventlet.socketpair
18 | config.new_event_pool_policy = aioeventlet.EventLoopPolicy
19 | aiotest.run.main(config)
20 |
--------------------------------------------------------------------------------
/runtests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Run Tulip unittests.
3 |
4 | Usage:
5 | python3 runtests.py [flags] [pattern] ...
6 |
7 | Patterns are matched against the fully qualified name of the test,
8 | including package, module, class and method,
9 | e.g. 'tests.test_events.PolicyTests.testPolicy'.
10 |
11 | For full help, try --help.
12 |
13 | runtests.py --coverage is equivalent of:
14 |
15 | $(COVERAGE) run --branch runtests.py -v
16 | $(COVERAGE) html $(list of files)
17 | $(COVERAGE) report -m $(list of files)
18 |
19 | """
20 |
21 | # Originally written by Beech Horn (for NDB).
22 |
23 | from __future__ import print_function
24 | import optparse
25 | import gc
26 | import logging
27 | import os
28 | import random
29 | import re
30 | import sys
31 | import textwrap
32 | PY33 = (sys.version_info >= (3, 3))
33 | if PY33:
34 | import importlib.machinery
35 | else:
36 | import imp
37 | try:
38 | import coverage
39 | except ImportError:
40 | coverage = None
41 | if sys.version_info < (3,):
42 | sys.exc_clear()
43 |
44 | try:
45 | import unittest
46 | from unittest.signals import installHandler
47 | except ImportError:
48 | import unittest2 as unittest
49 | from unittest2.signals import installHandler
50 |
51 | ARGS = optparse.OptionParser(description="Run all unittests.", usage="%prog [options] [pattern] [pattern2 ...]")
52 | ARGS.add_option(
53 | '-v', '--verbose', action="store_true", dest='verbose',
54 | default=0, help='verbose')
55 | ARGS.add_option(
56 | '-x', action="store_true", dest='exclude', help='exclude tests')
57 | ARGS.add_option(
58 | '-f', '--failfast', action="store_true", default=False,
59 | dest='failfast', help='Stop on first fail or error')
60 | ARGS.add_option(
61 | '-c', '--catch', action="store_true", default=False,
62 | dest='catchbreak', help='Catch control-C and display results')
63 | ARGS.add_option(
64 | '-m', '--monkey-patch', action="store_true", default=False,
65 | dest='monkey_patch', help='Enable eventlet monkey patching')
66 | ARGS.add_option(
67 | '--forever', action="store_true", dest='forever', default=False,
68 | help='run tests forever to catch sporadic errors')
69 | ARGS.add_option(
70 | '--findleaks', action='store_true', dest='findleaks',
71 | help='detect tests that leak memory')
72 | ARGS.add_option(
73 | '-r', '--randomize', action='store_true',
74 | help='randomize test execution order.')
75 | ARGS.add_option(
76 | '--seed', type=int,
77 | help='random seed to reproduce a previous random run')
78 | ARGS.add_option(
79 | '-q', action="store_true", dest='quiet', help='quiet')
80 | ARGS.add_option(
81 | '--tests', action="store", dest='testsdir', default='tests',
82 | help='tests directory')
83 | ARGS.add_option(
84 | '--coverage', action="store_true", dest='coverage',
85 | help='enable html coverage report')
86 |
87 |
88 | if PY33:
89 | def load_module(modname, sourcefile):
90 | loader = importlib.machinery.SourceFileLoader(modname, sourcefile)
91 | return loader.load_module()
92 | else:
93 | def load_module(modname, sourcefile):
94 | return imp.load_source(modname, sourcefile)
95 |
96 |
97 | def load_modules(basedir, suffix='.py'):
98 | def list_dir(prefix, dir):
99 | files = []
100 |
101 | modpath = os.path.join(dir, '__init__.py')
102 | if os.path.isfile(modpath):
103 | mod = os.path.split(dir)[-1]
104 | files.append(('{0}{1}'.format(prefix, mod), modpath))
105 |
106 | prefix = '{0}{1}.'.format(prefix, mod)
107 |
108 | for name in os.listdir(dir):
109 | path = os.path.join(dir, name)
110 |
111 | if os.path.isdir(path):
112 | files.extend(list_dir('{0}{1}.'.format(prefix, name), path))
113 | else:
114 | if (name != '__init__.py' and
115 | name.endswith(suffix) and
116 | not name.startswith(('.', '_'))):
117 | files.append(('{0}{1}'.format(prefix, name[:-3]), path))
118 |
119 | return files
120 |
121 | mods = []
122 | for modname, sourcefile in list_dir('', basedir):
123 | if modname == 'runtests':
124 | continue
125 | try:
126 | mod = load_module(modname, sourcefile)
127 | mods.append((mod, sourcefile))
128 | except SyntaxError:
129 | raise
130 | #except Exception as err:
131 | # print("Skipping '{0}': {1}".format(modname, err), file=sys.stderr)
132 |
133 | return mods
134 |
135 |
136 | def randomize_tests(tests, seed):
137 | if seed is None:
138 | seed = random.randrange(10000000)
139 | random.seed(seed)
140 | print("Using random seed", seed)
141 | random.shuffle(tests._tests)
142 |
143 |
144 | class TestsFinder:
145 |
146 | def __init__(self, testsdir, includes=(), excludes=()):
147 | self._testsdir = testsdir
148 | self._includes = includes
149 | self._excludes = excludes
150 | self.find_available_tests()
151 |
152 | def find_available_tests(self):
153 | """
154 | Find available test classes without instantiating them.
155 | """
156 | self._test_factories = []
157 | mods = [mod for mod, _ in load_modules(self._testsdir)]
158 | for mod in mods:
159 | for name in set(dir(mod)):
160 | if name.endswith('Tests'):
161 | self._test_factories.append(getattr(mod, name))
162 |
163 | def load_tests(self):
164 | """
165 | Load test cases from the available test classes and apply
166 | optional include / exclude filters.
167 | """
168 | loader = unittest.TestLoader()
169 | suite = unittest.TestSuite()
170 | for test_factory in self._test_factories:
171 | tests = loader.loadTestsFromTestCase(test_factory)
172 | if self._includes:
173 | tests = [test
174 | for test in tests
175 | if any(re.search(pat, test.id())
176 | for pat in self._includes)]
177 | if self._excludes:
178 | tests = [test
179 | for test in tests
180 | if not any(re.search(pat, test.id())
181 | for pat in self._excludes)]
182 | suite.addTests(tests)
183 | return suite
184 |
185 |
186 | class TestResult(unittest.TextTestResult):
187 |
188 | def __init__(self, stream, descriptions, verbosity):
189 | super().__init__(stream, descriptions, verbosity)
190 | self.leaks = []
191 |
192 | def startTest(self, test):
193 | super().startTest(test)
194 | gc.collect()
195 |
196 | def addSuccess(self, test):
197 | super().addSuccess(test)
198 | gc.collect()
199 | if gc.garbage:
200 | if self.showAll:
201 | self.stream.writeln(
202 | " Warning: test created {} uncollectable "
203 | "object(s).".format(len(gc.garbage)))
204 | # move the uncollectable objects somewhere so we don't see
205 | # them again
206 | self.leaks.append((self.getDescription(test), gc.garbage[:]))
207 | del gc.garbage[:]
208 |
209 |
210 | class TestRunner(unittest.TextTestRunner):
211 | resultclass = TestResult
212 |
213 | def run(self, test):
214 | result = super().run(test)
215 | if result.leaks:
216 | self.stream.writeln("{0} tests leaks:".format(len(result.leaks)))
217 | for name, leaks in result.leaks:
218 | self.stream.writeln(' '*4 + name + ':')
219 | for leak in leaks:
220 | self.stream.writeln(' '*8 + repr(leak))
221 | return result
222 |
223 |
224 | def runtests():
225 | args, pattern = ARGS.parse_args()
226 |
227 | if args.coverage and coverage is None:
228 | URL = "bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py"
229 | print(textwrap.dedent("""
230 | coverage package is not installed.
231 |
232 | To install coverage3 for Python 3, you need:
233 | - Setuptools (https://pypi.python.org/pypi/setuptools)
234 |
235 | What worked for me:
236 | - download {0}
237 | * curl -O https://{0}
238 | - python3 ez_setup.py
239 | - python3 -m easy_install coverage
240 | """.format(URL)).strip())
241 | sys.exit(1)
242 |
243 | testsdir = os.path.abspath(args.testsdir)
244 | if not os.path.isdir(testsdir):
245 | print("Tests directory is not found: {0}\n".format(testsdir))
246 | ARGS.print_help()
247 | return
248 |
249 | excludes = includes = []
250 | if args.exclude:
251 | excludes = pattern
252 | else:
253 | includes = pattern
254 |
255 | v = 0 if args.quiet else args.verbose + 1
256 | failfast = args.failfast
257 | catchbreak = args.catchbreak
258 | findleaks = args.findleaks
259 | runner_factory = TestRunner if findleaks else unittest.TextTestRunner
260 |
261 | if args.monkey_patch:
262 | print("Enable eventlet monkey patching of the Python standard library")
263 | import eventlet
264 | eventlet.monkey_patch()
265 |
266 | if args.coverage:
267 | cov = coverage.coverage(branch=True,
268 | source=['asyncio'],
269 | )
270 | cov.start()
271 |
272 | logger = logging.getLogger()
273 | if v == 0:
274 | level = logging.CRITICAL
275 | elif v == 1:
276 | level = logging.ERROR
277 | elif v == 2:
278 | level = logging.WARNING
279 | elif v == 3:
280 | level = logging.INFO
281 | elif v >= 4:
282 | level = logging.DEBUG
283 | logging.basicConfig(level=level)
284 |
285 | finder = TestsFinder(args.testsdir, includes, excludes)
286 | if catchbreak:
287 | installHandler()
288 | import tests
289 | if hasattr(tests.asyncio, 'coroutines'):
290 | debug = tests.asyncio.coroutines._DEBUG
291 | else:
292 | # Tulip <= 3.4.1
293 | debug = tests.asyncio.tasks._DEBUG
294 | if debug:
295 | print("Run tests in debug mode")
296 | else:
297 | print("Run tests in release mode")
298 | sys.stdout.flush()
299 | try:
300 | if args.forever:
301 | while True:
302 | tests = finder.load_tests()
303 | if args.randomize:
304 | randomize_tests(tests, args.seed)
305 | result = runner_factory(verbosity=v,
306 | failfast=failfast).run(tests)
307 | if not result.wasSuccessful():
308 | sys.exit(1)
309 | else:
310 | tests = finder.load_tests()
311 | if args.randomize:
312 | randomize_tests(tests, args.seed)
313 | result = runner_factory(verbosity=v,
314 | failfast=failfast).run(tests)
315 | sys.exit(not result.wasSuccessful())
316 | finally:
317 | if args.coverage:
318 | cov.stop()
319 | cov.save()
320 | cov.html_report(directory='htmlcov')
321 | print("\nCoverage report:")
322 | cov.report(show_missing=False)
323 | here = os.path.dirname(os.path.abspath(__file__))
324 | print("\nFor html report:")
325 | print("open file://{0}/htmlcov/index.html".format(here))
326 |
327 |
328 | if __name__ == '__main__':
329 | runtests()
330 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Prepare a release:
2 | #
3 | # - fill the changelog
4 | # - run unit tests on Linux: run "tox"
5 | # - run unit tests on Windows, run::
6 | #
7 | # \Python27\python.exe runtest.py -r
8 | # \Python27\python.exe runtest.py -r -m
9 | #
10 | # - update the version in setup.py and doc/conf.py to X.Y
11 | # - check that "python setup.py sdist" contains all files tracked by
12 | # the SCM (Mercurial): update MANIFEST.in if needed
13 | # - set release date in doc/changelog.rst
14 | # - hg ci
15 | # - hg push
16 | #
17 | # Release a new version:
18 | #
19 | # - hg tag X.Y
20 | # - hg push
21 | # - python setup.py sdist register upload
22 | # WARNING: don't publish binary wheel packages, since setup.py
23 | # hardcodes dependencies depending on the Python version.
24 | # - increment version in setup.py and doc/conf.py
25 | # - hg ci && hg push
26 | #
27 | # After the release:
28 | #
29 | # - increment version in setup.py and doc/conf.py
30 | # - hg ci -m "post-release" && hg push
31 |
32 | import sys
33 | try:
34 | from setuptools import setup
35 | SETUPTOOLS = True
36 | except ImportError:
37 | SETUPTOOLS = False
38 | # Use distutils.core as a fallback.
39 | # We won't be able to build the Wheel file on Windows.
40 | from distutils.core import setup
41 |
42 | requirements = ['eventlet']
43 | if sys.version_info >= (3, 4):
44 | # Python 3.4 and newer: asyncio is now part of the stdlib
45 | pass
46 | elif (3, 3) <= sys.version_info < (3, 4):
47 | # Python 3.3: use Tulip
48 | requirements.append('asyncio>=0.4.1')
49 | else:
50 | # Python 2.7: use Trollius
51 | requirements.append('trollius>=0.3')
52 |
53 | with open("README") as fp:
54 | long_description = fp.read()
55 |
56 | install_options = {
57 | "name": "aioeventlet",
58 | "version": "0.5.1",
59 | "license": "Apache License 2.0",
60 | "author": 'Victor Stinner',
61 | "author_email": 'victor.stinner@gmail.com',
62 |
63 | "description": "asyncio event loop scheduling callbacks in eventlet.",
64 | "long_description": long_description,
65 | "url": "http://aioeventlet.readthedocs.org/",
66 |
67 | "classifiers": [
68 | "Programming Language :: Python",
69 | "Programming Language :: Python :: 3",
70 | "License :: OSI Approved :: Apache Software License",
71 | ],
72 |
73 | "py_modules": ["aioeventlet"],
74 | #"test_suite": "runtests.runtests",
75 | }
76 | if SETUPTOOLS:
77 | install_options['install_requires'] = requirements
78 |
79 | setup(**install_options)
80 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | import aioeventlet
2 | import sys
3 | try:
4 | import asyncio
5 | except ImportError:
6 | import trollius as asyncio
7 | import unittest
8 | try:
9 | from unittest import mock
10 | except ImportError:
11 | import mock
12 |
13 | class TestCase(unittest.TestCase):
14 | def setUp(self):
15 | policy = aioeventlet.EventLoopPolicy()
16 | asyncio.set_event_loop_policy(policy)
17 | self.addCleanup(asyncio.set_event_loop_policy, None)
18 |
19 | self.loop = policy.get_event_loop()
20 | self.addCleanup(self.loop.close)
21 | self.addCleanup(asyncio.set_event_loop, None)
22 |
23 | if sys.version_info < (3,):
24 | def assertRaisesRegex(self, *args):
25 | return self.assertRaisesRegexp(*args)
26 |
--------------------------------------------------------------------------------
/tests/test_eventlet.py:
--------------------------------------------------------------------------------
1 | import aioeventlet
2 | import eventlet
3 | import sys
4 | import tests
5 | from tests import unittest
6 |
7 | SHORT_SLEEP = 0.001
8 |
9 | def eventlet_slow_append(result, value, delay):
10 | eventlet.sleep(delay)
11 | result.append(value)
12 | return value * 10
13 |
14 | def eventlet_slow_error():
15 | eventlet.sleep(SHORT_SLEEP)
16 | raise ValueError("error")
17 |
18 | try:
19 | import asyncio
20 |
21 | exec('''if 1:
22 | @asyncio.coroutine
23 | def coro_wrap_greenthread():
24 | result = []
25 |
26 | gt = eventlet.spawn(eventlet_slow_append, result, 1, 0.020)
27 | value = yield from aioeventlet.wrap_greenthread(gt)
28 | result.append(value)
29 |
30 | gt = eventlet.spawn(eventlet_slow_append, result, 2, 0.010)
31 | value = yield from aioeventlet.wrap_greenthread(gt)
32 | result.append(value)
33 |
34 | gt = eventlet.spawn(eventlet_slow_error)
35 | try:
36 | yield from aioeventlet.wrap_greenthread(gt)
37 | except ValueError as exc:
38 | result.append(str(exc))
39 |
40 | result.append(4)
41 | return result
42 |
43 | @asyncio.coroutine
44 | def coro_slow_append(result, value, delay=SHORT_SLEEP):
45 | yield from asyncio.sleep(delay)
46 | result.append(value)
47 | return value * 10
48 |
49 | @asyncio.coroutine
50 | def coro_slow_error():
51 | yield from asyncio.sleep(0.001)
52 | raise ValueError("error")
53 | ''')
54 | except ImportError:
55 | import trollius as asyncio
56 | from trollius import From, Return
57 |
58 | @asyncio.coroutine
59 | def coro_wrap_greenthread():
60 | result = []
61 |
62 | gt = eventlet.spawn(eventlet_slow_append, result, 1, 0.020)
63 | value = yield From(aioeventlet.wrap_greenthread(gt))
64 | result.append(value)
65 |
66 | gt = eventlet.spawn(eventlet_slow_append, result, 2, 0.010)
67 | value = yield From(aioeventlet.wrap_greenthread(gt))
68 | result.append(value)
69 |
70 | gt = eventlet.spawn(eventlet_slow_error)
71 | try:
72 | yield From(aioeventlet.wrap_greenthread(gt))
73 | except ValueError as exc:
74 | result.append(str(exc))
75 |
76 | result.append(4)
77 | raise Return(result)
78 |
79 | @asyncio.coroutine
80 | def coro_slow_append(result, value, delay=SHORT_SLEEP):
81 | yield From(asyncio.sleep(delay))
82 | result.append(value)
83 | raise Return(value * 10)
84 |
85 | @asyncio.coroutine
86 | def coro_slow_error():
87 | yield From(asyncio.sleep(0.001))
88 | raise ValueError("error")
89 |
90 |
91 | def greenthread_yield_future(result, loop):
92 | try:
93 | value = aioeventlet.yield_future(coro_slow_append(result, 1, 0.020))
94 | result.append(value)
95 |
96 | value = aioeventlet.yield_future(coro_slow_append(result, 2, 0.010))
97 | result.append(value)
98 |
99 | try:
100 | value = aioeventlet.yield_future(coro_slow_error())
101 | except ValueError as exc:
102 | result.append(str(exc))
103 |
104 | result.append(4)
105 | return result
106 | except Exception as exc:
107 | result.append(repr(exc))
108 | finally:
109 | loop.stop()
110 |
111 |
112 | class EventletTests(tests.TestCase):
113 | def test_stop(self):
114 | def func():
115 | self.loop.stop()
116 |
117 | eventlet.spawn(func)
118 | self.loop.run_forever()
119 |
120 | def test_soon(self):
121 | result = []
122 |
123 | def func():
124 | result.append("spawn")
125 | self.loop.stop()
126 |
127 | eventlet.spawn(func)
128 | self.loop.run_forever()
129 | self.assertEqual(result, ["spawn"])
130 |
131 | def test_soon_spawn(self):
132 | result = []
133 |
134 | def func1():
135 | result.append("spawn")
136 |
137 | def func2():
138 | result.append("spawn_after")
139 | self.loop.stop()
140 |
141 | def schedule_greenthread():
142 | eventlet.spawn(func1)
143 | eventlet.spawn_after(0.010, func2)
144 |
145 | self.loop.call_soon(schedule_greenthread)
146 | self.loop.run_forever()
147 | self.assertEqual(result, ["spawn", "spawn_after"])
148 |
149 | def test_set_debug(self):
150 | hub = eventlet.hubs.get_hub()
151 | self.assertIs(self.loop._hub, hub)
152 |
153 | self.loop.set_debug(False)
154 | self.assertEqual(hub.debug_exceptions, False)
155 | self.assertEqual(hub.debug_blocking, False)
156 |
157 | self.loop.set_debug(True)
158 | self.assertEqual(hub.debug_exceptions, True)
159 | if sys.platform != 'win32':
160 | self.assertEqual(hub.debug_blocking, True)
161 | else:
162 | self.assertEqual(hub.debug_blocking, False)
163 |
164 |
165 | class LinkFutureTests(tests.TestCase):
166 | def test_greenthread_yield_future(self):
167 | result = []
168 | self.loop.call_soon(eventlet.spawn,
169 | greenthread_yield_future, result, self.loop)
170 | self.loop.run_forever()
171 | self.assertEqual(result, [1, 10, 2, 20, 'error', 4])
172 |
173 | def test_link_coro(self):
174 | result = []
175 |
176 | def func(fut):
177 | value = aioeventlet.yield_future(coro_slow_append(result, 3))
178 | result.append(value)
179 | self.loop.stop()
180 |
181 | fut = asyncio.Future(loop=self.loop)
182 | eventlet.spawn(func, fut)
183 | self.loop.run_forever()
184 | self.assertEqual(result, [3, 30])
185 |
186 | def test_yield_future_not_running(self):
187 | result = []
188 |
189 | def func(event, fut):
190 | event.send('link')
191 | value = aioeventlet.yield_future(fut)
192 | result.append(value)
193 | self.loop.stop()
194 |
195 | event = eventlet.event.Event()
196 | fut = asyncio.Future(loop=self.loop)
197 | eventlet.spawn(func, event, fut)
198 | event.wait()
199 |
200 | self.loop.call_soon(fut.set_result, 21)
201 | self.loop.run_forever()
202 | self.assertEqual(result, [21])
203 |
204 | def test_yield_future_from_loop(self):
205 | result = []
206 |
207 | def func(fut):
208 | try:
209 | value = aioeventlet.yield_future(fut)
210 | except Exception as exc:
211 | result.append('error')
212 | else:
213 | result.append(value)
214 | self.loop.stop()
215 |
216 | fut = asyncio.Future(loop=self.loop)
217 | self.loop.call_soon(func, fut)
218 | self.loop.call_soon(fut.set_result, 'unused')
219 | self.loop.run_forever()
220 | self.assertEqual(result, ['error'])
221 |
222 | def test_yield_future_invalid_type(self):
223 | def func(obj):
224 | return aioeventlet.yield_future(obj)
225 |
226 | @asyncio.coroutine
227 | def coro_func():
228 | print("do something")
229 |
230 | def regular_func():
231 | return 3
232 |
233 | for obj in (coro_func, regular_func):
234 | gt = eventlet.spawn(func, coro_func)
235 | # ignore logged traceback
236 | with tests.mock.patch('traceback.print_exception') as m_print:
237 | self.assertRaises(TypeError, gt.wait)
238 |
239 | def test_yield_future_wrong_loop(self):
240 | result = []
241 | loop2 = asyncio.new_event_loop()
242 | self.addCleanup(loop2.close)
243 |
244 | def func(fut):
245 | try:
246 | value = aioeventlet.yield_future(fut, loop=loop2)
247 | except Exception as exc:
248 | result.append(str(exc))
249 | else:
250 | result.append(value)
251 | self.loop.stop()
252 |
253 | fut = asyncio.Future(loop=self.loop)
254 | self.loop.call_soon(func, fut)
255 | self.loop.call_soon(fut.set_result, 'unused')
256 | self.loop.run_forever()
257 | self.assertEqual(result[0],
258 | 'loop argument must agree with Future')
259 |
260 |
261 | class WrapGreenthreadTests(tests.TestCase):
262 | def test_wrap_greenthread(self):
263 | def func():
264 | eventlet.sleep(0.010)
265 | return 'ok'
266 |
267 | gt = eventlet.spawn(func)
268 | fut = aioeventlet.wrap_greenthread(gt)
269 | result = self.loop.run_until_complete(fut)
270 | self.assertEqual(result, 'ok')
271 |
272 | def test_wrap_greenthread_exc(self):
273 | self.loop.set_debug(True)
274 |
275 | def func():
276 | raise ValueError(7)
277 |
278 | # FIXME: the unit test must fail!?
279 | with tests.mock.patch('traceback.print_exception') as print_exception:
280 | gt = eventlet.spawn(func)
281 | fut = aioeventlet.wrap_greenthread(gt)
282 | self.assertRaises(ValueError, self.loop.run_until_complete, fut)
283 |
284 | # the exception must not be logger by traceback: the caller must
285 | # consume the exception from the future object
286 | self.assertFalse(print_exception.called)
287 |
288 | def test_wrap_greenthread_running(self):
289 | def func():
290 | return aioeventlet.wrap_greenthread(gt)
291 |
292 | self.loop.set_debug(False)
293 | gt = eventlet.spawn(func)
294 | msg = "wrap_greenthread: the greenthread is running"
295 | self.assertRaisesRegex(RuntimeError, msg, gt.wait)
296 |
297 | def test_wrap_greenthread_dead(self):
298 | def func():
299 | return 'ok'
300 |
301 | gt = eventlet.spawn(func)
302 | result = gt.wait()
303 | self.assertEqual(result, 'ok')
304 |
305 | msg = "wrap_greenthread: the greenthread already finished"
306 | self.assertRaisesRegex(RuntimeError, msg,
307 | aioeventlet.wrap_greenthread, gt)
308 |
309 | def test_coro_wrap_greenthread(self):
310 | result = self.loop.run_until_complete(coro_wrap_greenthread())
311 | self.assertEqual(result, [1, 10, 2, 20, 'error', 4])
312 |
313 | def test_wrap_invalid_type(self):
314 | def func():
315 | pass
316 | self.assertRaises(TypeError, aioeventlet.wrap_greenthread, func)
317 |
318 | @asyncio.coroutine
319 | def coro_func():
320 | pass
321 | coro_obj = coro_func()
322 | self.addCleanup(coro_obj.close)
323 | self.assertRaises(TypeError, aioeventlet.wrap_greenthread, coro_obj)
324 |
325 |
326 | class WrapGreenletTests(tests.TestCase):
327 | def test_wrap_greenlet(self):
328 | def func():
329 | eventlet.sleep(0.010)
330 | return "ok"
331 |
332 | gt = eventlet.spawn_n(func)
333 | fut = aioeventlet.wrap_greenthread(gt)
334 | result = self.loop.run_until_complete(fut)
335 | self.assertEqual(result, "ok")
336 |
337 | def test_wrap_greenlet_exc(self):
338 | self.loop.set_debug(True)
339 |
340 | def func():
341 | raise ValueError(7)
342 |
343 | gt = eventlet.spawn_n(func)
344 | fut = aioeventlet.wrap_greenthread(gt)
345 | self.assertRaises(ValueError, self.loop.run_until_complete, fut)
346 |
347 | def test_wrap_greenlet_running(self):
348 | event = eventlet.event.Event()
349 |
350 | def func():
351 | try:
352 | gt = eventlet.getcurrent()
353 | fut = aioeventlet.wrap_greenthread(gt)
354 | except Exception as exc:
355 | event.send_exception(exc)
356 | else:
357 | event.send(fut)
358 |
359 | eventlet.spawn_n(func)
360 | msg = "wrap_greenthread: the greenthread is running"
361 | self.assertRaisesRegex(RuntimeError, msg, event.wait)
362 |
363 | def test_wrap_greenlet_dead(self):
364 | event = eventlet.event.Event()
365 | def func():
366 | event.send('done')
367 |
368 | gt = eventlet.spawn_n(func)
369 | event.wait()
370 | msg = "wrap_greenthread: the greenthread already finished"
371 | self.assertRaisesRegex(RuntimeError, msg, aioeventlet.wrap_greenthread, gt)
372 |
373 |
374 | if __name__ == '__main__':
375 | import unittest
376 | unittest.main()
377 |
--------------------------------------------------------------------------------
/tests/test_greenlet.py:
--------------------------------------------------------------------------------
1 | import aioeventlet
2 | import greenlet
3 | import tests
4 |
5 |
6 | class WrapGreenletTests(tests.TestCase):
7 | def test_wrap_greenlet(self):
8 | def func(value):
9 | return value * 3
10 |
11 | gl = greenlet.greenlet(func)
12 | fut = aioeventlet.wrap_greenthread(gl)
13 | gl.switch(5)
14 | result = self.loop.run_until_complete(fut)
15 | self.assertEqual(result, 15)
16 |
17 | def test_wrap_greenlet_exc(self):
18 | def func():
19 | raise ValueError(7)
20 |
21 | gl = greenlet.greenlet(func)
22 | fut = aioeventlet.wrap_greenthread(gl)
23 | gl.switch()
24 | self.assertRaises(ValueError, self.loop.run_until_complete, fut)
25 |
26 | def test_wrap_greenlet_no_run_attr(self):
27 | gl = greenlet.greenlet()
28 | msg = "wrap_greenthread: the run attribute of the greenlet is not set"
29 | self.assertRaisesRegex(RuntimeError, msg,
30 | aioeventlet.wrap_greenthread, gl)
31 |
32 | def test_wrap_greenlet_running(self):
33 | def func(value):
34 | gl = greenlet.getcurrent()
35 | return aioeventlet.wrap_greenthread(gl)
36 |
37 | gl = greenlet.greenlet(func)
38 | msg = "wrap_greenthread: the greenthread is running"
39 | self.assertRaisesRegex(RuntimeError, msg, gl.switch, 5)
40 |
41 | def test_wrap_greenlet_dead(self):
42 | def func(value):
43 | return value * 3
44 |
45 | gl = greenlet.greenlet(func)
46 | gl.switch(5)
47 | msg = "wrap_greenthread: the greenthread already finished"
48 | self.assertRaisesRegex(RuntimeError, msg, aioeventlet.wrap_greenthread, gl)
49 |
50 |
51 | if __name__ == '__main__':
52 | import unittest
53 | unittest.main()
54 |
55 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | minversion = 1.4
3 | envlist = py27,py27_old,py27_patch,py33,py3_patch,py3_old,py34,py35
4 |
5 | [testenv]
6 | commands=
7 | python runtests.py -r
8 | python run_aiotest.py -r
9 |
10 | [testenv:py27]
11 | setenv =
12 | TROLLIUSDEBUG = 1
13 | deps=
14 | aiotest
15 | eventlet
16 | mock
17 | trollius
18 |
19 | [testenv:py27_old]
20 | basepython = python2.7
21 | setenv =
22 | TROLLIUSDEBUG = 1
23 | deps=
24 | aiotest
25 | eventlet==0.14.0
26 | mock
27 | trollius==0.3
28 |
29 | [testenv:py27_patch]
30 | basepython = python2.7
31 | setenv =
32 | TROLLIUSDEBUG = 1
33 | deps=
34 | aiotest
35 | eventlet
36 | mock
37 | trollius
38 | commands=
39 | python runtests.py -r -m
40 | python run_aiotest.py -r -m
41 |
42 | [testenv:py33]
43 | setenv =
44 | PYTHONASYNCIODEBUG = 1
45 | deps=
46 | aiotest
47 | asyncio
48 | eventlet
49 |
50 | [testenv:py3_patch]
51 | basepython = python3
52 | setenv =
53 | PYTHONASYNCIODEBUG = 1
54 | deps=
55 | aiotest
56 | asyncio
57 | eventlet
58 | commands=
59 | python runtests.py -r -m
60 | python run_aiotest.py -r -m
61 |
62 | [testenv:py3_old]
63 | basepython = python3
64 | setenv =
65 | PYTHONASYNCIODEBUG = 1
66 | deps=
67 | aiotest
68 | asyncio==0.4.1
69 | eventlet==0.15.0
70 |
71 | [testenv:py34]
72 | basepython = python3.4
73 | setenv =
74 | PYTHONASYNCIODEBUG = 1
75 | deps=
76 | aiotest
77 | eventlet
78 |
79 | [testenv:py35]
80 | basepython = python3.5
81 | setenv =
82 | PYTHONASYNCIODEBUG = 1
83 | deps=
84 | aiotest
85 | eventlet
86 |
--------------------------------------------------------------------------------