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