├── tests ├── __init__.py ├── support.py └── test_selectors2.py ├── setup.cfg ├── MANIFEST.in ├── dev-requirements.txt ├── .travis ├── run.sh └── install.sh ├── tox.ini ├── LICENSE ├── .travis.yml ├── .gitignore ├── CHANGELOG.rst ├── setup.py ├── appveyor.yml ├── README.rst ├── .appveyor └── install.ps1 └── selectors2.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst CHANGELOG.rst LICENSE dev-requirements.txt tox.ini 2 | recursive-include tests *.py 3 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | check-manifest==0.36 2 | nose==1.3.7 3 | psutil==5.2.2 4 | readme_renderer==17.2 5 | mock==2.0.0 6 | -------------------------------------------------------------------------------- /.travis/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | if [[ "$(uname -s)" == "Darwin" ]]; then 7 | # initialize our pyenv 8 | PYENV_ROOT="$HOME/.pyenv" 9 | PATH="$PYENV_ROOT/bin:$PATH" 10 | eval "$(pyenv init -)" 11 | fi 12 | 13 | tox 14 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = lint, packaging, py26, py27, py33, py34, py35, py36 3 | skip_missing_interpreters = true 4 | 5 | [testenv] 6 | deps= -r{toxinidir}/dev-requirements.txt 7 | commands= 8 | pip install . 9 | nosetests tests/test_selectors2.py 10 | passenv = TRAVIS APPVEYOR 11 | 12 | [testenv:py26] 13 | # Additional dependency on unittest2 for Python 2.6 14 | deps= 15 | {[testenv]deps} 16 | unittest2 17 | 18 | [testenv:lint] 19 | commands = 20 | python -m pip install flake8 21 | flake8 --max-line-length 100 selectors2.py 22 | 23 | [testenv:packaging] 24 | commands = 25 | check-manifest --ignore *.yml,.mention-bot,.appveyor*,.travis*,.github* 26 | python setup.py check --metadata --restructuredtext --strict 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Seth Michael Larson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.travis/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | if [[ "$(uname -s)" == 'Darwin' ]]; then 7 | # install pyenv 8 | git clone --depth 1 https://github.com/yyuu/pyenv.git ~/.pyenv 9 | PYENV_ROOT="$HOME/.pyenv" 10 | PATH="$PYENV_ROOT/bin:$PATH" 11 | eval "$(pyenv init -)" 12 | 13 | case "${TOXENV}" in 14 | py26) 15 | pyenv install 2.6.9 16 | pyenv global 2.6.9 17 | ;; 18 | py27) 19 | curl -O https://bootstrap.pypa.io/get-pip.py 20 | python get-pip.py --user 21 | ;; 22 | py33) 23 | pyenv install 3.3.6 24 | pyenv global 3.3.6 25 | ;; 26 | py34) 27 | pyenv install 3.4.5 28 | pyenv global 3.4.5 29 | ;; 30 | py35) 31 | pyenv install 3.5.2 32 | pyenv global 3.5.2 33 | ;; 34 | py36) 35 | pyenv install 3.6.0 36 | pyenv global 3.6.0 37 | ;; 38 | esac 39 | pyenv rehash 40 | pip install -U setuptools 41 | pip install --user virtualenv 42 | else 43 | pip install virtualenv 44 | fi 45 | 46 | pip install tox 47 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | 4 | matrix: 5 | include: 6 | # Miscellaneous Builds 7 | - python: 3.6 8 | env: TOXENV=lint 9 | - python: 3.6 10 | env: TOXENV=packaging 11 | 12 | # Linux Builds 13 | - python: 2.6 14 | env: TOXENV=py26 15 | - python: 2.7 16 | env: TOXENV=py27 17 | - python: 3.3 18 | env: TOXENV=py33 19 | - python: 3.4 20 | env: TOXENV=py34 21 | - python: 3.5 22 | env: TOXENV=py35 23 | - python: 3.6 24 | env: TOXENV=py36 25 | 26 | # OSX Builds 27 | - language: generic 28 | os: osx 29 | env: TOXENV=py26 30 | - language: generic 31 | os: osx 32 | env: TOXENV=py27 33 | - language: generic 34 | os: osx 35 | env: TOXENV=py33 36 | - language: generic 37 | os: osx 38 | env: TOXENV=py34 39 | - language: generic 40 | os: osx 41 | env: TOXENV=py35 42 | - language: generic 43 | os: osx 44 | env: TOXENV=py36 45 | 46 | cache: 47 | - pip 48 | - directories: 49 | - ${HOME}/.cache 50 | 51 | install: 52 | - chmod a+x ./.travis/install.sh ./.travis/run.sh 53 | - ./.travis/install.sh 54 | 55 | script: 56 | - ./.travis/run.sh 57 | 58 | notifications: 59 | email: false 60 | 61 | branches: 62 | only: 63 | - master 64 | - release 65 | 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | 93 | # PyCharm 94 | .idea/ 95 | 96 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | Release 2.0.2 (July 21, 2020) 5 | ----------------------------- 6 | 7 | * [BUGFIX] Added support for ``long`` integers in Python 2.x. 8 | 9 | Release 2.0.1 (August 17, 2017) 10 | ------------------------------- 11 | 12 | * [BUGFIX] Timeouts would not be properly recalculated after receiving an EINTR error. 13 | 14 | Release 2.0.0 (May 30, 2017) 15 | ---------------------------- 16 | 17 | * [FEATURE] Add support for Jython with ``JythonSelectSelector``. 18 | * [FEATURE] Add support for ``/dev/devpoll`` with ``DevpollSelector``. 19 | * [CHANGE] Raises a ``RuntimeError`` instead of ``ValueError`` if there is no selector available. 20 | * [CHANGE] No longer wraps exceptions in ``SelectorError``, raises original exception including 21 | in timeout situations. 22 | * [BUGFIX] Detect defects in a system that defines a selector but does not implement it. 23 | * [BUGFIX] Can now detect a change in the ``select`` module after import such as when 24 | ``gevent.monkey.monkey_patch()`` is called before importing ``selectors2``. 25 | 26 | Release 1.1.1 (February 6, 2017) 27 | -------------------------------- 28 | 29 | * [BUGFIX] Platforms that define ``select.kqueue`` would not have ``KqueueSelector`` as the ``DefaultSelector``. 30 | 31 | Release 1.1.0 (January 17, 2017) 32 | -------------------------------- 33 | 34 | * [FEATURE] Make system calls faster for Python versions that support PEP 475. 35 | * [FEATURE] Wheels are now universal. 36 | 37 | Release 1.0.0 (November 3, 2016) 38 | -------------------------------- 39 | 40 | * Initial implementation of ``selectors2``. 41 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from setuptools import setup 4 | 5 | # Get the version (borrowed from SQLAlchemy) 6 | base_path = os.path.dirname(__file__) 7 | with open(os.path.join(base_path, 'selectors2.py')) as f: 8 | VERSION = re.compile(r'.*__version__ = \'(.*?)\'', re.S).match(f.read()).group(1) 9 | 10 | with open('README.rst') as f: 11 | long_description = f.read() 12 | 13 | with open('CHANGELOG.rst') as f: 14 | changelog = f.read() 15 | 16 | if __name__ == '__main__': 17 | setup(name='selectors2', 18 | description='Back-ported, durable, and portable selectors', 19 | long_description=long_description + '\n\n' + changelog, 20 | license='MIT', 21 | url='https://www.github.com/sethmlarson/selectors2', 22 | version=VERSION, 23 | author='Seth Michael Larson', 24 | author_email='sethmichaellarson@gmail.com', 25 | maintainer='Seth Michael Larson', 26 | maintainer_email='sethmichaellarson@gmail.com', 27 | install_requires=[], 28 | keywords=['async', 'file', 'socket', 'select', 'backport'], 29 | py_modules=['selectors2'], 30 | zip_safe=False, 31 | classifiers=['Programming Language :: Python :: 2', 32 | 'Programming Language :: Python :: 2.6', 33 | 'Programming Language :: Python :: 2.7', 34 | 'Programming Language :: Python :: 3', 35 | 'Programming Language :: Python :: 3.3', 36 | 'Programming Language :: Python :: 3.4', 37 | 'License :: OSI Approved :: Python Software Foundation License', 38 | 'License :: OSI Approved :: MIT License']) 39 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor.yml from https://github.com/ogrisel/python-appveyor-demo 2 | # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 3 | 4 | build: off 5 | 6 | environment: 7 | matrix: 8 | - PYTHON: "C:\\Python27-x64" 9 | PYTHON_VERSION: "2.7.x" 10 | PYTHON_ARCH: "64" 11 | TOXENV: "py27" 12 | 13 | - PYTHON: "C:\\Python33-x64" 14 | PYTHON_VERSION: "3.3.x" 15 | PYTHON_ARCH: "64" 16 | TOXENV: "py33" 17 | 18 | - PYTHON: "C:\\Python34-x64" 19 | PYTHON_VERSION: "3.4.x" 20 | PYTHON_ARCH: "64" 21 | TOXENV: "py34" 22 | 23 | - PYTHON: "C:\\Python35-x64" 24 | PYTHON_VERSION: "3.5.x" 25 | PYTHON_ARCH: "64" 26 | TOXENV: "py35" 27 | 28 | - PYTHON: "C:\\Python36-x64" 29 | PYTHON_VERSION: "3.6.x" 30 | PYTHON_ARCH: "64" 31 | TOXENV: "py36" 32 | 33 | install: 34 | # Install Python (from the official .msi of http://python.org) and pip when 35 | # not already installed. 36 | - ps: if (-not(Test-Path($env:PYTHON))) { & .appveyor\install.ps1 } 37 | 38 | # Prepend newly installed Python to the PATH of this build (this cannot be 39 | # done from inside the powershell script as it would require to restart 40 | # the parent CMD process). 41 | - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" 42 | 43 | # Check that we have the expected version and architecture for Python 44 | - "python --version" 45 | - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" 46 | 47 | # Upgrade to the latest version of pip to avoid it displaying warnings 48 | # about it being out of date. 49 | - "python -m pip install --disable-pip-version-check --user --upgrade pip" 50 | - "python -m pip install -U tox setuptools" 51 | 52 | test_script: 53 | - "tox" 54 | 55 | branches: 56 | only: 57 | - master 58 | - release 59 | 60 | -------------------------------------------------------------------------------- /tests/support.py: -------------------------------------------------------------------------------- 1 | import os 2 | import signal 3 | import socket 4 | import threading 5 | import time 6 | 7 | __all__ = [ 8 | "get_time", 9 | "resource", 10 | "socketpair", 11 | "AlarmMixin", 12 | "TimerMixin" 13 | ] 14 | 15 | # Tolerance values for timer/speed fluctuations. 16 | TOLERANCE = 0.5 17 | 18 | # Detect whether we're running on Travis or AppVeyor. This 19 | # is used to skip some verification points inside of tests to 20 | # not randomly fail our CI due to wild timer/speed differences. 21 | TRAVIS_CI = "TRAVIS" in os.environ 22 | APPVEYOR = "APPVEYOR" in os.environ 23 | 24 | try: # Python 2.x doesn't define time.perf_counter. 25 | from time import perf_counter as get_time 26 | except ImportError: 27 | from time import time as get_time 28 | 29 | try: # Python 2.6 doesn't have the resource module. 30 | import resource 31 | except ImportError: 32 | resource = None 33 | 34 | if hasattr(socket, 'socketpair'): 35 | # Since Python 3.5, socket.socketpair() is now also available on Windows 36 | socketpair = socket.socketpair 37 | else: 38 | # Replacement for socket.socketpair() 39 | def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0): 40 | """A socket pair usable as a self-pipe, for Windows. 41 | Origin: https://gist.github.com/4325783, by Geert Jansen. 42 | Public domain. 43 | """ 44 | if family == socket.AF_INET: 45 | host = '127.0.0.1' 46 | elif family == socket.AF_INET6: 47 | host = '::1' 48 | else: 49 | raise ValueError("Only AF_INET and AF_INET6 socket address " 50 | "families are supported") 51 | if type != socket.SOCK_STREAM: 52 | raise ValueError("Only SOCK_STREAM socket type is supported") 53 | if proto != 0: 54 | raise ValueError("Only protocol zero is supported") 55 | 56 | # We create a connected TCP socket. Note the trick with setblocking(0) 57 | # that prevents us from having to create a thread. 58 | lsock = socket.socket(family, type, proto) 59 | try: 60 | lsock.bind((host, 0)) 61 | lsock.listen(1) 62 | # On IPv6, ignore flow_info and scope_id 63 | addr, port = lsock.getsockname()[:2] 64 | csock = socket.socket(family, type, proto) 65 | try: 66 | csock.setblocking(False) 67 | try: 68 | csock.connect((addr, port)) 69 | except (OSError, socket.error): 70 | pass 71 | csock.setblocking(True) 72 | ssock, _ = lsock.accept() 73 | except: 74 | csock.close() 75 | raise 76 | finally: 77 | lsock.close() 78 | return ssock, csock 79 | 80 | 81 | class AlarmThread(threading.Thread): 82 | def __init__(self, timeout): 83 | super(AlarmThread, self).__init__(group=None) 84 | self.setDaemon(True) 85 | self.timeout = timeout 86 | self.canceled = False 87 | 88 | def cancel(self): 89 | self.canceled = True 90 | 91 | def run(self): 92 | time.sleep(self.timeout) 93 | if not self.canceled: 94 | os.kill(os.getpid(), signal.SIGALRM) 95 | 96 | 97 | class AlarmMixin(object): 98 | alarm_thread = None 99 | 100 | def _begin_alarm_thread(self, timeout): 101 | if not hasattr(signal, "SIGALRM"): 102 | self.skipTest("Platform doesn't have signal.SIGALRM") 103 | self.addCleanup(self._cancel_alarm_thread) 104 | self.alarm_thread = AlarmThread(timeout) 105 | self.alarm_thread.start() 106 | 107 | def _cancel_alarm_thread(self): 108 | if self.alarm_thread is not None: 109 | self.alarm_thread.cancel() 110 | self.alarm_thread.join(0.0) 111 | self.alarm_thread = None 112 | 113 | def set_alarm(self, duration, handler): 114 | sigalrm_handler = signal.signal(signal.SIGALRM, handler) 115 | self.addCleanup(signal.signal, signal.SIGALRM, sigalrm_handler) 116 | self._begin_alarm_thread(duration) 117 | 118 | 119 | class TimerContext(object): 120 | def __init__(self, testcase, lower=None, upper=None): 121 | self.testcase = testcase 122 | self.lower = lower 123 | self.upper = upper 124 | self.start_time = None 125 | self.end_time = None 126 | 127 | def __enter__(self): 128 | self.start_time = get_time() 129 | 130 | def __exit__(self, *args, **kwargs): 131 | self.end_time = get_time() 132 | total_time = self.end_time - self.start_time 133 | 134 | # Skip timing on CI due to flakiness. 135 | if TRAVIS_CI or APPVEYOR: 136 | return 137 | 138 | if self.lower is not None: 139 | self.testcase.assertGreaterEqual(total_time, self.lower * (1.0 - TOLERANCE)) 140 | if self.upper is not None: 141 | self.testcase.assertLessEqual(total_time, self.upper * (1.0 + TOLERANCE)) 142 | 143 | 144 | class TimerMixin(object): 145 | def assertTakesTime(self, lower=None, upper=None): 146 | return TimerContext(self, lower=lower, upper=upper) -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Selectors2 2 | ========== 3 | 4 | .. image:: https://img.shields.io/travis/SethMichaelLarson/selectors2/master.svg?style=flat-square 5 | :target: https://travis-ci.org/SethMichaelLarson/selectors2 6 | .. image:: https://img.shields.io/appveyor/ci/SethMichaelLarson/selectors2/master.svg?style=flat-square 7 | :target: https://ci.appveyor.com/project/SethMichaelLarson/selectors2 8 | .. image:: https://img.shields.io/pypi/v/selectors2.svg?style=flat-square 9 | :target: https://pypi.python.org/pypi/selectors2 10 | .. image:: https://img.shields.io/badge/say-thanks-ff69b4.svg?style=flat-square 11 | :target: https://saythanks.io/to/SethMichaelLarson 12 | 13 | Backported, durable, and portable selectors designed to replace 14 | the standard library selectors module. 15 | 16 | Features 17 | -------- 18 | 19 | * Support for all major platforms. (Linux, Mac OS, Windows) 20 | * Support for Python 2.6 or later and **Jython**. 21 | * Support many different selectors 22 | * ``select.kqueue`` (BSD, Mac OS) 23 | * ``select.devpoll`` (Solaris) 24 | * ``select.epoll`` (Linux 2.5.44+) 25 | * ``select.poll`` (Linux, Mac OS) 26 | * ``select.select`` - (Linux, Mac OS, Windows) 27 | * Support for `PEP 475 `_ (Retries system calls on interrupt) 28 | * Support for modules which monkey-patch the standard library after import (like greenlet, gevent) 29 | * Support for systems which define a selector being available but don't actually implement it. () 30 | 31 | About 32 | ----- 33 | 34 | This module was originally written by me for the `urllib3 `_ project 35 | (history in PR `#1001 `_) but it was decided that it would 36 | be beneficial for everyone to have access to this work. 37 | 38 | All the additional features that ``selectors2`` provides are real-world problems that have occurred 39 | and been reported during the lifetime of its maintenance and use within ``urllib3``. 40 | 41 | If this work is useful to you, `feel free to say thanks `_, 42 | takes only a little time and really brightens my day! :cake: 43 | 44 | Can this module be used in place of ``selectors``? 45 | -------------------------------------------------- 46 | 47 | Yes! This module is a 1-to-1 drop-in replacement for ``selectors`` and 48 | provides all selector types that would be available in ``selectors`` including 49 | ``DevpollSelector``, ``KqueueSelector``, ``EpollSelector``, ``PollSelector``, and ``SelectSelector``. 50 | 51 | What is different between `selectors2` and `selectors34`? 52 | --------------------------------------------------------- 53 | 54 | This module is similar to ``selectors34`` in that it supports Python 2.6 - 3.3 55 | but differs in that this module also implements PEP 475 for the backported selectors. 56 | This allows similar behaviour between Python 3.5+ selectors and selectors from before PEP 475. 57 | In ``selectors34``, an interrupted system call would result in an incorrect return of no events, which 58 | for some use cases is not an acceptable behavior. 59 | 60 | I will also add here that ``selectors2`` also makes large improvements on the test suite surrounding it 61 | providing 100% test coverage for each selector. The test suite is also more robust and tests durability 62 | of the selectors in many different situations that aren't tested in ``selectors34``. 63 | 64 | What types of objects are supported? 65 | ------------------------------------ 66 | 67 | At this current time ``selectors2`` only support the ``SelectSelector`` for Windows which cannot select on non-socket objects. 68 | On Linux and Mac OS, both sockets and pipes are supported (some other types may be supported as well, such as fifos or special file devices). 69 | 70 | What if I have to support a platform without ``select.select``? 71 | --------------------------------------------------------------- 72 | 73 | There are a few platforms that don't have a selector available, notably 74 | Google AppEngine. When running on those platforms any call to ``DefaultSelector()`` 75 | will raise a ``RuntimeError`` explaining that there are no selectors available. 76 | 77 | License 78 | ------- 79 | 80 | This module is dual-licensed under MIT and PSF License. 81 | 82 | Installation 83 | ------------ 84 | 85 | ``$ python -m pip install selectors2`` 86 | 87 | Usage 88 | ----- 89 | .. code-block:: python 90 | 91 | import sys 92 | import selectors2 as selectors 93 | 94 | # Use DefaultSelector, it picks the best 95 | # selector available for your platform! :) 96 | s = selectors.DefaultSelector() 97 | 98 | import socket 99 | 100 | # We're going to use Google as an example. 101 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 102 | sock.connect(("www.google.com", 80)) 103 | 104 | # Register the file to be watched for write availibility. 105 | s.register(sock, selectors.EVENT_WRITE) 106 | 107 | # Give a timeout in seconds or no 108 | # timeout to block until an event happens. 109 | events = s.select(timeout=1.0) 110 | 111 | # Loop over all events that happened. 112 | for key, event in events: 113 | if event & selectors.EVENT_WRITE: 114 | key.fileobj.send(b'HEAD / HTTP/1.1\r\n\r\n') 115 | 116 | # Change what event you're waiting for. 117 | s.modify(sock, selectors.EVENT_READ) 118 | 119 | # Timeout of None let's the selector wait as long as it needs to. 120 | events = s.select(timeout=None) 121 | for key, event in events: 122 | if event & selectors.EVENT_READ: 123 | data = key.fileobj.recv(4096) 124 | print(data) 125 | 126 | # Stop watching the socket. 127 | s.unregister(sock) 128 | sock.close() 129 | -------------------------------------------------------------------------------- /.appveyor/install.ps1: -------------------------------------------------------------------------------- 1 | # Sample script to install Python and pip under Windows 2 | # Authors: Olivier Grisel, Jonathan Helmus, Kyle Kastner, and Alex Willmer 3 | # License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 4 | 5 | $MINICONDA_URL = "http://repo.continuum.io/miniconda/" 6 | $BASE_URL = "https://www.python.org/ftp/python/" 7 | $GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" 8 | $GET_PIP_PATH = "C:\get-pip.py" 9 | 10 | $PYTHON_PRERELEASE_REGEX = @" 11 | (?x) 12 | (?\d+) 13 | \. 14 | (?\d+) 15 | \. 16 | (?\d+) 17 | (?[a-z]{1,2}\d+) 18 | "@ 19 | 20 | 21 | function Download ($filename, $url) { 22 | $webclient = New-Object System.Net.WebClient 23 | 24 | $basedir = $pwd.Path + "\" 25 | $filepath = $basedir + $filename 26 | if (Test-Path $filename) { 27 | Write-Host "Reusing" $filepath 28 | return $filepath 29 | } 30 | 31 | # Download and retry up to 3 times in case of network transient errors. 32 | Write-Host "Downloading" $filename "from" $url 33 | $retry_attempts = 2 34 | for ($i = 0; $i -lt $retry_attempts; $i++) { 35 | try { 36 | $webclient.DownloadFile($url, $filepath) 37 | break 38 | } 39 | Catch [Exception]{ 40 | Start-Sleep 1 41 | } 42 | } 43 | if (Test-Path $filepath) { 44 | Write-Host "File saved at" $filepath 45 | } else { 46 | # Retry once to get the error message if any at the last try 47 | $webclient.DownloadFile($url, $filepath) 48 | } 49 | return $filepath 50 | } 51 | 52 | 53 | function ParsePythonVersion ($python_version) { 54 | if ($python_version -match $PYTHON_PRERELEASE_REGEX) { 55 | return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro, 56 | $matches.prerelease) 57 | } 58 | $version_obj = [version]$python_version 59 | return ($version_obj.major, $version_obj.minor, $version_obj.build, "") 60 | } 61 | 62 | 63 | function DownloadPython ($python_version, $platform_suffix) { 64 | $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version 65 | 66 | if (($major -le 2 -and $micro -eq 0) ` 67 | -or ($major -eq 3 -and $minor -le 2 -and $micro -eq 0) ` 68 | ) { 69 | $dir = "$major.$minor" 70 | $python_version = "$major.$minor$prerelease" 71 | } else { 72 | $dir = "$major.$minor.$micro" 73 | } 74 | 75 | if ($prerelease) { 76 | if (($major -le 2) ` 77 | -or ($major -eq 3 -and $minor -eq 1) ` 78 | -or ($major -eq 3 -and $minor -eq 2) ` 79 | -or ($major -eq 3 -and $minor -eq 3) ` 80 | ) { 81 | $dir = "$dir/prev" 82 | } 83 | } 84 | 85 | if (($major -le 2) -or ($major -le 3 -and $minor -le 4)) { 86 | $ext = "msi" 87 | if ($platform_suffix) { 88 | $platform_suffix = ".$platform_suffix" 89 | } 90 | } else { 91 | $ext = "exe" 92 | if ($platform_suffix) { 93 | $platform_suffix = "-$platform_suffix" 94 | } 95 | } 96 | 97 | $filename = "python-$python_version$platform_suffix.$ext" 98 | $url = "$BASE_URL$dir/$filename" 99 | $filepath = Download $filename $url 100 | return $filepath 101 | } 102 | 103 | 104 | function InstallPython ($python_version, $architecture, $python_home) { 105 | Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home 106 | if (Test-Path $python_home) { 107 | Write-Host $python_home "already exists, skipping." 108 | return $false 109 | } 110 | if ($architecture -eq "32") { 111 | $platform_suffix = "" 112 | } else { 113 | $platform_suffix = "amd64" 114 | } 115 | $installer_path = DownloadPython $python_version $platform_suffix 116 | $installer_ext = [System.IO.Path]::GetExtension($installer_path) 117 | Write-Host "Installing $installer_path to $python_home" 118 | $install_log = $python_home + ".log" 119 | if ($installer_ext -eq '.msi') { 120 | InstallPythonMSI $installer_path $python_home $install_log 121 | } else { 122 | InstallPythonEXE $installer_path $python_home $install_log 123 | } 124 | if (Test-Path $python_home) { 125 | Write-Host "Python $python_version ($architecture) installation complete" 126 | } else { 127 | Write-Host "Failed to install Python in $python_home" 128 | Get-Content -Path $install_log 129 | Exit 1 130 | } 131 | } 132 | 133 | 134 | function InstallPythonEXE ($exepath, $python_home, $install_log) { 135 | $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home" 136 | RunCommand $exepath $install_args 137 | } 138 | 139 | 140 | function InstallPythonMSI ($msipath, $python_home, $install_log) { 141 | $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home" 142 | $uninstall_args = "/qn /x $msipath" 143 | RunCommand "msiexec.exe" $install_args 144 | if (-not(Test-Path $python_home)) { 145 | Write-Host "Python seems to be installed else-where, reinstalling." 146 | RunCommand "msiexec.exe" $uninstall_args 147 | RunCommand "msiexec.exe" $install_args 148 | } 149 | } 150 | 151 | function RunCommand ($command, $command_args) { 152 | Write-Host $command $command_args 153 | Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru 154 | } 155 | 156 | 157 | function InstallPip ($python_home) { 158 | $pip_path = $python_home + "\Scripts\pip.exe" 159 | $python_path = $python_home + "\python.exe" 160 | if (-not(Test-Path $pip_path)) { 161 | Write-Host "Installing pip..." 162 | $webclient = New-Object System.Net.WebClient 163 | $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) 164 | Write-Host "Executing:" $python_path $GET_PIP_PATH 165 | & $python_path $GET_PIP_PATH 166 | } else { 167 | Write-Host "pip already installed." 168 | } 169 | } 170 | 171 | 172 | function DownloadMiniconda ($python_version, $platform_suffix) { 173 | if ($python_version -eq "3.4") { 174 | $filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe" 175 | } else { 176 | $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe" 177 | } 178 | $url = $MINICONDA_URL + $filename 179 | $filepath = Download $filename $url 180 | return $filepath 181 | } 182 | 183 | 184 | function InstallMiniconda ($python_version, $architecture, $python_home) { 185 | Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home 186 | if (Test-Path $python_home) { 187 | Write-Host $python_home "already exists, skipping." 188 | return $false 189 | } 190 | if ($architecture -eq "32") { 191 | $platform_suffix = "x86" 192 | } else { 193 | $platform_suffix = "x86_64" 194 | } 195 | $filepath = DownloadMiniconda $python_version $platform_suffix 196 | Write-Host "Installing" $filepath "to" $python_home 197 | $install_log = $python_home + ".log" 198 | $args = "/S /D=$python_home" 199 | Write-Host $filepath $args 200 | Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru 201 | if (Test-Path $python_home) { 202 | Write-Host "Python $python_version ($architecture) installation complete" 203 | } else { 204 | Write-Host "Failed to install Python in $python_home" 205 | Get-Content -Path $install_log 206 | Exit 1 207 | } 208 | } 209 | 210 | 211 | function InstallMinicondaPip ($python_home) { 212 | $pip_path = $python_home + "\Scripts\pip.exe" 213 | $conda_path = $python_home + "\Scripts\conda.exe" 214 | if (-not(Test-Path $pip_path)) { 215 | Write-Host "Installing pip..." 216 | $args = "install --yes pip" 217 | Write-Host $conda_path $args 218 | Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru 219 | } else { 220 | Write-Host "pip already installed." 221 | } 222 | } 223 | 224 | function main () { 225 | InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON 226 | InstallPip $env:PYTHON 227 | } 228 | 229 | main -------------------------------------------------------------------------------- /tests/test_selectors2.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import errno 3 | import os 4 | import psutil 5 | import platform 6 | import mock 7 | import select 8 | import signal 9 | import sys 10 | import time 11 | from .support import socketpair, AlarmMixin, TimerMixin 12 | 13 | import selectors2 14 | 15 | try: # Python 2.6 unittest module doesn't have skip decorators. 16 | from unittest import skipIf, skipUnless 17 | import unittest 18 | except ImportError: 19 | from unittest2 import skipIf, skipUnless 20 | import unittest2 as unittest 21 | 22 | try: # Python 2.x doesn't define time.perf_counter. 23 | from time import perf_counter as get_time 24 | except ImportError: 25 | from time import time as get_time 26 | 27 | try: # Python 2.6 doesn't have the resource module. 28 | import resource 29 | except ImportError: 30 | resource = None 31 | 32 | HAS_ALARM = hasattr(signal, "alarm") 33 | 34 | LONG_SELECT = 1.0 35 | SHORT_SELECT = 0.01 36 | 37 | 38 | skipUnlessHasSelector = skipUnless(hasattr(selectors2, 'SelectSelector'), "Platform doesn't have a selector") 39 | skipUnlessHasENOSYS = skipUnless(hasattr(errno, 'ENOSYS'), "Platform doesn't have errno.ENOSYS") 40 | skipUnlessHasAlarm = skipUnless(hasattr(signal, 'alarm'), "Platform doesn't have signal.alarm()") 41 | skipUnlessJython = skipUnless(platform.system() == 'Java', "Platform is not Jython") 42 | skipIfRetriesInterrupts = skipIf(sys.version_info >= (3, 5), "Platform retries interrupts") 43 | 44 | 45 | def patch_select_module(testcase, *keep, **replace): 46 | """ Helper function that removes all selectors from the select module 47 | except those listed in *keep and **replace. Those in keep will be kept 48 | if they exist in the select module and those in replace will be patched 49 | with the value that is given regardless if they exist or not. Cleanup 50 | will restore previous state. This helper also resets the selectors module 51 | so that a call to DefaultSelector() will do feature detection again. """ 52 | 53 | selectors2._DEFAULT_SELECTOR = None 54 | for s in ['select', 'poll', 'epoll', 'kqueue']: 55 | if s in replace: 56 | if hasattr(select, s): 57 | old_selector = getattr(select, s) 58 | testcase.addCleanup(setattr, select, s, old_selector) 59 | else: 60 | testcase.addCleanup(delattr, select, s) 61 | setattr(select, s, replace[s]) 62 | elif s not in keep and hasattr(select, s): 63 | old_selector = getattr(select, s) 64 | testcase.addCleanup(setattr, select, s, old_selector) 65 | delattr(select, s) 66 | 67 | 68 | @skipUnlessHasSelector 69 | class _BaseSelectorTestCase(unittest.TestCase, AlarmMixin, TimerMixin): 70 | """ Implements the tests that each type of selector must pass. """ 71 | 72 | def make_socketpair(self): 73 | rd, wr = socketpair() 74 | 75 | # Make non-blocking so we get errors if the 76 | # sockets are interacted with but not ready. 77 | rd.settimeout(0.0) 78 | wr.settimeout(0.0) 79 | 80 | self.addCleanup(rd.close) 81 | self.addCleanup(wr.close) 82 | return rd, wr 83 | 84 | def make_selector(self): 85 | s = selectors2.DefaultSelector() 86 | self.addCleanup(s.close) 87 | return s 88 | 89 | def standard_setup(self): 90 | s = self.make_selector() 91 | rd, wr = self.make_socketpair() 92 | s.register(rd, selectors2.EVENT_READ) 93 | s.register(wr, selectors2.EVENT_WRITE) 94 | return s, rd, wr 95 | 96 | 97 | class _AllSelectorsTestCase(_BaseSelectorTestCase): 98 | def test_get_key(self): 99 | s = self.make_selector() 100 | rd, wr = self.make_socketpair() 101 | 102 | key = s.register(rd, selectors2.EVENT_READ, "data") 103 | self.assertEqual(key, s.get_key(rd)) 104 | 105 | # Unknown fileobj 106 | self.assertRaises(KeyError, s.get_key, 999999) 107 | 108 | def test_get_map(self): 109 | s = self.make_selector() 110 | rd, wr = self.make_socketpair() 111 | 112 | keys = s.get_map() 113 | self.assertFalse(keys) 114 | self.assertEqual(len(keys), 0) 115 | self.assertEqual(list(keys), []) 116 | key = s.register(rd, selectors2.EVENT_READ, "data") 117 | self.assertIn(rd, keys) 118 | self.assertEqual(key, keys[rd]) 119 | self.assertEqual(len(keys), 1) 120 | self.assertEqual(list(keys), [rd.fileno()]) 121 | self.assertEqual(list(keys.values()), [key]) 122 | 123 | # Unknown fileobj 124 | self.assertRaises(KeyError, keys.__getitem__, 999999) 125 | 126 | # Read-only mapping 127 | with self.assertRaises(TypeError): 128 | del keys[rd] 129 | 130 | # Doesn't define __setitem__ 131 | with self.assertRaises(TypeError): 132 | keys[rd] = key 133 | 134 | def test_register(self): 135 | s = self.make_selector() 136 | rd, wr = self.make_socketpair() 137 | 138 | # Ensure that the file is not yet added. 139 | self.assertEqual(0, len(s.get_map())) 140 | self.assertRaises(KeyError, lambda: s.get_map()[rd.fileno()]) 141 | self.assertRaises(KeyError, s.get_key, rd) 142 | self.assertEqual(None, s._key_from_fd(rd.fileno())) 143 | 144 | data = object() 145 | key = s.register(rd, selectors2.EVENT_READ, data) 146 | self.assertIsInstance(key, selectors2.SelectorKey) 147 | self.assertEqual(key.fileobj, rd) 148 | self.assertEqual(key.fd, rd.fileno()) 149 | self.assertEqual(key.events, selectors2.EVENT_READ) 150 | self.assertIs(key.data, data) 151 | self.assertEqual(1, len(s.get_map())) 152 | for fd in s.get_map(): 153 | self.assertEqual(fd, rd.fileno()) 154 | 155 | def test_register_bad_event(self): 156 | s = self.make_selector() 157 | rd, wr = self.make_socketpair() 158 | 159 | self.assertRaises(ValueError, s.register, rd, 99999) 160 | 161 | def test_register_negative_fd(self): 162 | s = self.make_selector() 163 | self.assertRaises(ValueError, s.register, -1, selectors2.EVENT_READ) 164 | 165 | def test_register_invalid_fileobj(self): 166 | s = self.make_selector() 167 | self.assertRaises(ValueError, s.register, "string", selectors2.EVENT_READ) 168 | 169 | def test_reregister_fd_same_fileobj(self): 170 | s, rd, wr = self.standard_setup() 171 | self.assertRaises(KeyError, s.register, rd, selectors2.EVENT_READ) 172 | 173 | def test_reregister_fd_different_fileobj(self): 174 | s, rd, wr = self.standard_setup() 175 | self.assertRaises(KeyError, s.register, rd.fileno(), selectors2.EVENT_READ) 176 | 177 | def test_context_manager(self): 178 | s = self.make_selector() 179 | rd, wr = self.make_socketpair() 180 | 181 | with s as sel: 182 | rd_key = sel.register(rd, selectors2.EVENT_READ) 183 | wr_key = sel.register(wr, selectors2.EVENT_WRITE) 184 | self.assertEqual(rd_key, sel.get_key(rd)) 185 | self.assertEqual(wr_key, sel.get_key(wr)) 186 | 187 | self.assertRaises(RuntimeError, s.get_key, rd) 188 | self.assertRaises(RuntimeError, s.get_key, wr) 189 | 190 | def test_unregister(self): 191 | s, rd, wr = self.standard_setup() 192 | s.unregister(rd) 193 | 194 | self.assertRaises(KeyError, s.unregister, 99999) 195 | 196 | def test_reunregister(self): 197 | s, rd, wr = self.standard_setup() 198 | s.unregister(rd) 199 | 200 | self.assertRaises(KeyError, s.unregister, rd) 201 | 202 | def test_unregister_after_fd_close(self): 203 | s = self.make_selector() 204 | rd, wr = self.make_socketpair() 205 | rdfd = rd.fileno() 206 | wrfd = wr.fileno() 207 | s.register(rdfd, selectors2.EVENT_READ) 208 | s.register(wrfd, selectors2.EVENT_WRITE) 209 | 210 | rd.close() 211 | wr.close() 212 | 213 | s.unregister(rdfd) 214 | s.unregister(wrfd) 215 | 216 | self.assertEqual(0, len(s.get_map())) 217 | 218 | def test_unregister_after_fileobj_close(self): 219 | s = self.make_selector() 220 | rd, wr = self.make_socketpair() 221 | s.register(rd, selectors2.EVENT_READ) 222 | s.register(wr, selectors2.EVENT_WRITE) 223 | 224 | rd.close() 225 | wr.close() 226 | 227 | s.unregister(rd) 228 | s.unregister(wr) 229 | 230 | self.assertEqual(0, len(s.get_map())) 231 | 232 | @skipUnless(os.name == "posix", "Platform doesn't support os.dup2") 233 | def test_unregister_after_reuse_fd(self): 234 | s, rd, wr = self.standard_setup() 235 | rdfd = rd.fileno() 236 | wrfd = wr.fileno() 237 | 238 | rd2, wr2 = self.make_socketpair() 239 | rd.close() 240 | wr.close() 241 | os.dup2(rd2.fileno(), rdfd) 242 | os.dup2(wr2.fileno(), wrfd) 243 | 244 | s.unregister(rdfd) 245 | s.unregister(wrfd) 246 | 247 | self.assertEqual(0, len(s.get_map())) 248 | 249 | def test_modify(self): 250 | s = self.make_selector() 251 | rd, wr = self.make_socketpair() 252 | 253 | key = s.register(rd, selectors2.EVENT_READ) 254 | 255 | # Modify events 256 | key2 = s.modify(rd, selectors2.EVENT_WRITE) 257 | self.assertNotEqual(key.events, key2.events) 258 | self.assertEqual(key2, s.get_key(rd)) 259 | 260 | s.unregister(rd) 261 | 262 | # Modify data 263 | d1 = object() 264 | d2 = object() 265 | 266 | key = s.register(rd, selectors2.EVENT_READ, d1) 267 | key2 = s.modify(rd, selectors2.EVENT_READ, d2) 268 | self.assertEqual(key.events, key2.events) 269 | self.assertIsNot(key.data, key2.data) 270 | self.assertEqual(key2, s.get_key(rd)) 271 | self.assertIs(key2.data, d2) 272 | 273 | # Modify invalid fileobj 274 | self.assertRaises(KeyError, s.modify, 999999, selectors2.EVENT_READ) 275 | 276 | def test_empty_select(self): 277 | s = self.make_selector() 278 | self.assertEqual([], s.select(timeout=SHORT_SELECT)) 279 | 280 | def test_select_multiple_event_types(self): 281 | s = self.make_selector() 282 | 283 | rd, wr = self.make_socketpair() 284 | key = s.register(rd, selectors2.EVENT_READ | selectors2.EVENT_WRITE) 285 | 286 | self.assertEqual([(key, selectors2.EVENT_WRITE)], s.select(0.001)) 287 | 288 | wr.send(b'x') 289 | time.sleep(0.01) # Wait for the write to flush. 290 | 291 | self.assertEqual([(key, selectors2.EVENT_READ | selectors2.EVENT_WRITE)], s.select(0.001)) 292 | 293 | def test_select_multiple_selectors(self): 294 | s1 = self.make_selector() 295 | s2 = self.make_selector() 296 | rd, wr = self.make_socketpair() 297 | key1 = s1.register(rd, selectors2.EVENT_READ) 298 | key2 = s2.register(rd, selectors2.EVENT_READ) 299 | 300 | wr.send(b'x') 301 | time.sleep(0.01) # Wait for the write to flush. 302 | 303 | self.assertEqual([(key1, selectors2.EVENT_READ)], s1.select(timeout=0.001)) 304 | self.assertEqual([(key2, selectors2.EVENT_READ)], s2.select(timeout=0.001)) 305 | 306 | def test_select_no_event_types(self): 307 | s = self.make_selector() 308 | rd, wr = self.make_socketpair() 309 | self.assertRaises(ValueError, s.register, rd, 0) 310 | 311 | def test_select_many_events(self): 312 | s = self.make_selector() 313 | readers = [] 314 | writers = [] 315 | for _ in range(32): 316 | rd, wr = self.make_socketpair() 317 | readers.append(rd) 318 | writers.append(wr) 319 | s.register(rd, selectors2.EVENT_READ) 320 | 321 | self.assertEqual(0, len(s.select(0.001))) 322 | 323 | # Write a byte to each end. 324 | for wr in writers: 325 | wr.send(b'x') 326 | 327 | # Give time to flush the writes. 328 | time.sleep(0.01) 329 | 330 | ready = s.select(0.001) 331 | self.assertEqual(32, len(ready)) 332 | for key, events in ready: 333 | self.assertEqual(selectors2.EVENT_READ, events) 334 | self.assertIn(key.fileobj, readers) 335 | 336 | # Now read the byte from each endpoint. 337 | for rd in readers: 338 | data = rd.recv(1) 339 | self.assertEqual(b'x', data) 340 | 341 | self.assertEqual(0, len(s.select(0.001))) 342 | 343 | def test_select_timeout_none(self): 344 | s = self.make_selector() 345 | rd, wr = self.make_socketpair() 346 | s.register(wr, selectors2.EVENT_WRITE) 347 | 348 | with self.assertTakesTime(upper=SHORT_SELECT): 349 | self.assertEqual(1, len(s.select(timeout=None))) 350 | 351 | def test_select_timeout_ready(self): 352 | s, rd, wr = self.standard_setup() 353 | 354 | with self.assertTakesTime(upper=SHORT_SELECT): 355 | self.assertEqual(1, len(s.select(timeout=0))) 356 | self.assertEqual(1, len(s.select(timeout=-1))) 357 | self.assertEqual(1, len(s.select(timeout=0.001))) 358 | 359 | def test_select_timeout_not_ready(self): 360 | s = self.make_selector() 361 | rd, wr = self.make_socketpair() 362 | s.register(rd, selectors2.EVENT_READ) 363 | 364 | with self.assertTakesTime(upper=SHORT_SELECT): 365 | self.assertEqual(0, len(s.select(timeout=0))) 366 | 367 | with self.assertTakesTime(lower=SHORT_SELECT, upper=SHORT_SELECT): 368 | self.assertEqual(0, len(s.select(timeout=SHORT_SELECT))) 369 | 370 | @skipUnlessHasAlarm 371 | def test_select_timing(self): 372 | s = self.make_selector() 373 | rd, wr = self.make_socketpair() 374 | key = s.register(rd, selectors2.EVENT_READ) 375 | 376 | self.set_alarm(SHORT_SELECT, lambda *args: wr.send(b'x')) 377 | 378 | with self.assertTakesTime(upper=SHORT_SELECT): 379 | ready = s.select(LONG_SELECT) 380 | self.assertEqual([(key, selectors2.EVENT_READ)], ready) 381 | 382 | @skipUnlessHasAlarm 383 | def test_select_interrupt_no_event(self): 384 | s = self.make_selector() 385 | rd, wr = self.make_socketpair() 386 | s.register(rd, selectors2.EVENT_READ) 387 | 388 | self.set_alarm(SHORT_SELECT, lambda *args: None) 389 | 390 | with self.assertTakesTime(lower=LONG_SELECT, upper=LONG_SELECT): 391 | self.assertEqual([], s.select(LONG_SELECT)) 392 | 393 | @skipUnlessHasAlarm 394 | def test_select_interrupt_with_event(self): 395 | s = self.make_selector() 396 | rd, wr = self.make_socketpair() 397 | s.register(rd, selectors2.EVENT_READ) 398 | key = s.get_key(rd) 399 | 400 | self.set_alarm(SHORT_SELECT, lambda *args: wr.send(b'x')) 401 | 402 | with self.assertTakesTime(lower=SHORT_SELECT, upper=SHORT_SELECT): 403 | self.assertEqual([(key, selectors2.EVENT_READ)], s.select(LONG_SELECT)) 404 | self.assertEqual(rd.recv(1), b'x') 405 | 406 | @skipUnlessHasAlarm 407 | def test_select_multiple_interrupts_with_event(self): 408 | s = self.make_selector() 409 | rd, wr = self.make_socketpair() 410 | s.register(rd, selectors2.EVENT_READ) 411 | key = s.get_key(rd) 412 | 413 | def second_alarm(*args): 414 | wr.send(b'x') 415 | 416 | def first_alarm(*args): 417 | self._begin_alarm_thread(SHORT_SELECT) 418 | signal.signal(signal.SIGALRM, second_alarm) 419 | 420 | self.set_alarm(SHORT_SELECT, first_alarm) 421 | 422 | with self.assertTakesTime(lower=SHORT_SELECT * 2, upper=SHORT_SELECT * 2): 423 | self.assertEqual([(key, selectors2.EVENT_READ)], s.select(LONG_SELECT)) 424 | self.assertEqual(rd.recv(1), b'x') 425 | 426 | @skipUnlessHasAlarm 427 | def test_selector_error(self): 428 | s = self.make_selector() 429 | rd, wr = self.make_socketpair() 430 | s.register(rd, selectors2.EVENT_READ) 431 | 432 | def alarm_exception(*args): 433 | err = OSError() 434 | err.errno = errno.EACCES 435 | raise err 436 | 437 | self.set_alarm(SHORT_SELECT, alarm_exception) 438 | 439 | try: 440 | s.select(LONG_SELECT) 441 | except OSError as e: 442 | self.assertEqual(e.errno, errno.EACCES) 443 | except Exception as e: 444 | self.fail("Raised incorrect exception: " + str(e)) 445 | else: 446 | self.fail("select() didn't raise OSError") 447 | 448 | # Test ensures that _syscall_wrapper properly raises the 449 | # exception that is raised from an interrupt handler. 450 | @skipUnlessHasAlarm 451 | def test_select_interrupt_exception(self): 452 | s = self.make_selector() 453 | rd, wr = self.make_socketpair() 454 | s.register(rd, selectors2.EVENT_READ) 455 | 456 | class AlarmInterrupt(Exception): 457 | pass 458 | 459 | def alarm_exception(*args): 460 | raise AlarmInterrupt() 461 | 462 | self.set_alarm(SHORT_SELECT, alarm_exception) 463 | 464 | with self.assertTakesTime(lower=SHORT_SELECT, upper=SHORT_SELECT): 465 | self.assertRaises(AlarmInterrupt, s.select, LONG_SELECT) 466 | 467 | def test_fileno(self): 468 | s = self.make_selector() 469 | if hasattr(s, "fileno"): 470 | fd = s.fileno() 471 | self.assertTrue(isinstance(fd, int)) 472 | self.assertGreaterEqual(fd, 0) 473 | else: 474 | self.skipTest("Selector doesn't implement fileno()") 475 | 476 | # According to the psutil docs, open_files() has strange behavior 477 | # on Windows including giving back incorrect results so to 478 | # stop random failures from occurring we're skipping on Windows. 479 | @skipIf(sys.platform == "win32", "psutil.Process.open_files() is unstable on Windows.") 480 | def test_leaking_fds(self): 481 | proc = psutil.Process() 482 | before_fds = len(proc.open_files()) 483 | s = self.make_selector() 484 | s.close() 485 | after_fds = len(proc.open_files()) 486 | self.assertEqual(before_fds, after_fds) 487 | 488 | 489 | class ScalableSelectorMixin(object): 490 | """ Mixin to test selectors that allow more fds than FD_SETSIZE """ 491 | @skipUnless(resource, "Could not import the resource module") 492 | def test_above_fd_setsize(self): 493 | # A scalable implementation should have no problem with more than 494 | # FD_SETSIZE file descriptors. Since we don't know the value, we just 495 | # try to set the soft RLIMIT_NOFILE to the hard RLIMIT_NOFILE ceiling. 496 | soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) 497 | if hard == resource.RLIM_INFINITY: 498 | self.skipTest("RLIMIT_NOFILE is infinite") 499 | 500 | try: # If we're on a *BSD system, the limit tag is different. 501 | _, bsd_hard = resource.getrlimit(resource.RLIMIT_OFILE) 502 | if bsd_hard == resource.RLIM_INFINITY: 503 | self.skipTest("RLIMIT_OFILE is infinite") 504 | if bsd_hard < hard: 505 | hard = bsd_hard 506 | 507 | # NOTE: AttributeError resource.RLIMIT_OFILE is not defined on Mac OS. 508 | except (OSError, resource.error, AttributeError): 509 | pass 510 | 511 | try: 512 | resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard)) 513 | self.addCleanup(resource.setrlimit, resource.RLIMIT_NOFILE, 514 | (soft, hard)) 515 | limit_nofile = min(hard, 2 ** 16) 516 | except (OSError, ValueError): 517 | limit_nofile = soft 518 | 519 | # Guard against already allocated FDs 520 | limit_nofile -= 256 521 | limit_nofile = max(0, limit_nofile) 522 | 523 | s = self.make_selector() 524 | 525 | for i in range(limit_nofile // 2): 526 | rd, wr = self.make_socketpair() 527 | s.register(rd, selectors2.EVENT_READ) 528 | s.register(wr, selectors2.EVENT_WRITE) 529 | 530 | self.assertEqual(limit_nofile // 2, len(s.select())) 531 | 532 | 533 | @skipUnlessHasSelector 534 | class TestUniqueSelectScenarios(_BaseSelectorTestCase): 535 | def test_long_filenos_instead_of_int(self): 536 | # This test tries the module on objects that have 64bit filenos. 537 | selector = self.make_selector() 538 | big_fileno = 2 ** 64 539 | 540 | mock_socket = mock.Mock() 541 | mock_socket.fileno.return_value = big_fileno 542 | 543 | selector.register(big_fileno, selectors2.EVENT_READ) 544 | selector.unregister(big_fileno) 545 | 546 | selector.register(mock_socket, selectors2.EVENT_READ) 547 | selector.unregister(mock_socket) 548 | 549 | def test_select_module_patched_after_import(self): 550 | # This test is to make sure that after import time 551 | # calling DefaultSelector() will still give a good 552 | # return value. This issue is caused by gevent, eventlet. 553 | 554 | # Now remove all selectors except `select.select`. 555 | patch_select_module(self, 'select') 556 | 557 | # Make sure that the selector returned only uses the selector available. 558 | selector = self.make_selector() 559 | self.assertIsInstance(selector, selectors2.SelectSelector) 560 | 561 | @skipUnlessHasENOSYS 562 | def test_select_module_defines_does_not_implement_poll(self): 563 | # This test is to make sure that if a platform defines 564 | # a selector as being available but does not actually 565 | # implement it. 566 | 567 | # Reset the _DEFAULT_SELECTOR value as if using for the first time. 568 | selectors2._DEFAULT_SELECTOR = None 569 | 570 | # Now we're going to patch in a bad `poll`. 571 | class BadPoll(object): 572 | def poll(self, timeout): 573 | raise OSError(errno.ENOSYS) 574 | 575 | # Remove all selectors except `select.select` and replace `select.poll`. 576 | patch_select_module(self, 'select', poll=BadPoll) 577 | 578 | selector = self.make_selector() 579 | self.assertIsInstance(selector, selectors2.SelectSelector) 580 | 581 | @skipUnlessHasENOSYS 582 | def test_select_module_defines_does_not_implement_epoll(self): 583 | # Same as above test except with `select.epoll`. 584 | 585 | # Reset the _DEFAULT_SELECTOR value as if using for the first time. 586 | selectors2._DEFAULT_SELECTOR = None 587 | 588 | # Now we're going to patch in a bad `epoll`. 589 | def bad_epoll(*args, **kwargs): 590 | raise OSError(errno.ENOSYS) 591 | 592 | # Remove all selectors except `select.select` and replace `select.epoll`. 593 | patch_select_module(self, 'select', epoll=bad_epoll) 594 | 595 | selector = self.make_selector() 596 | self.assertIsInstance(selector, selectors2.SelectSelector) 597 | 598 | @skipIfRetriesInterrupts 599 | def test_selector_raises_timeout_error_on_interrupt_over_time(self): 600 | selectors2._DEFAULT_SELECTOR = None 601 | 602 | mock_socket = mock.Mock() 603 | mock_socket.fileno.return_value = 1 604 | 605 | def slow_interrupting_select(*args, **kwargs): 606 | time.sleep(0.2) 607 | error = OSError() 608 | error.errno = errno.EINTR 609 | raise error 610 | 611 | patch_select_module(self, select=slow_interrupting_select) 612 | 613 | selector = self.make_selector() 614 | selector.register(mock_socket, selectors2.EVENT_READ) 615 | 616 | try: 617 | selector.select(timeout=0.1) 618 | except OSError as e: 619 | self.assertEqual(e.errno, errno.ETIMEDOUT) 620 | else: 621 | self.fail('Didn\'t raise an OSError') 622 | 623 | @skipIfRetriesInterrupts 624 | def test_timeout_is_recalculated_after_interrupt(self): 625 | selectors2._DEFAULT_SELECTOR = None 626 | 627 | mock_socket = mock.Mock() 628 | mock_socket.fileno.return_value = 1 629 | 630 | class InterruptingSelect(object): 631 | """ Helper object that imitates a select that interrupts 632 | after sleeping some time then returns a result. """ 633 | def __init__(self): 634 | self.call_count = 0 635 | self.calls = [] 636 | 637 | def select(self, *args, **kwargs): 638 | self.calls.append((args, kwargs)) 639 | self.call_count += 1 640 | if self.call_count == 1: 641 | time.sleep(0.1) 642 | error = OSError() 643 | error.errno = errno.EINTR 644 | raise error 645 | else: 646 | return [1], [], [] 647 | 648 | mock_select = InterruptingSelect() 649 | 650 | patch_select_module(self, select=mock_select.select) 651 | 652 | selector = self.make_selector() 653 | selector.register(mock_socket, selectors2.EVENT_READ) 654 | 655 | result = selector.select(timeout=1.0) 656 | 657 | # Make sure the mocked call actually completed correctly. 658 | self.assertEqual(len(result), 1) 659 | self.assertEqual(result[0][0].fileobj, mock_socket) 660 | self.assertEqual(result[0][1], selectors2.EVENT_READ) 661 | 662 | # There should be two calls to the mock_select.select() function 663 | self.assertEqual(mock_select.call_count, 2) 664 | 665 | # Timeout should be less in the second call. 666 | # The structure of mock_select.calls is [(args, kwargs), (args, kwargs)] where 667 | # args is ([r], [w], [x], timeout). 668 | self.assertLess(mock_select.calls[1][0][3], mock_select.calls[0][0][3]) 669 | 670 | 671 | class TestSelectors2Module(unittest.TestCase): 672 | def test__all__has_correct_contents(self): 673 | for entry in dir(selectors2): 674 | if entry.endswith('Selector'): 675 | self.assertIn(entry, selectors2.__all__) 676 | 677 | for entry in selectors2.__all__: 678 | self.assertIn(entry, dir(selectors2)) 679 | 680 | 681 | @skipUnless(hasattr(selectors2, "SelectSelector"), "Platform doesn't have a SelectSelector") 682 | class SelectSelectorTestCase(_AllSelectorsTestCase): 683 | def setUp(self): 684 | patch_select_module(self, 'select') 685 | 686 | 687 | @skipUnless(hasattr(selectors2, "PollSelector"), "Platform doesn't have a PollSelector") 688 | class PollSelectorTestCase(_AllSelectorsTestCase, ScalableSelectorMixin): 689 | def setUp(self): 690 | patch_select_module(self, 'poll') 691 | 692 | 693 | @skipUnless(hasattr(selectors2, "EpollSelector"), "Platform doesn't have an EpollSelector") 694 | class EpollSelectorTestCase(_AllSelectorsTestCase, ScalableSelectorMixin): 695 | def setUp(self): 696 | patch_select_module(self, 'epoll') 697 | 698 | 699 | @skipUnless(hasattr(selectors2, "DevpollSelector"), "Platform doesn't have an DevpollSelector") 700 | class DevpollSelectorTestCase(_AllSelectorsTestCase, ScalableSelectorMixin): 701 | def setUp(self): 702 | patch_select_module(self, 'devpoll') 703 | 704 | 705 | @skipUnless(hasattr(selectors2, "KqueueSelector"), "Platform doesn't have a KqueueSelector") 706 | class KqueueSelectorTestCase(_AllSelectorsTestCase, ScalableSelectorMixin): 707 | def setUp(self): 708 | patch_select_module(self, 'kqueue') 709 | 710 | 711 | @skipUnlessJython 712 | @skipUnless(hasattr(selectors2, "JythonSelectSelector"), "Platform doesn't have a SelectSelector") 713 | class JythonSelectSelectorTestBase(_AllSelectorsTestCase): 714 | def setUp(self): 715 | patch_select_module(self, 'select') 716 | -------------------------------------------------------------------------------- /selectors2.py: -------------------------------------------------------------------------------- 1 | """ Back-ported, durable, and portable selectors """ 2 | 3 | # MIT License 4 | # 5 | # Copyright (c) 2017 Seth Michael Larson 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | from collections import namedtuple, Mapping 26 | import errno 27 | import math 28 | import platform 29 | import select 30 | import socket 31 | import sys 32 | import time 33 | 34 | try: 35 | monotonic = time.monotonic 36 | except AttributeError: 37 | monotonic = time.time 38 | 39 | __author__ = 'Seth Michael Larson' 40 | __email__ = 'sethmichaellarson@protonmail.com' 41 | __version__ = '2.0.2' 42 | __license__ = 'MIT' 43 | __url__ = 'https://www.github.com/SethMichaelLarson/selectors2' 44 | 45 | __all__ = ['EVENT_READ', 46 | 'EVENT_WRITE', 47 | 'SelectorKey', 48 | 'DefaultSelector', 49 | 'BaseSelector'] 50 | 51 | EVENT_READ = (1 << 0) 52 | EVENT_WRITE = (1 << 1) 53 | _DEFAULT_SELECTOR = None 54 | _SYSCALL_SENTINEL = object() # Sentinel in case a system call returns None. 55 | _ERROR_TYPES = (OSError, IOError, socket.error) 56 | 57 | try: 58 | _INTEGER_TYPES = (int, long) 59 | except NameError: 60 | _INTEGER_TYPES = (int,) 61 | 62 | 63 | SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data']) 64 | 65 | 66 | class _SelectorMapping(Mapping): 67 | """ Mapping of file objects to selector keys """ 68 | 69 | def __init__(self, selector): 70 | self._selector = selector 71 | 72 | def __len__(self): 73 | return len(self._selector._fd_to_key) 74 | 75 | def __getitem__(self, fileobj): 76 | try: 77 | fd = self._selector._fileobj_lookup(fileobj) 78 | return self._selector._fd_to_key[fd] 79 | except KeyError: 80 | raise KeyError("{0!r} is not registered.".format(fileobj)) 81 | 82 | def __iter__(self): 83 | return iter(self._selector._fd_to_key) 84 | 85 | 86 | def _fileobj_to_fd(fileobj): 87 | """ Return a file descriptor from a file object. If 88 | given an integer will simply return that integer back. """ 89 | if isinstance(fileobj, _INTEGER_TYPES): 90 | fd = fileobj 91 | else: 92 | for _integer_type in _INTEGER_TYPES: 93 | try: 94 | fd = _integer_type(fileobj.fileno()) 95 | break 96 | except (AttributeError, TypeError, ValueError): 97 | continue 98 | else: 99 | raise ValueError("Invalid file object: {0!r}".format(fileobj)) 100 | if fd < 0: 101 | raise ValueError("Invalid file descriptor: {0}".format(fd)) 102 | return fd 103 | 104 | 105 | class BaseSelector(object): 106 | """ Abstract Selector class 107 | 108 | A selector supports registering file objects to be monitored 109 | for specific I/O events. 110 | 111 | A file object is a file descriptor or any object with a 112 | `fileno()` method. An arbitrary object can be attached to the 113 | file object which can be used for example to store context info, 114 | a callback, etc. 115 | 116 | A selector can use various implementations (select(), poll(), epoll(), 117 | and kqueue()) depending on the platform. The 'DefaultSelector' class uses 118 | the most efficient implementation for the current platform. 119 | """ 120 | def __init__(self): 121 | # Maps file descriptors to keys. 122 | self._fd_to_key = {} 123 | 124 | # Read-only mapping returned by get_map() 125 | self._map = _SelectorMapping(self) 126 | 127 | def _fileobj_lookup(self, fileobj): 128 | """ Return a file descriptor from a file object. 129 | This wraps _fileobj_to_fd() to do an exhaustive 130 | search in case the object is invalid but we still 131 | have it in our map. Used by unregister() so we can 132 | unregister an object that was previously registered 133 | even if it is closed. It is also used by _SelectorMapping 134 | """ 135 | try: 136 | return _fileobj_to_fd(fileobj) 137 | except ValueError: 138 | 139 | # Search through all our mapped keys. 140 | for key in self._fd_to_key.values(): 141 | if key.fileobj is fileobj: 142 | return key.fd 143 | 144 | # Raise ValueError after all. 145 | raise 146 | 147 | def register(self, fileobj, events, data=None): 148 | """ Register a file object for a set of events to monitor. """ 149 | if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)): 150 | raise ValueError("Invalid events: {0!r}".format(events)) 151 | 152 | key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data) 153 | 154 | if key.fd in self._fd_to_key: 155 | raise KeyError("{0!r} (FD {1}) is already registered" 156 | .format(fileobj, key.fd)) 157 | 158 | self._fd_to_key[key.fd] = key 159 | return key 160 | 161 | def unregister(self, fileobj): 162 | """ Unregister a file object from being monitored. """ 163 | try: 164 | key = self._fd_to_key.pop(self._fileobj_lookup(fileobj)) 165 | except KeyError: 166 | raise KeyError("{0!r} is not registered".format(fileobj)) 167 | 168 | # Getting the fileno of a closed socket on Windows errors with EBADF. 169 | except socket.error as err: 170 | if err.errno != errno.EBADF: 171 | raise 172 | else: 173 | for key in self._fd_to_key.values(): 174 | if key.fileobj is fileobj: 175 | self._fd_to_key.pop(key.fd) 176 | break 177 | else: 178 | raise KeyError("{0!r} is not registered".format(fileobj)) 179 | return key 180 | 181 | def modify(self, fileobj, events, data=None): 182 | """ Change a registered file object monitored events and data. """ 183 | # NOTE: Some subclasses optimize this operation even further. 184 | try: 185 | key = self._fd_to_key[self._fileobj_lookup(fileobj)] 186 | except KeyError: 187 | raise KeyError("{0!r} is not registered".format(fileobj)) 188 | 189 | if events != key.events: 190 | self.unregister(fileobj) 191 | key = self.register(fileobj, events, data) 192 | 193 | elif data != key.data: 194 | # Use a shortcut to update the data. 195 | key = key._replace(data=data) 196 | self._fd_to_key[key.fd] = key 197 | 198 | return key 199 | 200 | def select(self, timeout=None): 201 | """ Perform the actual selection until some monitored file objects 202 | are ready or the timeout expires. """ 203 | raise NotImplementedError() 204 | 205 | def close(self): 206 | """ Close the selector. This must be called to ensure that all 207 | underlying resources are freed. """ 208 | self._fd_to_key.clear() 209 | self._map = None 210 | 211 | def get_key(self, fileobj): 212 | """ Return the key associated with a registered file object. """ 213 | mapping = self.get_map() 214 | if mapping is None: 215 | raise RuntimeError("Selector is closed") 216 | try: 217 | return mapping[fileobj] 218 | except KeyError: 219 | raise KeyError("{0!r} is not registered".format(fileobj)) 220 | 221 | def get_map(self): 222 | """ Return a mapping of file objects to selector keys """ 223 | return self._map 224 | 225 | def _key_from_fd(self, fd): 226 | """ Return the key associated to a given file descriptor 227 | Return None if it is not found. """ 228 | try: 229 | return self._fd_to_key[fd] 230 | except KeyError: 231 | return None 232 | 233 | def __enter__(self): 234 | return self 235 | 236 | def __exit__(self, *_): 237 | self.close() 238 | 239 | 240 | # Almost all platforms have select.select() 241 | if hasattr(select, "select"): 242 | class SelectSelector(BaseSelector): 243 | """ Select-based selector. """ 244 | def __init__(self): 245 | super(SelectSelector, self).__init__() 246 | self._readers = set() 247 | self._writers = set() 248 | 249 | def register(self, fileobj, events, data=None): 250 | key = super(SelectSelector, self).register(fileobj, events, data) 251 | if events & EVENT_READ: 252 | self._readers.add(key.fd) 253 | if events & EVENT_WRITE: 254 | self._writers.add(key.fd) 255 | return key 256 | 257 | def unregister(self, fileobj): 258 | key = super(SelectSelector, self).unregister(fileobj) 259 | self._readers.discard(key.fd) 260 | self._writers.discard(key.fd) 261 | return key 262 | 263 | def select(self, timeout=None): 264 | # Selecting on empty lists on Windows errors out. 265 | if not len(self._readers) and not len(self._writers): 266 | return [] 267 | 268 | timeout = None if timeout is None else max(timeout, 0.0) 269 | ready = [] 270 | r, w, _ = _syscall_wrapper(self._wrap_select, True, self._readers, 271 | self._writers, timeout=timeout) 272 | r = set(r) 273 | w = set(w) 274 | for fd in r | w: 275 | events = 0 276 | if fd in r: 277 | events |= EVENT_READ 278 | if fd in w: 279 | events |= EVENT_WRITE 280 | 281 | key = self._key_from_fd(fd) 282 | if key: 283 | ready.append((key, events & key.events)) 284 | return ready 285 | 286 | def _wrap_select(self, r, w, timeout=None): 287 | """ Wrapper for select.select because timeout is a positional arg """ 288 | return select.select(r, w, [], timeout) 289 | 290 | __all__.append('SelectSelector') 291 | 292 | # Jython has a different implementation of .fileno() for socket objects. 293 | if platform.python_implementation() == 'Jython': 294 | class _JythonSelectorMapping(object): 295 | """ This is an implementation of _SelectorMapping that is built 296 | for use specifically with Jython, which does not provide a hashable 297 | value from socket.socket.fileno(). """ 298 | 299 | def __init__(self, selector): 300 | assert isinstance(selector, JythonSelectSelector) 301 | self._selector = selector 302 | 303 | def __len__(self): 304 | return len(self._selector._sockets) 305 | 306 | def __getitem__(self, fileobj): 307 | for sock, key in self._selector._sockets: 308 | if sock is fileobj: 309 | return key 310 | else: 311 | raise KeyError("{0!r} is not registered.".format(fileobj)) 312 | 313 | class JythonSelectSelector(SelectSelector): 314 | """ This is an implementation of SelectSelector that is for Jython 315 | which works around that Jython's socket.socket.fileno() does not 316 | return an integer fd value. All SelectorKey.fd will be equal to -1 317 | and should not be used. This instead uses object id to compare fileobj 318 | and will only use select.select as it's the only selector that allows 319 | directly passing in socket objects rather than registering fds. 320 | See: http://bugs.jython.org/issue1678 321 | https://wiki.python.org/jython/NewSocketModule#socket.fileno.28.29_does_not_return_an_integer 322 | """ 323 | 324 | def __init__(self): 325 | super(JythonSelectSelector, self).__init__() 326 | 327 | self._sockets = [] # Uses a list of tuples instead of dictionary. 328 | self._map = _JythonSelectorMapping(self) 329 | self._readers = [] 330 | self._writers = [] 331 | 332 | # Jython has a select.cpython_compatible_select function in older versions. 333 | self._select_func = getattr(select, 'cpython_compatible_select', select.select) 334 | 335 | def register(self, fileobj, events, data=None): 336 | for sock, _ in self._sockets: 337 | if sock is fileobj: 338 | raise KeyError("{0!r} is already registered" 339 | .format(fileobj, sock)) 340 | 341 | key = SelectorKey(fileobj, -1, events, data) 342 | self._sockets.append((fileobj, key)) 343 | 344 | if events & EVENT_READ: 345 | self._readers.append(fileobj) 346 | if events & EVENT_WRITE: 347 | self._writers.append(fileobj) 348 | return key 349 | 350 | def unregister(self, fileobj): 351 | for i, (sock, key) in enumerate(self._sockets): 352 | if sock is fileobj: 353 | break 354 | else: 355 | raise KeyError("{0!r} is not registered.".format(fileobj)) 356 | 357 | if key.events & EVENT_READ: 358 | self._readers.remove(fileobj) 359 | if key.events & EVENT_WRITE: 360 | self._writers.remove(fileobj) 361 | 362 | del self._sockets[i] 363 | return key 364 | 365 | def _wrap_select(self, r, w, timeout=None): 366 | """ Wrapper for select.select because timeout is a positional arg """ 367 | return self._select_func(r, w, [], timeout) 368 | 369 | __all__.append('JythonSelectSelector') 370 | SelectSelector = JythonSelectSelector # Override so the wrong selector isn't used. 371 | 372 | 373 | if hasattr(select, "poll"): 374 | class PollSelector(BaseSelector): 375 | """ Poll-based selector """ 376 | def __init__(self): 377 | super(PollSelector, self).__init__() 378 | self._poll = select.poll() 379 | 380 | def register(self, fileobj, events, data=None): 381 | key = super(PollSelector, self).register(fileobj, events, data) 382 | event_mask = 0 383 | if events & EVENT_READ: 384 | event_mask |= select.POLLIN 385 | if events & EVENT_WRITE: 386 | event_mask |= select.POLLOUT 387 | self._poll.register(key.fd, event_mask) 388 | return key 389 | 390 | def unregister(self, fileobj): 391 | key = super(PollSelector, self).unregister(fileobj) 392 | self._poll.unregister(key.fd) 393 | return key 394 | 395 | def _wrap_poll(self, timeout=None): 396 | """ Wrapper function for select.poll.poll() so that 397 | _syscall_wrapper can work with only seconds. """ 398 | if timeout is not None: 399 | if timeout <= 0: 400 | timeout = 0 401 | else: 402 | # select.poll.poll() has a resolution of 1 millisecond, 403 | # round away from zero to wait *at least* timeout seconds. 404 | timeout = math.ceil(timeout * 1000) 405 | 406 | result = self._poll.poll(timeout) 407 | return result 408 | 409 | def select(self, timeout=None): 410 | ready = [] 411 | fd_events = _syscall_wrapper(self._wrap_poll, True, timeout=timeout) 412 | for fd, event_mask in fd_events: 413 | events = 0 414 | if event_mask & ~select.POLLIN: 415 | events |= EVENT_WRITE 416 | if event_mask & ~select.POLLOUT: 417 | events |= EVENT_READ 418 | 419 | key = self._key_from_fd(fd) 420 | if key: 421 | ready.append((key, events & key.events)) 422 | 423 | return ready 424 | 425 | __all__.append('PollSelector') 426 | 427 | if hasattr(select, "epoll"): 428 | class EpollSelector(BaseSelector): 429 | """ Epoll-based selector """ 430 | def __init__(self): 431 | super(EpollSelector, self).__init__() 432 | self._epoll = select.epoll() 433 | 434 | def fileno(self): 435 | return self._epoll.fileno() 436 | 437 | def register(self, fileobj, events, data=None): 438 | key = super(EpollSelector, self).register(fileobj, events, data) 439 | events_mask = 0 440 | if events & EVENT_READ: 441 | events_mask |= select.EPOLLIN 442 | if events & EVENT_WRITE: 443 | events_mask |= select.EPOLLOUT 444 | _syscall_wrapper(self._epoll.register, False, key.fd, events_mask) 445 | return key 446 | 447 | def unregister(self, fileobj): 448 | key = super(EpollSelector, self).unregister(fileobj) 449 | try: 450 | _syscall_wrapper(self._epoll.unregister, False, key.fd) 451 | except _ERROR_TYPES: 452 | # This can occur when the fd was closed since registry. 453 | pass 454 | return key 455 | 456 | def select(self, timeout=None): 457 | if timeout is not None: 458 | if timeout <= 0: 459 | timeout = 0.0 460 | else: 461 | # select.epoll.poll() has a resolution of 1 millisecond 462 | # but luckily takes seconds so we don't need a wrapper 463 | # like PollSelector. Just for better rounding. 464 | timeout = math.ceil(timeout * 1000) * 0.001 465 | timeout = float(timeout) 466 | else: 467 | timeout = -1.0 # epoll.poll() must have a float. 468 | 469 | # We always want at least 1 to ensure that select can be called 470 | # with no file descriptors registered. Otherwise will fail. 471 | max_events = max(len(self._fd_to_key), 1) 472 | 473 | ready = [] 474 | fd_events = _syscall_wrapper(self._epoll.poll, True, 475 | timeout=timeout, 476 | maxevents=max_events) 477 | for fd, event_mask in fd_events: 478 | events = 0 479 | if event_mask & ~select.EPOLLIN: 480 | events |= EVENT_WRITE 481 | if event_mask & ~select.EPOLLOUT: 482 | events |= EVENT_READ 483 | 484 | key = self._key_from_fd(fd) 485 | if key: 486 | ready.append((key, events & key.events)) 487 | return ready 488 | 489 | def close(self): 490 | self._epoll.close() 491 | super(EpollSelector, self).close() 492 | 493 | __all__.append('EpollSelector') 494 | 495 | 496 | if hasattr(select, "devpoll"): 497 | class DevpollSelector(BaseSelector): 498 | """Solaris /dev/poll selector.""" 499 | 500 | def __init__(self): 501 | super(DevpollSelector, self).__init__() 502 | self._devpoll = select.devpoll() 503 | 504 | def fileno(self): 505 | return self._devpoll.fileno() 506 | 507 | def register(self, fileobj, events, data=None): 508 | key = super(DevpollSelector, self).register(fileobj, events, data) 509 | poll_events = 0 510 | if events & EVENT_READ: 511 | poll_events |= select.POLLIN 512 | if events & EVENT_WRITE: 513 | poll_events |= select.POLLOUT 514 | self._devpoll.register(key.fd, poll_events) 515 | return key 516 | 517 | def unregister(self, fileobj): 518 | key = super(DevpollSelector, self).unregister(fileobj) 519 | self._devpoll.unregister(key.fd) 520 | return key 521 | 522 | def _wrap_poll(self, timeout=None): 523 | """ Wrapper function for select.poll.poll() so that 524 | _syscall_wrapper can work with only seconds. """ 525 | if timeout is not None: 526 | if timeout <= 0: 527 | timeout = 0 528 | else: 529 | # select.devpoll.poll() has a resolution of 1 millisecond, 530 | # round away from zero to wait *at least* timeout seconds. 531 | timeout = math.ceil(timeout * 1000) 532 | 533 | result = self._devpoll.poll(timeout) 534 | return result 535 | 536 | def select(self, timeout=None): 537 | ready = [] 538 | fd_events = _syscall_wrapper(self._wrap_poll, True, timeout=timeout) 539 | for fd, event_mask in fd_events: 540 | events = 0 541 | if event_mask & ~select.POLLIN: 542 | events |= EVENT_WRITE 543 | if event_mask & ~select.POLLOUT: 544 | events |= EVENT_READ 545 | 546 | key = self._key_from_fd(fd) 547 | if key: 548 | ready.append((key, events & key.events)) 549 | 550 | return ready 551 | 552 | def close(self): 553 | self._devpoll.close() 554 | super(DevpollSelector, self).close() 555 | 556 | __all__.append('DevpollSelector') 557 | 558 | 559 | if hasattr(select, "kqueue"): 560 | class KqueueSelector(BaseSelector): 561 | """ Kqueue / Kevent-based selector """ 562 | def __init__(self): 563 | super(KqueueSelector, self).__init__() 564 | self._kqueue = select.kqueue() 565 | 566 | def fileno(self): 567 | return self._kqueue.fileno() 568 | 569 | def register(self, fileobj, events, data=None): 570 | key = super(KqueueSelector, self).register(fileobj, events, data) 571 | if events & EVENT_READ: 572 | kevent = select.kevent(key.fd, 573 | select.KQ_FILTER_READ, 574 | select.KQ_EV_ADD) 575 | 576 | _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0) 577 | 578 | if events & EVENT_WRITE: 579 | kevent = select.kevent(key.fd, 580 | select.KQ_FILTER_WRITE, 581 | select.KQ_EV_ADD) 582 | 583 | _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0) 584 | 585 | return key 586 | 587 | def unregister(self, fileobj): 588 | key = super(KqueueSelector, self).unregister(fileobj) 589 | if key.events & EVENT_READ: 590 | kevent = select.kevent(key.fd, 591 | select.KQ_FILTER_READ, 592 | select.KQ_EV_DELETE) 593 | try: 594 | _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0) 595 | except _ERROR_TYPES: 596 | pass 597 | if key.events & EVENT_WRITE: 598 | kevent = select.kevent(key.fd, 599 | select.KQ_FILTER_WRITE, 600 | select.KQ_EV_DELETE) 601 | try: 602 | _syscall_wrapper(self._wrap_control, False, [kevent], 0, 0) 603 | except _ERROR_TYPES: 604 | pass 605 | 606 | return key 607 | 608 | def select(self, timeout=None): 609 | if timeout is not None: 610 | timeout = max(timeout, 0) 611 | 612 | max_events = len(self._fd_to_key) * 2 613 | ready_fds = {} 614 | 615 | kevent_list = _syscall_wrapper(self._wrap_control, True, 616 | None, max_events, timeout=timeout) 617 | 618 | for kevent in kevent_list: 619 | fd = kevent.ident 620 | event_mask = kevent.filter 621 | events = 0 622 | if event_mask == select.KQ_FILTER_READ: 623 | events |= EVENT_READ 624 | if event_mask == select.KQ_FILTER_WRITE: 625 | events |= EVENT_WRITE 626 | 627 | key = self._key_from_fd(fd) 628 | if key: 629 | if key.fd not in ready_fds: 630 | ready_fds[key.fd] = (key, events & key.events) 631 | else: 632 | old_events = ready_fds[key.fd][1] 633 | ready_fds[key.fd] = (key, (events | old_events) & key.events) 634 | 635 | return list(ready_fds.values()) 636 | 637 | def close(self): 638 | self._kqueue.close() 639 | super(KqueueSelector, self).close() 640 | 641 | def _wrap_control(self, changelist, max_events, timeout): 642 | return self._kqueue.control(changelist, max_events, timeout) 643 | 644 | __all__.append('KqueueSelector') 645 | 646 | 647 | def _can_allocate(struct): 648 | """ Checks that select structs can be allocated by the underlying 649 | operating system, not just advertised by the select module. We don't 650 | check select() because we'll be hopeful that most platforms that 651 | don't have it available will not advertise it. (ie: GAE) """ 652 | try: 653 | # select.poll() objects won't fail until used. 654 | if struct == 'poll': 655 | p = select.poll() 656 | p.poll(0) 657 | 658 | # All others will fail on allocation. 659 | else: 660 | getattr(select, struct)().close() 661 | return True 662 | except (OSError, AttributeError): 663 | return False 664 | 665 | 666 | # Python 3.5 uses a more direct route to wrap system calls to increase speed. 667 | if sys.version_info >= (3, 5): 668 | def _syscall_wrapper(func, _, *args, **kwargs): 669 | """ This is the short-circuit version of the below logic 670 | because in Python 3.5+ all selectors restart system calls. """ 671 | return func(*args, **kwargs) 672 | else: 673 | def _syscall_wrapper(func, recalc_timeout, *args, **kwargs): 674 | """ Wrapper function for syscalls that could fail due to EINTR. 675 | All functions should be retried if there is time left in the timeout 676 | in accordance with PEP 475. """ 677 | timeout = kwargs.get("timeout", None) 678 | if timeout is None: 679 | expires = None 680 | recalc_timeout = False 681 | else: 682 | timeout = float(timeout) 683 | if timeout < 0.0: # Timeout less than 0 treated as no timeout. 684 | expires = None 685 | else: 686 | expires = monotonic() + timeout 687 | 688 | if recalc_timeout and 'timeout' not in kwargs: 689 | raise ValueError( 690 | 'Timeout must be in kwargs to be recalculated') 691 | 692 | result = _SYSCALL_SENTINEL 693 | while result is _SYSCALL_SENTINEL: 694 | try: 695 | result = func(*args, **kwargs) 696 | # OSError is thrown by select.select 697 | # IOError is thrown by select.epoll.poll 698 | # select.error is thrown by select.poll.poll 699 | # Aren't we thankful for Python 3.x rework for exceptions? 700 | except (OSError, IOError, select.error) as e: 701 | # select.error wasn't a subclass of OSError in the past. 702 | errcode = None 703 | if hasattr(e, 'errno') and e.errno is not None: 704 | errcode = e.errno 705 | elif hasattr(e, 'args'): 706 | errcode = e.args[0] 707 | 708 | # Also test for the Windows equivalent of EINTR. 709 | is_interrupt = (errcode == errno.EINTR or (hasattr(errno, 'WSAEINTR') and 710 | errcode == errno.WSAEINTR)) 711 | 712 | if is_interrupt: 713 | if expires is not None: 714 | current_time = monotonic() 715 | if current_time > expires: 716 | raise OSError(errno.ETIMEDOUT, 'Connection timed out') 717 | if recalc_timeout: 718 | kwargs["timeout"] = expires - current_time 719 | continue 720 | raise 721 | return result 722 | 723 | 724 | # Choose the best implementation, roughly: 725 | # kqueue == devpoll == epoll > poll > select 726 | # select() also can't accept a FD > FD_SETSIZE (usually around 1024) 727 | def DefaultSelector(): 728 | """ This function serves as a first call for DefaultSelector to 729 | detect if the select module is being monkey-patched incorrectly 730 | by eventlet, greenlet, and preserve proper behavior. """ 731 | global _DEFAULT_SELECTOR 732 | if _DEFAULT_SELECTOR is None: 733 | if platform.python_implementation() == 'Jython': # Platform-specific: Jython 734 | _DEFAULT_SELECTOR = JythonSelectSelector 735 | elif _can_allocate('kqueue'): 736 | _DEFAULT_SELECTOR = KqueueSelector 737 | elif _can_allocate('devpoll'): 738 | _DEFAULT_SELECTOR = DevpollSelector 739 | elif _can_allocate('epoll'): 740 | _DEFAULT_SELECTOR = EpollSelector 741 | elif _can_allocate('poll'): 742 | _DEFAULT_SELECTOR = PollSelector 743 | elif hasattr(select, 'select'): 744 | _DEFAULT_SELECTOR = SelectSelector 745 | else: # Platform-specific: AppEngine 746 | raise RuntimeError('Platform does not have a selector.') 747 | return _DEFAULT_SELECTOR() 748 | --------------------------------------------------------------------------------