├── .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 | --------------------------------------------------------------------------------