From 78208c8c327b8bacd864456212e1335d3560b12d Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 29 Jul 2018 14:07:36 +0300 Subject: [PATCH] Drop support for EOL Python 3.2 and 3.3 (#1839) --- .pylintrc | 1 - NOTICE | 53 ---- README.rst | 2 +- docs/source/index.rst | 2 +- docs/source/install.rst | 2 +- gunicorn/_compat.py | 90 ------ gunicorn/selectors.py | 592 ------------------------------------ gunicorn/workers/gthread.py | 13 +- setup.py | 4 +- tests/test_selectors.py | 441 --------------------------- 10 files changed, 14 insertions(+), 1186 deletions(-) delete mode 100644 gunicorn/selectors.py delete mode 100644 tests/test_selectors.py diff --git a/.pylintrc b/.pylintrc index b3eedfec..9d6c393b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -8,7 +8,6 @@ ignore= _compat.py, argparse_compat.py, six.py, - selectors.py, _gaiohttp.py, [MESSAGES CONTROL] diff --git a/NOTICE b/NOTICE index 31b5ddc0..736bf32e 100644 --- a/NOTICE +++ b/NOTICE @@ -122,56 +122,3 @@ util/unlink.py -------------- backport frop python3 Lib/test/support.py - - -gunicorn/selectors.py ---------------------- -Copyright (c) 2001-2016 Python Software Foundation; All Rights Reserved - -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF hereby -grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, -analyze, test, perform and/or display publicly, prepare derivative works, -distribute, and otherwise use Python alone or in any derivative version, -provided, however, that PSF's License Agreement and PSF's notice of copyright, -i.e., "Copyright (c) 2001-2016 Python Software Foundation; All Rights -Reserved" are retained in Python alone or in any derivative version prepared -by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. diff --git a/README.rst b/README.rst index 170e2e3a..4bb8fac1 100644 --- a/README.rst +++ b/README.rst @@ -28,7 +28,7 @@ The documentation is hosted at http://docs.gunicorn.org. Installation ------------ -Gunicorn requires **Python 2.x >= 2.6** or **Python 3.x >= 3.2**. +Gunicorn requires **Python 2.x >= 2.6** or **Python 3.x >= 3.4**. Install from PyPI:: diff --git a/docs/source/index.rst b/docs/source/index.rst index d8a6ed1a..0c9303c5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,7 +23,7 @@ Features * Simple Python configuration * Multiple worker configurations * Various server hooks for extensibility -* Compatible with Python 2.x >= 2.6 or 3.x >= 3.2 +* Compatible with Python 2.x >= 2.6 or 3.x >= 3.4 Contents diff --git a/docs/source/install.rst b/docs/source/install.rst index 2d312773..f997a1ac 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -4,7 +4,7 @@ Installation .. highlight:: bash -:Requirements: **Python 2.x >= 2.6** or **Python 3.x >= 3.2** +:Requirements: **Python 2.x >= 2.6** or **Python 3.x >= 3.4** To install the latest released version of Gunicorn:: diff --git a/gunicorn/_compat.py b/gunicorn/_compat.py index 39dbfdfc..2416b3e3 100644 --- a/gunicorn/_compat.py +++ b/gunicorn/_compat.py @@ -3,7 +3,6 @@ import sys from gunicorn import six PY26 = (sys.version_info[:2] == (2, 6)) -PY33 = (sys.version_info >= (3, 3)) def _check_if_pyc(fname): @@ -110,95 +109,6 @@ def _wrap_error(exc, mapping, key): six.reraise(new_err_cls, new_err, exc.__traceback__ if hasattr(exc, '__traceback__') else sys.exc_info()[2]) -if PY33: - import builtins - - BlockingIOError = builtins.BlockingIOError - BrokenPipeError = builtins.BrokenPipeError - ChildProcessError = builtins.ChildProcessError - ConnectionRefusedError = builtins.ConnectionRefusedError - ConnectionResetError = builtins.ConnectionResetError - InterruptedError = builtins.InterruptedError - ConnectionAbortedError = builtins.ConnectionAbortedError - PermissionError = builtins.PermissionError - FileNotFoundError = builtins.FileNotFoundError - ProcessLookupError = builtins.ProcessLookupError - - def wrap_error(func, *args, **kw): - return func(*args, **kw) -else: - import errno - import select - import socket - - class BlockingIOError(OSError): - pass - - class BrokenPipeError(OSError): - pass - - class ChildProcessError(OSError): - pass - - class ConnectionRefusedError(OSError): - pass - - class InterruptedError(OSError): - pass - - class ConnectionResetError(OSError): - pass - - class ConnectionAbortedError(OSError): - pass - - class PermissionError(OSError): - pass - - class FileNotFoundError(OSError): - pass - - class ProcessLookupError(OSError): - pass - - _MAP_ERRNO = { - errno.EACCES: PermissionError, - errno.EAGAIN: BlockingIOError, - errno.EALREADY: BlockingIOError, - errno.ECHILD: ChildProcessError, - errno.ECONNABORTED: ConnectionAbortedError, - errno.ECONNREFUSED: ConnectionRefusedError, - errno.ECONNRESET: ConnectionResetError, - errno.EINPROGRESS: BlockingIOError, - errno.EINTR: InterruptedError, - errno.ENOENT: FileNotFoundError, - errno.EPERM: PermissionError, - errno.EPIPE: BrokenPipeError, - errno.ESHUTDOWN: BrokenPipeError, - errno.EWOULDBLOCK: BlockingIOError, - errno.ESRCH: ProcessLookupError, - } - - def wrap_error(func, *args, **kw): - """ - Wrap socket.error, IOError, OSError, select.error to raise new specialized - exceptions of Python 3.3 like InterruptedError (PEP 3151). - """ - try: - return func(*args, **kw) - except (socket.error, IOError, OSError) as exc: - if hasattr(exc, 'winerror'): - _wrap_error(exc, _MAP_ERRNO, exc.winerror) - # _MAP_ERRNO does not contain all Windows errors. - # For some errors like "file not found", exc.errno should - # be used (ex: ENOENT). - _wrap_error(exc, _MAP_ERRNO, exc.errno) - raise - except select.error as exc: - if exc.args: - _wrap_error(exc, _MAP_ERRNO, exc.args[0]) - raise - if PY26: from urlparse import ( _parse_cache, MAX_CACHE_SIZE, clear_cache, _splitnetloc, SplitResult, diff --git a/gunicorn/selectors.py b/gunicorn/selectors.py deleted file mode 100644 index cdae5690..00000000 --- a/gunicorn/selectors.py +++ /dev/null @@ -1,592 +0,0 @@ -"""Selectors module. - -This module allows high-level and efficient I/O multiplexing, built upon the -`select` module primitives. - -The following code adapted from trollius.selectors. -""" - - -from abc import ABCMeta, abstractmethod -from collections import namedtuple, Mapping -import math -import select -import sys - -from gunicorn._compat import wrap_error, InterruptedError -from gunicorn import six - - -# generic events, that must be mapped to implementation-specific ones -EVENT_READ = (1 << 0) -EVENT_WRITE = (1 << 1) - - -def _fileobj_to_fd(fileobj): - """Return a file descriptor from a file object. - - Parameters: - fileobj -- file object or file descriptor - - Returns: - corresponding file descriptor - - Raises: - ValueError if the object is invalid - """ - if isinstance(fileobj, six.integer_types): - fd = fileobj - else: - try: - fd = int(fileobj.fileno()) - except (AttributeError, TypeError, ValueError): - raise ValueError("Invalid file object: " - "{0!r}".format(fileobj)) - if fd < 0: - raise ValueError("Invalid file descriptor: {0}".format(fd)) - return fd - - -SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data']) -"""Object used to associate a file object to its backing file descriptor, -selected event mask and attached data.""" - - -class _SelectorMapping(Mapping): - """Mapping of file objects to selector keys.""" - - def __init__(self, selector): - self._selector = selector - - def __len__(self): - return len(self._selector._fd_to_key) - - def __getitem__(self, fileobj): - try: - fd = self._selector._fileobj_lookup(fileobj) - return self._selector._fd_to_key[fd] - except KeyError: - raise KeyError("{0!r} is not registered".format(fileobj)) - - def __iter__(self): - return iter(self._selector._fd_to_key) - - -class BaseSelector(six.with_metaclass(ABCMeta)): - """Selector abstract base class. - - A selector supports registering file objects to be monitored for specific - I/O events. - - A file object is a file descriptor or any object with a `fileno()` method. - An arbitrary object can be attached to the file object, which can be used - for example to store context information, a callback, etc. - - A selector can use various implementations (select(), poll(), epoll()...) - depending on the platform. The default `Selector` class uses the most - efficient implementation on the current platform. - """ - - @abstractmethod - def register(self, fileobj, events, data=None): - """Register a file object. - - Parameters: - fileobj -- file object or file descriptor - events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE) - data -- attached data - - Returns: - SelectorKey instance - - Raises: - ValueError if events is invalid - KeyError if fileobj is already registered - OSError if fileobj is closed or otherwise is unacceptable to - the underlying system call (if a system call is made) - - Note: - OSError may or may not be raised - """ - raise NotImplementedError - - @abstractmethod - def unregister(self, fileobj): - """Unregister a file object. - - Parameters: - fileobj -- file object or file descriptor - - Returns: - SelectorKey instance - - Raises: - KeyError if fileobj is not registered - - Note: - If fileobj is registered but has since been closed this does - *not* raise OSError (even if the wrapped syscall does) - """ - raise NotImplementedError - - def modify(self, fileobj, events, data=None): - """Change a registered file object monitored events or attached data. - - Parameters: - fileobj -- file object or file descriptor - events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE) - data -- attached data - - Returns: - SelectorKey instance - - Raises: - Anything that unregister() or register() raises - """ - self.unregister(fileobj) - return self.register(fileobj, events, data) - - @abstractmethod - def select(self, timeout=None): - """Perform the actual selection, until some monitored file objects are - ready or a timeout expires. - - Parameters: - timeout -- if timeout > 0, this specifies the maximum wait time, in - seconds - if timeout <= 0, the select() call won't block, and will - report the currently ready file objects - if timeout is None, select() will block until a monitored - file object becomes ready - - Returns: - list of (key, events) for ready file objects - `events` is a bitwise mask of EVENT_READ|EVENT_WRITE - """ - raise NotImplementedError - - def close(self): - """Close the selector. - - This must be called to make sure that any underlying resource is freed. - """ - pass - - def get_key(self, fileobj): - """Return the key associated to a registered file object. - - Returns: - SelectorKey for this file object - """ - mapping = self.get_map() - try: - return mapping[fileobj] - except KeyError: - raise KeyError("{0!r} is not registered".format(fileobj)) - - @abstractmethod - def get_map(self): - """Return a mapping of file objects to selector keys.""" - raise NotImplementedError - - def __enter__(self): - return self - - def __exit__(self, *args): - self.close() - - -class _BaseSelectorImpl(BaseSelector): - """Base selector implementation.""" - - def __init__(self): - # this maps file descriptors to keys - self._fd_to_key = {} - # read-only mapping returned by get_map() - self._map = _SelectorMapping(self) - - def _fileobj_lookup(self, fileobj): - """Return a file descriptor from a file object. - - This wraps _fileobj_to_fd() to do an exhaustive search in case - the object is invalid but we still have it in our map. This - is used by unregister() so we can unregister an object that - was previously registered even if it is closed. It is also - used by _SelectorMapping. - """ - try: - return _fileobj_to_fd(fileobj) - except ValueError: - # Do an exhaustive search. - for key in self._fd_to_key.values(): - if key.fileobj is fileobj: - return key.fd - # Raise ValueError after all. - raise - - def register(self, fileobj, events, data=None): - if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)): - raise ValueError("Invalid events: {0!r}".format(events)) - - key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data) - - if key.fd in self._fd_to_key: - raise KeyError("{0!r} (FD {1}) is already registered" - .format(fileobj, key.fd)) - - self._fd_to_key[key.fd] = key - return key - - def unregister(self, fileobj): - try: - key = self._fd_to_key.pop(self._fileobj_lookup(fileobj)) - except KeyError: - raise KeyError("{0!r} is not registered".format(fileobj)) - return key - - def modify(self, fileobj, events, data=None): - # TODO: Subclasses can probably optimize this even further. - try: - key = self._fd_to_key[self._fileobj_lookup(fileobj)] - except KeyError: - raise KeyError("{0!r} is not registered".format(fileobj)) - if events != key.events: - self.unregister(fileobj) - key = self.register(fileobj, events, data) - elif data != key.data: - # Use a shortcut to update the data. - key = key._replace(data=data) - self._fd_to_key[key.fd] = key - return key - - def close(self): - self._fd_to_key.clear() - - def get_map(self): - return self._map - - def _key_from_fd(self, fd): - """Return the key associated to a given file descriptor. - - Parameters: - fd -- file descriptor - - Returns: - corresponding key, or None if not found - """ - try: - return self._fd_to_key[fd] - except KeyError: - return None - - -class SelectSelector(_BaseSelectorImpl): - """Select-based selector.""" - - def __init__(self): - super(SelectSelector, self).__init__() - self._readers = set() - self._writers = set() - - def register(self, fileobj, events, data=None): - key = super(SelectSelector, self).register(fileobj, events, data) - if events & EVENT_READ: - self._readers.add(key.fd) - if events & EVENT_WRITE: - self._writers.add(key.fd) - return key - - def unregister(self, fileobj): - key = super(SelectSelector, self).unregister(fileobj) - self._readers.discard(key.fd) - self._writers.discard(key.fd) - return key - - if sys.platform == 'win32': - def _select(self, r, w, _, timeout=None): - r, w, x = select.select(r, w, w, timeout) - return r, w + x, [] - else: - _select = select.select - - def select(self, timeout=None): - timeout = None if timeout is None else max(timeout, 0) - ready = [] - try: - r, w, _ = wrap_error(self._select, - self._readers, self._writers, [], timeout) - except InterruptedError: - return ready - r = set(r) - w = set(w) - for fd in r | w: - events = 0 - if fd in r: - events |= EVENT_READ - if fd in w: - events |= EVENT_WRITE - - key = self._key_from_fd(fd) - if key: - ready.append((key, events & key.events)) - return ready - - -if hasattr(select, 'poll'): - - class PollSelector(_BaseSelectorImpl): - """Poll-based selector.""" - - def __init__(self): - super(PollSelector, self).__init__() - self._poll = select.poll() - - def register(self, fileobj, events, data=None): - key = super(PollSelector, self).register(fileobj, events, data) - poll_events = 0 - if events & EVENT_READ: - poll_events |= select.POLLIN - if events & EVENT_WRITE: - poll_events |= select.POLLOUT - self._poll.register(key.fd, poll_events) - return key - - def unregister(self, fileobj): - key = super(PollSelector, self).unregister(fileobj) - self._poll.unregister(key.fd) - return key - - def select(self, timeout=None): - if timeout is None: - timeout = None - elif timeout <= 0: - timeout = 0 - else: - # poll() has a resolution of 1 millisecond, round away from - # zero to wait *at least* timeout seconds. - timeout = int(math.ceil(timeout * 1e3)) - ready = [] - try: - fd_event_list = wrap_error(self._poll.poll, timeout) - except InterruptedError: - return ready - for fd, event in fd_event_list: - events = 0 - if event & ~select.POLLIN: - events |= EVENT_WRITE - if event & ~select.POLLOUT: - events |= EVENT_READ - - key = self._key_from_fd(fd) - if key: - ready.append((key, events & key.events)) - return ready - - -if hasattr(select, 'epoll'): - - class EpollSelector(_BaseSelectorImpl): - """Epoll-based selector.""" - - def __init__(self): - super(EpollSelector, self).__init__() - self._epoll = select.epoll() - - def fileno(self): - return self._epoll.fileno() - - def register(self, fileobj, events, data=None): - key = super(EpollSelector, self).register(fileobj, events, data) - epoll_events = 0 - if events & EVENT_READ: - epoll_events |= select.EPOLLIN - if events & EVENT_WRITE: - epoll_events |= select.EPOLLOUT - self._epoll.register(key.fd, epoll_events) - return key - - def unregister(self, fileobj): - key = super(EpollSelector, self).unregister(fileobj) - try: - self._epoll.unregister(key.fd) - except OSError: - # This can happen if the FD was closed since it - # was registered. - pass - return key - - def select(self, timeout=None): - if timeout is None: - timeout = -1 - elif timeout <= 0: - timeout = 0 - else: - # epoll_wait() has a resolution of 1 millisecond, round away - # from zero to wait *at least* timeout seconds. - timeout = math.ceil(timeout * 1e3) * 1e-3 - max_ev = len(self._fd_to_key) - ready = [] - try: - fd_event_list = wrap_error(self._epoll.poll, timeout, max_ev) - except InterruptedError: - return ready - for fd, event in fd_event_list: - events = 0 - if event & ~select.EPOLLIN: - events |= EVENT_WRITE - if event & ~select.EPOLLOUT: - events |= EVENT_READ - - key = self._key_from_fd(fd) - if key: - ready.append((key, events & key.events)) - return ready - - def close(self): - self._epoll.close() - super(EpollSelector, self).close() - - -if hasattr(select, 'devpoll'): - - class DevpollSelector(_BaseSelectorImpl): - """Solaris /dev/poll selector.""" - - def __init__(self): - super(DevpollSelector, self).__init__() - self._devpoll = select.devpoll() - - def fileno(self): - return self._devpoll.fileno() - - def register(self, fileobj, events, data=None): - key = super(DevpollSelector, self).register(fileobj, events, data) - poll_events = 0 - if events & EVENT_READ: - poll_events |= select.POLLIN - if events & EVENT_WRITE: - poll_events |= select.POLLOUT - self._devpoll.register(key.fd, poll_events) - return key - - def unregister(self, fileobj): - key = super(DevpollSelector, self).unregister(fileobj) - self._devpoll.unregister(key.fd) - return key - - def select(self, timeout=None): - if timeout is None: - timeout = None - elif timeout <= 0: - timeout = 0 - else: - # devpoll() has a resolution of 1 millisecond, round away from - # zero to wait *at least* timeout seconds. - timeout = math.ceil(timeout * 1e3) - ready = [] - try: - fd_event_list = self._devpoll.poll(timeout) - except InterruptedError: - return ready - for fd, event in fd_event_list: - events = 0 - if event & ~select.POLLIN: - events |= EVENT_WRITE - if event & ~select.POLLOUT: - events |= EVENT_READ - - key = self._key_from_fd(fd) - if key: - ready.append((key, events & key.events)) - return ready - - def close(self): - self._devpoll.close() - super(DevpollSelector, self).close() - - -if hasattr(select, 'kqueue'): - - class KqueueSelector(_BaseSelectorImpl): - """Kqueue-based selector.""" - - def __init__(self): - super(KqueueSelector, self).__init__() - self._kqueue = select.kqueue() - - def fileno(self): - return self._kqueue.fileno() - - def register(self, fileobj, events, data=None): - key = super(KqueueSelector, self).register(fileobj, events, data) - if events & EVENT_READ: - kev = select.kevent(key.fd, select.KQ_FILTER_READ, - select.KQ_EV_ADD) - self._kqueue.control([kev], 0, 0) - if events & EVENT_WRITE: - kev = select.kevent(key.fd, select.KQ_FILTER_WRITE, - select.KQ_EV_ADD) - self._kqueue.control([kev], 0, 0) - return key - - def unregister(self, fileobj): - key = super(KqueueSelector, self).unregister(fileobj) - if key.events & EVENT_READ: - kev = select.kevent(key.fd, select.KQ_FILTER_READ, - select.KQ_EV_DELETE) - try: - self._kqueue.control([kev], 0, 0) - except OSError: - # This can happen if the FD was closed since it - # was registered. - pass - if key.events & EVENT_WRITE: - kev = select.kevent(key.fd, select.KQ_FILTER_WRITE, - select.KQ_EV_DELETE) - try: - self._kqueue.control([kev], 0, 0) - except OSError: - # See comment above. - pass - return key - - def select(self, timeout=None): - timeout = None if timeout is None else max(timeout, 0) - max_ev = len(self._fd_to_key) - ready = [] - try: - kev_list = wrap_error(self._kqueue.control, - None, max_ev, timeout) - except InterruptedError: - return ready - for kev in kev_list: - fd = kev.ident - flag = kev.filter - events = 0 - if flag == select.KQ_FILTER_READ: - events |= EVENT_READ - if flag == select.KQ_FILTER_WRITE: - events |= EVENT_WRITE - - key = self._key_from_fd(fd) - if key: - ready.append((key, events & key.events)) - return ready - - def close(self): - self._kqueue.close() - super(KqueueSelector, self).close() - - -# Choose the best implementation: roughly, epoll|kqueue|devpoll > poll > select. -# select() also can't accept a FD > FD_SETSIZE (usually around 1024) -if 'KqueueSelector' in globals(): - DefaultSelector = KqueueSelector -elif 'EpollSelector' in globals(): - DefaultSelector = EpollSelector -elif 'DevpollSelector' in globals(): - DefaultSelector = DevpollSelector -elif 'PollSelector' in globals(): - DefaultSelector = PollSelector -else: - DefaultSelector = SelectSelector diff --git a/gunicorn/workers/gthread.py b/gunicorn/workers/gthread.py index 862f873d..43712c45 100644 --- a/gunicorn/workers/gthread.py +++ b/gunicorn/workers/gthread.py @@ -37,10 +37,17 @@ except ImportError: """) try: - from asyncio import selectors + # Python 3.4+ + import selectors except ImportError: - from gunicorn import selectors - + # Python 2 + try: + import selectors34 as selectors + except ImportError: + raise RuntimeError( + "You need to install the 'selectors34' package to use this worker " + "with this Python version." + ) class TConn(object): diff --git a/setup.py b/setup.py index 325f6a2e..639435c5 100644 --- a/setup.py +++ b/setup.py @@ -24,8 +24,6 @@ CLASSIFIERS = [ 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', @@ -93,7 +91,7 @@ setup( license='MIT', url='http://gunicorn.org', - python_requires='>=2.6, !=3.0.*, !=3.1.*', + python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=CLASSIFIERS, zip_safe=False, packages=find_packages(exclude=['examples', 'tests']), diff --git a/tests/test_selectors.py b/tests/test_selectors.py deleted file mode 100644 index 8018fc98..00000000 --- a/tests/test_selectors.py +++ /dev/null @@ -1,441 +0,0 @@ -# The following code adapted from CPython (see Lib/test/test_selectors.py) - -import errno -import random -import signal -import socket -from time import sleep -try: - import unittest2 as unittest -except ImportError: - import unittest -try: - import unittest.mock as mock -except ImportError: - import mock -try: - from time import monotonic as time -except ImportError: - from time import time -try: - import resource -except ImportError: - resource = None - -from gunicorn import selectors -import support - - -if hasattr(socket, 'socketpair'): - socketpair = socket.socketpair -else: - def socketpair(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0): - with socket.socket(family, type, proto) as l: - l.bind((support.HOST, 0)) - l.listen() - c = socket.socket(family, type, proto) - try: - c.connect(l.getsockname()) - caddr = c.getsockname() - while True: - a, addr = l.accept() - # check that we've got the correct client - if addr == caddr: - return c, a - a.close() - except OSError: - c.close() - raise - - -def find_ready_matching(ready, flag): - match = [] - for key, events in ready: - if events & flag: - match.append(key.fileobj) - return match - - -class BaseSelectorTestCase(object): - - def make_socketpair(self): - rd, wr = socketpair() - self.addCleanup(rd.close) - self.addCleanup(wr.close) - return rd, wr - - def test_register(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - rd, wr = self.make_socketpair() - - key = s.register(rd, selectors.EVENT_READ, "data") - self.assertIsInstance(key, selectors.SelectorKey) - self.assertEqual(key.fileobj, rd) - self.assertEqual(key.fd, rd.fileno()) - self.assertEqual(key.events, selectors.EVENT_READ) - self.assertEqual(key.data, "data") - - # register an unknown event - self.assertRaises(ValueError, s.register, 0, 999999) - - # register an invalid FD - self.assertRaises(ValueError, s.register, -10, selectors.EVENT_READ) - - # register twice - self.assertRaises(KeyError, s.register, rd, selectors.EVENT_READ) - - # register the same FD, but with a different object - self.assertRaises(KeyError, s.register, rd.fileno(), - selectors.EVENT_READ) - - def test_unregister(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - rd, wr = self.make_socketpair() - - s.register(rd, selectors.EVENT_READ) - s.unregister(rd) - - # unregister an unknown file obj - self.assertRaises(KeyError, s.unregister, 999999) - - # unregister twice - self.assertRaises(KeyError, s.unregister, rd) - - def test_unregister_after_fd_close(self): - s = self.SELECTOR() - self.addCleanup(s.close) - rd, wr = self.make_socketpair() - r, w = rd.fileno(), wr.fileno() - s.register(r, selectors.EVENT_READ) - s.register(w, selectors.EVENT_WRITE) - rd.close() - wr.close() - s.unregister(r) - s.unregister(w) - - def test_unregister_after_socket_close(self): - s = self.SELECTOR() - self.addCleanup(s.close) - rd, wr = self.make_socketpair() - s.register(rd, selectors.EVENT_READ) - s.register(wr, selectors.EVENT_WRITE) - rd.close() - wr.close() - s.unregister(rd) - s.unregister(wr) - - def test_modify(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - rd, wr = self.make_socketpair() - - key = s.register(rd, selectors.EVENT_READ) - - # modify events - key2 = s.modify(rd, selectors.EVENT_WRITE) - self.assertNotEqual(key.events, key2.events) - self.assertEqual(key2, s.get_key(rd)) - - s.unregister(rd) - - # modify data - d1 = object() - d2 = object() - - key = s.register(rd, selectors.EVENT_READ, d1) - key2 = s.modify(rd, selectors.EVENT_READ, d2) - self.assertEqual(key.events, key2.events) - self.assertNotEqual(key.data, key2.data) - self.assertEqual(key2, s.get_key(rd)) - self.assertEqual(key2.data, d2) - - # modify unknown file obj - self.assertRaises(KeyError, s.modify, 999999, selectors.EVENT_READ) - - # modify use a shortcut - d3 = object() - s.register = mock.Mock() - s.unregister = mock.Mock() - - s.modify(rd, selectors.EVENT_READ, d3) - self.assertFalse(s.register.called) - self.assertFalse(s.unregister.called) - - def test_close(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - rd, wr = self.make_socketpair() - - s.register(rd, selectors.EVENT_READ) - s.register(wr, selectors.EVENT_WRITE) - - s.close() - self.assertRaises(KeyError, s.get_key, rd) - self.assertRaises(KeyError, s.get_key, wr) - - def test_get_key(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - rd, wr = self.make_socketpair() - - key = s.register(rd, selectors.EVENT_READ, "data") - self.assertEqual(key, s.get_key(rd)) - - # unknown file obj - self.assertRaises(KeyError, s.get_key, 999999) - - def test_get_map(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - rd, wr = self.make_socketpair() - - keys = s.get_map() - self.assertFalse(keys) - self.assertEqual(len(keys), 0) - self.assertEqual(list(keys), []) - key = s.register(rd, selectors.EVENT_READ, "data") - self.assertIn(rd, keys) - self.assertEqual(key, keys[rd]) - self.assertEqual(len(keys), 1) - self.assertEqual(list(keys), [rd.fileno()]) - self.assertEqual(list(keys.values()), [key]) - - # unknown file obj - with self.assertRaises(KeyError): - keys[999999] - - # Read-only mapping - with self.assertRaises(TypeError): - del keys[rd] - - def test_select(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - rd, wr = self.make_socketpair() - - s.register(rd, selectors.EVENT_READ) - wr_key = s.register(wr, selectors.EVENT_WRITE) - - result = s.select() - for key, events in result: - self.assertTrue(isinstance(key, selectors.SelectorKey)) - self.assertTrue(events) - self.assertFalse(events & ~(selectors.EVENT_READ | - selectors.EVENT_WRITE)) - - self.assertEqual([(wr_key, selectors.EVENT_WRITE)], result) - - def test_context_manager(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - rd, wr = self.make_socketpair() - - with s as sel: - sel.register(rd, selectors.EVENT_READ) - sel.register(wr, selectors.EVENT_WRITE) - - self.assertRaises(KeyError, s.get_key, rd) - self.assertRaises(KeyError, s.get_key, wr) - - def test_fileno(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - if hasattr(s, 'fileno'): - fd = s.fileno() - self.assertTrue(isinstance(fd, int)) - self.assertGreaterEqual(fd, 0) - - def test_selector(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - NUM_SOCKETS = 12 - MSG = b" This is a test." - MSG_LEN = len(MSG) - readers = [] - writers = [] - r2w = {} - w2r = {} - - for i in range(NUM_SOCKETS): - rd, wr = self.make_socketpair() - s.register(rd, selectors.EVENT_READ) - s.register(wr, selectors.EVENT_WRITE) - readers.append(rd) - writers.append(wr) - r2w[rd] = wr - w2r[wr] = rd - - bufs = [] - - while writers: - ready = s.select() - ready_writers = find_ready_matching(ready, selectors.EVENT_WRITE) - if not ready_writers: - self.fail("no sockets ready for writing") - wr = random.choice(ready_writers) - wr.send(MSG) - - for i in range(10): - ready = s.select() - ready_readers = find_ready_matching(ready, - selectors.EVENT_READ) - if ready_readers: - break - # there might be a delay between the write to the write end and - # the read end is reported ready - sleep(0.1) - else: - self.fail("no sockets ready for reading") - self.assertEqual([w2r[wr]], ready_readers) - rd = ready_readers[0] - buf = rd.recv(MSG_LEN) - self.assertEqual(len(buf), MSG_LEN) - bufs.append(buf) - s.unregister(r2w[rd]) - s.unregister(rd) - writers.remove(r2w[rd]) - - self.assertEqual(bufs, [MSG] * NUM_SOCKETS) - - def test_timeout(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - rd, wr = self.make_socketpair() - - s.register(wr, selectors.EVENT_WRITE) - t = time() - self.assertEqual(1, len(s.select(0))) - self.assertEqual(1, len(s.select(-1))) - self.assertLess(time() - t, 0.5) - - s.unregister(wr) - s.register(rd, selectors.EVENT_READ) - t = time() - self.assertFalse(s.select(0)) - self.assertFalse(s.select(-1)) - self.assertLess(time() - t, 0.5) - - t0 = time() - self.assertFalse(s.select(1)) - t1 = time() - dt = t1 - t0 - self.assertTrue(0.8 <= dt <= 1.6, dt) - - @unittest.skipUnless(hasattr(signal, "alarm"), - "signal.alarm() required for this test") - def test_select_interrupt(self): - s = self.SELECTOR() - self.addCleanup(s.close) - - rd, wr = self.make_socketpair() - - orig_alrm_handler = signal.signal(signal.SIGALRM, lambda *args: None) - self.addCleanup(signal.signal, signal.SIGALRM, orig_alrm_handler) - self.addCleanup(signal.alarm, 0) - - signal.alarm(1) - - s.register(rd, selectors.EVENT_READ) - t = time() - self.assertFalse(s.select(2)) - self.assertLess(time() - t, 2.5) - - -class ScalableSelectorMixIn: - - # see issue #18963 for why it's skipped on older OS X versions - @support.requires_mac_ver(10, 5) - @unittest.skipUnless(resource, "Test needs resource module") - def test_above_fd_setsize(self): - # A scalable implementation should have no problem with more than - # FD_SETSIZE file descriptors. Since we don't know the value, we just - # try to set the soft RLIMIT_NOFILE to the hard RLIMIT_NOFILE ceiling. - soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) - try: - resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard)) - self.addCleanup(resource.setrlimit, resource.RLIMIT_NOFILE, - (soft, hard)) - NUM_FDS = min(hard, 2**16) - except (OSError, ValueError): - NUM_FDS = soft - - # guard for already allocated FDs (stdin, stdout...) - NUM_FDS -= 32 - - s = self.SELECTOR() - self.addCleanup(s.close) - - for i in range(NUM_FDS // 2): - try: - rd, wr = self.make_socketpair() - except (IOError, OSError): - # too many FDs, skip - note that we should only catch EMFILE - # here, but apparently *BSD and Solaris can fail upon connect() - # or bind() with EADDRNOTAVAIL, so let's be safe - self.skipTest("FD limit reached") - - try: - s.register(rd, selectors.EVENT_READ) - s.register(wr, selectors.EVENT_WRITE) - except OSError as e: - if e.errno == errno.ENOSPC: - # this can be raised by epoll if we go over - # fs.epoll.max_user_watches sysctl - self.skipTest("FD limit reached") - raise - - self.assertEqual(NUM_FDS // 2, len(s.select())) - - -class DefaultSelectorTestCase(BaseSelectorTestCase, unittest.TestCase): - - SELECTOR = selectors.DefaultSelector - - -class SelectSelectorTestCase(BaseSelectorTestCase, unittest.TestCase): - - SELECTOR = selectors.SelectSelector - - -@unittest.skipUnless(hasattr(selectors, 'PollSelector'), - "Test needs selectors.PollSelector") -class PollSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn, unittest.TestCase): - - SELECTOR = getattr(selectors, 'PollSelector', None) - - -@unittest.skipUnless(hasattr(selectors, 'EpollSelector'), - "Test needs selectors.EpollSelector") -class EpollSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn, unittest.TestCase): - - SELECTOR = getattr(selectors, 'EpollSelector', None) - - -@unittest.skipUnless(hasattr(selectors, 'KqueueSelector'), - "Test needs selectors.KqueueSelector)") -class KqueueSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn, unittest.TestCase): - - SELECTOR = getattr(selectors, 'KqueueSelector', None) - - -@unittest.skipUnless(hasattr(selectors, 'DevpollSelector'), - "Test needs selectors.DevpollSelector") -class DevpollSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn, unittest.TestCase): - - SELECTOR = getattr(selectors, 'DevpollSelector', None) - -if __name__ == "__main__": - unittest.main()