├── eventloop ├── loop.pyc ├── __init__.pyc ├── loopimpl.pyc ├── __init__.py ├── loopimpl.py └── loop.py ├── README.md ├── .gitignore └── LICENSE /eventloop/loop.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oshynsong/eventloop/HEAD/eventloop/loop.pyc -------------------------------------------------------------------------------- /eventloop/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oshynsong/eventloop/HEAD/eventloop/__init__.pyc -------------------------------------------------------------------------------- /eventloop/loopimpl.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oshynsong/eventloop/HEAD/eventloop/loopimpl.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `eventloop` - A independent event loop framework for easily event programming 2 | 3 | `eventloop` is inspired by the tornado's IOLoop and absorbs the core part from tornado 4 | for independant use. It can be easily added to any event-related programming projects. 5 | 6 | Any problem contacts Oshyn Song (dualyangsong@gmail.com). 7 | -------------------------------------------------------------------------------- /eventloop/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding = utf-8 -*- 3 | # 4 | # Copyright 2016 Oshyn Song 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, with_statement 19 | 20 | # Epoll constants 21 | _EPOLLIN = 0x001 22 | _EPOLLPRI = 0x002 23 | _EPOLLOUT = 0x004 24 | _EPOLLERR = 0x008 25 | _EPOLLHUP = 0x010 26 | _EPOLLRDHUP = 0x2000 27 | _EPOLLONESHOT = (1 << 30) 28 | _EPOLLET = (1 << 31) 29 | 30 | # eventloop supported event types 31 | EVENT_NONE = 0 32 | EVENT_READ = _EPOLLIN 33 | EVENT_WRITE = _EPOLLOUT 34 | EVENT_ERROR = _EPOLLERR | _EPOLLHUP 35 | 36 | from eventloop.loop import Loop 37 | 38 | __version__ = '0.1.0' 39 | __author__ = 'Oshyn Song (dualyangsong@gmail.com)' 40 | __all__ = ['Loop', 'EVENT_NONE', 'EVENT_READ', 'EVENT_WRITE', 'EVENT_ERROR'] 41 | 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /eventloop/loopimpl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding = utf-8 -*- 3 | # 4 | # Copyright 2016 Oshyn Song 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, with_statement 19 | 20 | import sys 21 | import time 22 | import logging 23 | import select 24 | 25 | from eventloop import EVENT_NONE, EVENT_READ, EVENT_WRITE, EVENT_ERROR 26 | 27 | 28 | class LoopImpl(object): 29 | """ Base class for concrete implementations of event loop class """ 30 | def __init__(self): 31 | pass 32 | 33 | def close(self): 34 | raise NotImplementedError() 35 | 36 | def register(self, fd, event_type): 37 | raise NotImplementedError() 38 | 39 | def unregister(self, fd): 40 | raise NotImplementedError() 41 | 42 | def modify(self, fd): 43 | raise NotImplementedError() 44 | 45 | def pool(self, timeout): 46 | """ Pool the ready event """ 47 | raise NotImplementedError() 48 | 49 | 50 | class EpollLoop(LoopImpl): 51 | """ 52 | A epoll-based event loop implementation for 53 | system supporting epoll system-call. 54 | """ 55 | def __init__(self, ): 56 | super(EpollLoop, self).__init__() 57 | if not hasattr(select, 'epoll'): 58 | raise SystemError("Not support epoll for current system.") 59 | self._epoll = select.epoll() 60 | 61 | def close(self): 62 | self._epoll.close() 63 | 64 | def register(self, fd, event_type): 65 | self._epoll.register(fd, event_type) 66 | 67 | def unregister(self, fd): 68 | self._epoll.unregister(fd) 69 | 70 | def modify(self, fd, event_type): 71 | self._epoll.modify(fd, event_type) 72 | 73 | def poll(self, timeout): 74 | return self._epoll.poll(timeout) 75 | 76 | 77 | class KQueueLoop(LoopImpl): 78 | """ 79 | A KQueue-based event loop implementation for BSD/Mac systems. 80 | """ 81 | # Max events can be handled by kqueue 82 | MAX_EVENTS = 1024 83 | 84 | def __init__(self): 85 | super(KQueueLoop, self).__init__() 86 | if not hasattr(select, 'kqueue'): 87 | raise SystemError("Not support kqueue for current system.") 88 | self._kqueue = select.kqueue() 89 | self._active = {} 90 | 91 | def close(self): 92 | self._kqueue.close() 93 | 94 | def fileno(self): 95 | return self._kqueue.fileno() 96 | 97 | def register(self, fd, event_type): 98 | if fd in self._active: 99 | raise IOError("fd %s already registered" % fd) 100 | self._control(fd, event_type, select.KQ_EV_ADD) 101 | self._active[fd] = event_type 102 | 103 | def unregister(self, fd): 104 | try: 105 | event_type = self._active.pop(fd) 106 | self._control(fd, event_type, select.KQ_EV_DELETE) 107 | except KeyError as e: 108 | # If fd not in active, just ignore 109 | pass 110 | 111 | def modify(self, fd, event_type): 112 | """ Change the given fd to the give event type """ 113 | self.unregister(fd) 114 | self.register(fd, event_type) 115 | 116 | def poll(self, timeout): 117 | if timeout < 0: 118 | timeout = None 119 | kevents = self._kqueue.control(None, KQueueLoop.MAX_EVENTS, timeout) 120 | events = {} 121 | for ke in kevents: 122 | fd = ke.ident 123 | if ke.filter == select.KQ_FILTER_READ: 124 | events[fd] = events.get(fd, 0) | EVENT_READ 125 | if ke.filter == select.KQ_FILTER_WRITE: 126 | if ke.flags & select.KQ_EV_EOF: 127 | # If an asynchronous connection is refused, kqueue 128 | # returns a write event with the EOF flag set. 129 | # Turn this into an error for consistency with the 130 | # other IOLoop implementations. 131 | # Note that for read events, EOF may be returned before 132 | # all data has been consumed from the socket buffer, 133 | # so we only check for EOF on write events. 134 | events[fd] = EVENT_ERROR 135 | else: 136 | events[fd] = events.get(fd, 0) | EVENT_WRITE 137 | if ke.flags & select.KQ_EV_ERROR: 138 | events[fd] = events.get(fd, 0) | EVENT_ERROR 139 | return events.iteritems() 140 | 141 | def _control(self, fd, event_type, flags): 142 | kevents = [] 143 | if event_type & EVENT_WRITE: 144 | kevents.append(select.kevent( 145 | fd, filter=select.KQ_FILTER_WRITE, flags=flags)) 146 | if events & EVENT_READ: 147 | kevents.append(select.kevent( 148 | fd, filter=select.KQ_FILTER_READ, flags=flags)) 149 | for ke in kevents: 150 | self._kqueue.control([ke], 0) 151 | 152 | 153 | class SelectLoop(LoopImpl): 154 | """ 155 | A select-based event loop implementation for 156 | system not supporting epoll and kqueue 157 | """ 158 | def __init__(self, ): 159 | super(SelectLoop, self).__init__() 160 | self._read_fd_set = set() 161 | self._write_fd_set = set() 162 | self._error_fd_set = set() 163 | self._fd_sets = (self._read_fd_set, 164 | self._write_fd_set, 165 | self._error_fd_set) 166 | 167 | def close(self): 168 | self._read_fd_set.clear() 169 | self._write_fd_set.clear() 170 | self._error_fd_set.clear() 171 | 172 | def register(self, fd, event_type): 173 | if fd in self._read_fd_set or fd in self._write_fd_set or fd in self._error_fd_set: 174 | raise IOError("fd %s already registered" % fd) 175 | 176 | if event_type & EVENT_READ: 177 | self._read_fd_set.add(fd) 178 | if event_type & EVENT_WRITE: 179 | self._write_fd_set.add(fd) 180 | if event_type & EVENT_ERROR: 181 | self._error_fd_set.add(fd) 182 | 183 | def unregister(self, fd): 184 | """ Remove the watching fd """ 185 | self._read_fd_set.discard(fd) 186 | self._write_fd_set.discard(fd) 187 | self._error_fd_set.discard(fd) 188 | 189 | def modify(self, fd, event_type): 190 | """ Change the given fd to the give event type """ 191 | self.unregister(fd) 192 | self.register(fd, event_type) 193 | 194 | def poll(self, timeout): 195 | readable, writeable, errors = select.select( 196 | self._read_fd_set, 197 | self._write_fd_set, 198 | self._error_fd_set, 199 | timeout) 200 | events = {} 201 | for fd in readable: 202 | events[fd] = events.get(fd, 0) | EVENT_READ 203 | for fd in writeable: 204 | events[fd] = events.get(fd, 0) | EVENT_WRITE 205 | for fd in errors: 206 | events[fd] = events.get(fd, 0) | EVENT_ERROR 207 | return events.items() 208 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /eventloop/loop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding = utf-8 -*- 3 | # 4 | # Copyright 2016 Oshyn Song 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from __future__ import absolute_import, division, print_function, with_statement 19 | 20 | import sys 21 | import logging 22 | import select 23 | import math 24 | import time 25 | import threading 26 | import numbers 27 | import functools 28 | import heapq 29 | import traceback 30 | from collections import defaultdict 31 | 32 | from eventloop import EVENT_NONE, EVENT_READ, EVENT_WRITE, EVENT_ERROR 33 | from eventloop.loopimpl import EpollLoop 34 | from eventloop.loopimpl import KQueueLoop 35 | from eventloop.loopimpl import SelectLoop 36 | 37 | try: 38 | import thread # py2 39 | except ImportError: 40 | import _thread as thread # py3 41 | 42 | 43 | class Loop(object): 44 | """ Event loop class for common usage. 45 | 46 | Use ``epoll`` (Linux) or ``kqueue`` (BSD and Mac OS X) if they 47 | are available, or else we fall back on select(). 48 | """ 49 | 50 | _instance_lock = threading.Lock() 51 | _current = threading.local() 52 | _POLL_TIMEOUT = 3600.0 53 | _THREAD_POOL_SIZE = 3 54 | 55 | @staticmethod 56 | def instance(): 57 | if not hasattr(Loop, "_instance"): 58 | with Loop._instance_lock: 59 | if not hasattr(Loop, "_instance"): 60 | # New instance after double check 61 | Loop._instance = Loop() 62 | return Loop._instance 63 | 64 | @staticmethod 65 | def is_initialized(): 66 | return hasattr(Loop, '_instance') 67 | 68 | @staticmethod 69 | def del_instance(): 70 | if hasattr(Loop, '_instance'): 71 | del Loop._instance 72 | 73 | @staticmethod 74 | def current(instance=True): 75 | current = getattr(Loop._current, "instance", None) 76 | if current is None and instance: 77 | return Loop.instance() 78 | return current 79 | 80 | def install_current(self): 81 | Loop._current.instance = self 82 | 83 | @staticmethod 84 | def del_current(): 85 | Loop._current.instance = None 86 | 87 | def __init__(self): 88 | self._thread_ident = None 89 | if Loop.current(instance=False) is None: 90 | self.install_current() 91 | 92 | model = None 93 | if hasattr(select, 'epoll'): 94 | self._impl = EpollLoop() 95 | model = 'epoll' 96 | elif hasattr(select, 'kqueue'): 97 | self._impl = KQueueLoop() 98 | model = 'kqueue' 99 | elif hasattr(select, 'select'): 100 | self._impl = SelectLoop() 101 | model = 'select' 102 | else: 103 | raise Exception('can not find any async event stratege') 104 | logging.debug('using event model: %s', model) 105 | 106 | # {fd1: {event: handler, event: handler,...}, fd2: {event: handler, ...},...} 107 | self._poll_handlers = defaultdict(dict) 108 | 109 | # [handler1, handler2,...] 110 | self._pure_handlers = [] 111 | self._pure_handler_lock = threading.Lock() 112 | 113 | # [handler1, handler2,...] 114 | self._periodic_handlers = [] 115 | self._periodic_handler_cancels = 0 116 | 117 | self._running = False # for poll 118 | self._closing = False # for periodic 119 | self._stopped = False 120 | 121 | def _split_fd(self, fd): 122 | """ Returns an (fd, obj) pair from an ``fd`` parameter """ 123 | try: 124 | return fd.fileno(), fd 125 | except AttributeError: 126 | return fd, fd 127 | 128 | def _close_fd(self, fd): 129 | """ Utility method to close an ``fd`` """ 130 | try: 131 | try: 132 | fd.close() 133 | except AttributeError: 134 | os.close(fd) 135 | except OSError: 136 | pass 137 | 138 | def _setup_logging(self): 139 | logging.getLogger('eventloop') 140 | 141 | def _run_handler(self, handler): 142 | """ Runs a handler with error handling """ 143 | logging.debug('run handler: %s', repr(handler)) 144 | #threading.Thread(target=handler).start() 145 | handler() 146 | if handler.error is not None: 147 | logging.error('Running handler occurs error: %s', handler.error) 148 | traceback.print_exc() 149 | del handler 150 | 151 | def time(self): 152 | """Returns the current time according to the `Loop`'s clock. 153 | The return value is a floating-point number relative to an 154 | unspecified time in the past(just use time.time() here). 155 | """ 156 | return time.time() 157 | 158 | def stop(self): 159 | self._running = False 160 | self._stopped = True 161 | 162 | def close(self, close_fds=False): 163 | """ Closes the `Loop`, freeing any resources used """ 164 | with self._pure_handler_lock: 165 | self._closing = True 166 | if close_fds: 167 | for fd in self._poll_handlers.iterkeys(): 168 | self._close_fd(fd) 169 | self._pure_handlers = None 170 | self._periodic_handlers = None 171 | # Close the loopimpl 172 | self._impl.close() 173 | 174 | def add_poll_handler(self, fd, callback, events, *args, **kwargs): 175 | """ 176 | Registers the given callback to receive the given events for ``fd``. 177 | 178 | The ``fd`` argument may either be an integer file descriptor or 179 | a file-like object with a ``fileno()`` method (and optionally a 180 | ``close()`` method, which may be called when the `Loop` is shut 181 | down). 182 | The ``events`` argument is a bitwise or of the constants 183 | ``EVENT_READ``, ``EVENT_WRITE``, and ``EVENT_ERROR``. 184 | When an event occurs, ``handler(fd, events)`` will be run. 185 | """ 186 | fd, obj = self._split_fd(fd) 187 | handler = _LoopHandler(functools.partial(callback, *args, **kwargs), 188 | fd=fd, events=events) 189 | if not self._poll_handlers.get(fd, None): 190 | self._poll_handlers[fd] = {events : handler} 191 | self._impl.register(fd, events) 192 | else: 193 | self._poll_handlers[fd][events] = handler 194 | old_event = events 195 | for e in self._poll_handlers[fd].iterkeys(): 196 | old_event |= e 197 | self._impl.modify(fd, old_event | events) 198 | logging.debug('%s' % repr(self._poll_handlers)) 199 | 200 | def remove_poll_handler(self, fd, events=None): 201 | """ Remove given listening events on ``fd`` """ 202 | fd, obj = self._split_fd(fd) 203 | if events == None: 204 | self._poll_handlers.pop(fd, None) 205 | try: 206 | self._impl.unregister(fd) 207 | except Exception as e: 208 | logging.error('Remove handler from Loop occurs exception: %s', e.message) 209 | logging.debug('%s' % repr(self._poll_handlers)) 210 | return 211 | # Just remove the specific event on fd 212 | event_handler = self._poll_handlers.get(fd, None) 213 | if not event_handler: 214 | return 215 | new_event_handler = {} 216 | new_event = EVENT_NONE 217 | for e, h in event_handler.items(): 218 | k = e & (~events) 219 | if k != 0: 220 | new_event_handler[k] = h 221 | new_event |= k 222 | # Change the poll handlers 223 | if len(new_event_handler) > 0: 224 | self._poll_handlers[fd] = new_event_handler 225 | else: 226 | self._poll_handlers.pop(fd, None) 227 | # Change the listening events 228 | if new_event != EVENT_NONE: 229 | self._impl.modify(fd, new_event) 230 | else: 231 | self._impl.unregister(fd) 232 | logging.debug('%s' % repr(self._poll_handlers)) 233 | 234 | def update_poll_handler(self, fd, events): 235 | """ Update the events listening on fd """ 236 | fd, obj = self._split_fd(fd) 237 | self._impl.modify(fd, events) 238 | 239 | def add_periodic_handler(self, callback, deadline, *args, **kwargs): 240 | """ Construct a periodic handler and add to the heap """ 241 | if isinstance(deadline, numbers.Real): 242 | handler = _LoopHandler( 243 | functools.partial(callback, *args, **kwargs), 244 | deadline=deadline) 245 | heapq.heappush(self._periodic_handlers, handler) 246 | #logging.debug('%s(total: %d)' % (repr(handler), len(self._periodic_handlers))) 247 | return handler 248 | raise TypeError('Not valid deadline(must be a real number)') 249 | 250 | def remove_periodic_handler(self, handler): 251 | """ Remove a periodic pending handler as returned by `add_periodic_handler` """ 252 | #logging.debug('%s' % repr(handler)) 253 | if not handler: 254 | return 255 | handler.callback = None 256 | self._periodic_handler_cancels += 1 257 | # delay remove for: self._periodic_handlers.remove(handler) 258 | 259 | def add_pure_handler(self, callback, *args, **kwargs): 260 | """ 261 | Add the handler to be called on the next loop iteration 262 | 263 | It is safe to call this method from any thread at any time. 264 | """ 265 | handler = _LoopHandler(functools.partial(callback, *args, **kwargs)) 266 | if thread.get_ident() != self._thread_ident: 267 | with self._pure_handler_lock: 268 | if self._closing: 269 | return 270 | self._pure_handlers.append(handler) 271 | else: 272 | if self._closing: 273 | return 274 | self._pure_handlers.append(handler) 275 | logging.debug('%s(total: %d)' % (repr(handler), len(self._pure_handlers))) 276 | return handler 277 | 278 | def remove_pure_handler(self, handler): 279 | """ Remove a pure pending handler as returned by `add_pure_handler` """ 280 | self._pure_handlers.remove(handler) 281 | logging.debug('%s' % repr(handler)) 282 | 283 | def start(self): 284 | if self._running: 285 | raise RuntimeError("Loop is already running") 286 | self._setup_logging() 287 | if self._stopped: 288 | self._stopped = False 289 | return 290 | old_current = getattr(Loop._current, "instance", None) 291 | Loop._current.instance = self 292 | self._thread_ident = thread.get_ident() 293 | self._running = True 294 | 295 | try: 296 | while True: 297 | # Run pure handlers 298 | with self._pure_handler_lock: 299 | pure_handlers = self._pure_handlers 300 | self._pure_handlers = [] 301 | if pure_handlers: 302 | for handler in pure_handlers: 303 | self._run_handler(handler) 304 | 305 | # Run periodic handlers 306 | periodic_handlers = [] 307 | if self._periodic_handlers: 308 | now = self.time() 309 | while self._periodic_handlers: 310 | if self._periodic_handlers[0].callback is None: 311 | heapq.heappop(self._periodic_handlers) 312 | self._periodic_handler_cancels -= 1 313 | elif self._periodic_handlers[0].deadline <= now: 314 | handler = heapq.heappop(self._periodic_handlers) 315 | periodic_handlers.append(handler) 316 | else: 317 | break 318 | if (self._periodic_handler_cancels > 512 and 319 | self._periodic_handler_cancels > (len(self._periodic_handlers) >> 1)): 320 | self._periodic_handler_cancels = 0 321 | self._periodic_handlers = [h for h in self._periodic_handlers 322 | if h.callback is not None] 323 | heapq.heapify(self._periodic_handlers) 324 | #logging.debug('periodic running: %s' % repr(periodic_handlers)) 325 | for handler in periodic_handlers: 326 | if handler.callback is not None: 327 | self._run_handler(handler) 328 | 329 | # Release memory when necessory before running the poll events 330 | pure_handlers = periodic_handlers = handler = None 331 | 332 | # Generate the poll time and run poll handlers 333 | poll_timeout = None 334 | if self._pure_handlers: 335 | # If adding new pure handlers, poll should return immediately 336 | # without any waiting. 337 | poll_timeout = 0.0 338 | elif self._periodic_handlers: 339 | # If periodic handlers adding, get the next deeadline delta for waiting 340 | next_periodic = self._periodic_handlers[0].deadline - self.time() 341 | poll_timeout = max(0, min(next_periodic, Loop._POLL_TIMEOUT)) 342 | else: 343 | poll_timeout = Loop._POLL_TIMEOUT 344 | if not self._running or self._closing: 345 | break 346 | ready_fd_event = self._impl.poll(poll_timeout) 347 | for fd, event in ready_fd_event: 348 | event_handler = self._poll_handlers[fd] 349 | for e, h in event_handler.items(): 350 | if event & e != 0: 351 | self._run_handler(h) 352 | poll_timeout = ready_fd_event = fd = event = None 353 | finally: 354 | self._stopped = False 355 | Loop._current.instance = old_current 356 | 357 | 358 | class _LoopHandler(object): 359 | """ Global handler for Loop """ 360 | # Reduce memory overhead when there are lots of pending callbacks 361 | __slots__ = ['callback', 'fd', 'events', 'deadline', 'error'] 362 | 363 | def __init__(self, callback, fd=None, events=None, deadline=None, error=None): 364 | if deadline != None and not isinstance(deadline, numbers.Real): 365 | raise TypeError("Unsupported deadline %r" % deadline) 366 | self.callback = callback 367 | self.fd = fd 368 | self.events = events 369 | self.deadline = deadline 370 | self.error = None 371 | 372 | def __lt__(self, other): 373 | delta = self.deadline - other.deadline 374 | if delta != 0: 375 | return delta 376 | else: 377 | return id(self) - id(other) 378 | 379 | def __call__(self): 380 | try: 381 | self.callback() 382 | except Exception as e: 383 | self.error = e.message 384 | 385 | def __repr__(self): 386 | res = 'handler %s' % repr(self.callback) 387 | res += (' with fd=%d' % self.fd) if self.fd is not None else '' 388 | res += (' with deadline=%d' % self.deadline) if self.deadline is not None else '' 389 | res += (' with events=%d' % self.events) if self.events is not None else '' 390 | return res 391 | 392 | 393 | --------------------------------------------------------------------------------