diff --git a/docs/source/design.rst b/docs/source/design.rst index 88180a82..85157666 100644 --- a/docs/source/design.rst +++ b/docs/source/design.rst @@ -59,7 +59,7 @@ WSGI application, this is not a recommended configuration. AsyncIO Workers --------------- -These workers are compatible with python3. You have two kind of workers. +These workers are compatible with Python 3. The worker `gthread` is a threaded worker. It accepts connections in the main loop, accepted connections are added to the thread pool as a @@ -67,24 +67,8 @@ connection job. On keepalive connections are put back in the loop waiting for an event. If no event happen after the keep alive timeout, the connection is closed. -The worker `gaiohttp` is a full asyncio worker using aiohttp_. - -.. note:: - The ``gaiohttp`` worker requires the aiohttp_ module to be installed. - aiohttp_ has removed its native WSGI application support in version 2. - If you want to continue to use the ``gaiohttp`` worker with your WSGI - application (e.g. an application that uses Flask or Django), there are - three options available: - - #. Install aiohttp_ version 1.3.5 instead of version 2:: - - $ pip install aiohttp==1.3.5 - - #. Use aiohttp_wsgi_ to wrap your WSGI application. You can take a look - at the `example`_ in the Gunicorn repository. - #. Port your application to use aiohttp_'s ``web.Application`` API. - #. Use the ``aiohttp.worker.GunicornWebWorker`` worker instead of the - deprecated ``gaiohttp`` worker. +You can port also your application to use aiohttp_'s `web.Application`` API and use the +``aiohttp.worker.GunicornWebWorker`` worker. Choosing a Worker Type ====================== @@ -150,13 +134,12 @@ the worker processes (unlike when using the preload setting, which loads the code in the master process). .. note:: - Under Python 2.x, you need to install the 'futures' package to use this + Under Python 2.x, you need to install the 'futures' package to use this feature. .. _Greenlets: https://github.com/python-greenlet/greenlet .. _Eventlet: http://eventlet.net/ .. _Gevent: http://www.gevent.org/ .. _Hey: https://github.com/rakyll/hey -.. _aiohttp: https://aiohttp.readthedocs.io/en/stable/ -.. _aiohttp_wsgi: https://aiohttp-wsgi.readthedocs.io/en/stable/index.html +.. _aiohttp: https://docs.aiohttp.org/en/stable/deployment.html#nginx-gunicorn .. _`example`: https://github.com/benoitc/gunicorn/blob/master/examples/frameworks/flaskapp_aiohttp_wsgi.py diff --git a/docs/source/run.rst b/docs/source/run.rst index d0799fa0..070f6ea5 100644 --- a/docs/source/run.rst +++ b/docs/source/run.rst @@ -61,7 +61,7 @@ Commonly Used Arguments to run. You'll definitely want to read the production page for the implications of this parameter. You can set this to ``$(NAME)`` where ``$(NAME)`` is one of ``sync``, ``eventlet``, ``gevent``, - ``tornado``, ``gthread``, ``gaiohttp`` (deprecated). + ``tornado``, ``gthread``. ``sync`` is the default. See the :ref:`worker-class` documentation for more information. * ``-n APP_NAME, --name=APP_NAME`` - If setproctitle_ is installed you can diff --git a/gunicorn/config.py b/gunicorn/config.py index e460e627..29a42f23 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -630,25 +630,19 @@ class WorkerClass(Setting): A string referring to one of the following bundled classes: * ``sync`` - * ``eventlet`` - Requires eventlet >= 0.9.7 (or install it via + * ``eventlet`` - Requires eventlet >= 0.9.7 (or install it via ``pip install gunicorn[eventlet]``) - * ``gevent`` - Requires gevent >= 0.13 (or install it via + * ``gevent`` - Requires gevent >= 0.13 (or install it via ``pip install gunicorn[gevent]``) - * ``tornado`` - Requires tornado >= 0.2 (or install it via + * ``tornado`` - Requires tornado >= 0.2 (or install it via ``pip install gunicorn[tornado]``) * ``gthread`` - Python 2 requires the futures package to be installed (or install it via ``pip install gunicorn[gthread]``) - * ``gaiohttp`` - Deprecated. Optionally, you can provide your own worker by giving Gunicorn a Python path to a subclass of ``gunicorn.workers.base.Worker``. This alternative syntax will load the gevent class: ``gunicorn.workers.ggevent.GeventWorker``. - - .. deprecated:: 19.8 - The ``gaiohttp`` worker is deprecated. Please use - ``aiohttp.worker.GunicornWebWorker`` instead. See - :ref:`asyncio-workers` for more information on how to use it. """ class WorkerThreads(Setting): @@ -671,7 +665,7 @@ class WorkerThreads(Setting): If it is not defined, the default is ``1``. This setting only affects the Gthread worker type. - + .. note:: If you try to use the ``sync`` worker type and set the ``threads`` setting to more than 1, the ``gthread`` worker type will be used diff --git a/gunicorn/workers/__init__.py b/gunicorn/workers/__init__.py index 29c04c2a..ae753e1c 100644 --- a/gunicorn/workers/__init__.py +++ b/gunicorn/workers/__init__.py @@ -7,7 +7,6 @@ SUPPORTED_WORKERS = { "sync": "gunicorn.workers.sync.SyncWorker", "eventlet": "gunicorn.workers.geventlet.EventletWorker", - "gaiohttp": "gunicorn.workers.gaiohttp.AiohttpWorker", "gevent": "gunicorn.workers.ggevent.GeventWorker", "gevent_wsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker", "gevent_pywsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker", diff --git a/gunicorn/workers/_gaiohttp.py b/gunicorn/workers/_gaiohttp.py deleted file mode 100644 index fe378c35..00000000 --- a/gunicorn/workers/_gaiohttp.py +++ /dev/null @@ -1,168 +0,0 @@ -# -*- coding: utf-8 - -# -# This file is part of gunicorn released under the MIT license. -# See the NOTICE for more information. - -import asyncio -import datetime -import functools -import logging -import os - -try: - import ssl -except ImportError: - ssl = None - -import gunicorn.workers.base as base - -from aiohttp.wsgi import WSGIServerHttpProtocol as OldWSGIServerHttpProtocol - - -class WSGIServerHttpProtocol(OldWSGIServerHttpProtocol): - def log_access(self, request, environ, response, time): - self.logger.access(response, request, environ, datetime.timedelta(0, 0, time)) - - -class AiohttpWorker(base.Worker): - - def __init__(self, *args, **kw): # pragma: no cover - super().__init__(*args, **kw) - cfg = self.cfg - if cfg.is_ssl: - self.ssl_context = self._create_ssl_context(cfg) - else: - self.ssl_context = None - self.servers = [] - self.connections = {} - - def init_process(self): - # create new event_loop after fork - asyncio.get_event_loop().close() - - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) - - super().init_process() - - def run(self): - self._runner = asyncio.ensure_future(self._run(), loop=self.loop) - - try: - self.loop.run_until_complete(self._runner) - finally: - self.loop.close() - - def wrap_protocol(self, proto): - proto.connection_made = _wrp( - proto, proto.connection_made, self.connections) - proto.connection_lost = _wrp( - proto, proto.connection_lost, self.connections, False) - return proto - - def factory(self, wsgi, addr): - # are we in debug level - is_debug = self.log.loglevel == logging.DEBUG - - proto = WSGIServerHttpProtocol( - wsgi, readpayload=True, - loop=self.loop, - log=self.log, - debug=is_debug, - keep_alive=self.cfg.keepalive, - access_log=self.log.access_log, - access_log_format=self.cfg.access_log_format) - return self.wrap_protocol(proto) - - def get_factory(self, sock, addr): - return functools.partial(self.factory, self.wsgi, addr) - - @asyncio.coroutine - def close(self): - try: - if hasattr(self.wsgi, 'close'): - yield from self.wsgi.close() - except: - self.log.exception('Process shutdown exception') - - @asyncio.coroutine - def _run(self): - for sock in self.sockets: - factory = self.get_factory(sock.sock, sock.cfg_addr) - self.servers.append( - (yield from self._create_server(factory, sock))) - - # If our parent changed then we shut down. - pid = os.getpid() - try: - while self.alive or self.connections: - self.notify() - - if (self.alive and - pid == os.getpid() and self.ppid != os.getppid()): - self.log.info("Parent changed, shutting down: %s", self) - self.alive = False - - # stop accepting requests - if not self.alive: - if self.servers: - self.log.info( - "Stopping server: %s, connections: %s", - pid, len(self.connections)) - for server in self.servers: - server.close() - self.servers.clear() - - # prepare connections for closing - for conn in self.connections.values(): - if hasattr(conn, 'closing'): - conn.closing() - - yield from asyncio.sleep(1.0, loop=self.loop) - except KeyboardInterrupt: - pass - - if self.servers: - for server in self.servers: - server.close() - - yield from self.close() - - @asyncio.coroutine - def _create_server(self, factory, sock): - return self.loop.create_server(factory, sock=sock.sock, - ssl=self.ssl_context) - - @staticmethod - def _create_ssl_context(cfg): - """ Creates SSLContext instance for usage in asyncio.create_server. - - See ssl.SSLSocket.__init__ for more details. - """ - ctx = ssl.SSLContext(cfg.ssl_version) - ctx.load_cert_chain(cfg.certfile, cfg.keyfile) - ctx.verify_mode = cfg.cert_reqs - if cfg.ca_certs: - ctx.load_verify_locations(cfg.ca_certs) - if cfg.ciphers: - ctx.set_ciphers(cfg.ciphers) - return ctx - - -class _wrp: - - def __init__(self, proto, meth, tracking, add=True): - self._proto = proto - self._id = id(proto) - self._meth = meth - self._tracking = tracking - self._add = add - - def __call__(self, *args): - if self._add: - self._tracking[self._id] = self._proto - elif self._id in self._tracking: - del self._tracking[self._id] - - conn = self._meth(*args) - return conn diff --git a/gunicorn/workers/gaiohttp.py b/gunicorn/workers/gaiohttp.py deleted file mode 100644 index b8248259..00000000 --- a/gunicorn/workers/gaiohttp.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 - -# -# This file is part of gunicorn released under the MIT license. -# See the NOTICE for more information. - -from gunicorn import util - -try: - import aiohttp # pylint: disable=unused-import -except ImportError: - raise RuntimeError("You need aiohttp installed to use this worker.") -else: - try: - from aiohttp.worker import GunicornWebWorker as AiohttpWorker - except ImportError: - from gunicorn.workers._gaiohttp import AiohttpWorker - - util.warn( - "The 'gaiohttp' worker is deprecated. See --worker-class " - "documentation for more information." - ) - __all__ = ['AiohttpWorker'] diff --git a/tests/test_gaiohttp.py b/tests/test_gaiohttp.py deleted file mode 100644 index e58f36c1..00000000 --- a/tests/test_gaiohttp.py +++ /dev/null @@ -1,193 +0,0 @@ -# -*- coding: utf-8 - -# -# This file is part of gunicorn released under the MIT license. -# See the NOTICE for more information. - -import unittest -import pytest - -aiohttp = pytest.importorskip("aiohttp") -WSGIServerHttpProtocol = pytest.importorskip("aiohttp.wsgi.WSGIServerHttpProtocol") - -import asyncio -from gunicorn.workers import gaiohttp -from gunicorn.workers._gaiohttp import _wrp -from gunicorn.config import Config -from unittest import mock - - -class WorkerTests(unittest.TestCase): - - def setUp(self): - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) - self.worker = gaiohttp.AiohttpWorker('age', - 'ppid', - 'sockets', - 'app', - 'timeout', - Config(), - 'log') - - def tearDown(self): - self.loop.close() - - @mock.patch('gunicorn.workers._gaiohttp.asyncio') - def test_init_process(self, m_asyncio): - try: - self.worker.init_process() - except TypeError: - # to mask incomplete initialization of AiohttWorker instance: - # we pass invalid values for ctor args - pass - - self.assertTrue(m_asyncio.get_event_loop.return_value.close.called) - self.assertTrue(m_asyncio.new_event_loop.called) - self.assertTrue(m_asyncio.set_event_loop.called) - - @mock.patch('gunicorn.workers._gaiohttp.asyncio') - def test_run(self, m_asyncio): - self.worker.loop = mock.Mock() - self.worker.run() - - self.assertTrue(m_asyncio.ensure_future.called) - self.assertTrue(self.worker.loop.run_until_complete.called) - self.assertTrue(self.worker.loop.close.called) - - def test_factory(self): - self.worker.wsgi = mock.Mock() - self.worker.loop = mock.Mock() - self.worker.log = mock.Mock() - self.worker.cfg = Config() - - f = self.worker.factory( - self.worker.wsgi, ('localhost', 8080)) - self.assertIsInstance(f, WSGIServerHttpProtocol) - - @mock.patch('gunicorn.workers._gaiohttp.asyncio') - def test__run(self, m_asyncio): - self.worker.ppid = 1 - self.worker.alive = True - self.worker.servers = [] - sock = mock.Mock() - sock.cfg_addr = ('localhost', 8080) - self.worker.sockets = [sock] - self.worker.wsgi = mock.Mock() - self.worker.log = mock.Mock() - self.worker.notify = mock.Mock() - loop = self.worker.loop = mock.Mock() - loop.create_server.return_value = asyncio.Future(loop=self.loop) - loop.create_server.return_value.set_result(sock) - - self.loop.run_until_complete(self.worker._run()) - - self.assertTrue(self.worker.log.info.called) - self.assertTrue(self.worker.notify.called) - - @mock.patch('gunicorn.workers._gaiohttp.asyncio') - def test__run_unix_socket(self, m_asyncio): - self.worker.ppid = 1 - self.worker.alive = True - self.worker.servers = [] - sock = mock.Mock() - sock.cfg_addr = '/tmp/gunicorn.sock' - self.worker.sockets = [sock] - self.worker.wsgi = mock.Mock() - self.worker.log = mock.Mock() - self.worker.notify = mock.Mock() - loop = self.worker.loop = mock.Mock() - loop.create_server.return_value = asyncio.Future(loop=self.loop) - loop.create_server.return_value.set_result(sock) - - self.loop.run_until_complete(self.worker._run()) - - self.assertTrue(self.worker.log.info.called) - self.assertTrue(self.worker.notify.called) - - def test__run_connections(self): - conn = mock.Mock() - self.worker.ppid = 1 - self.worker.alive = False - self.worker.servers = [mock.Mock()] - self.worker.connections = {1: conn} - self.worker.sockets = [] - self.worker.wsgi = mock.Mock() - self.worker.log = mock.Mock() - self.worker.loop = self.loop - self.worker.loop.create_server = mock.Mock() - self.worker.notify = mock.Mock() - - def _close_conns(): - self.worker.connections = {} - - self.loop.call_later(0.1, _close_conns) - self.loop.run_until_complete(self.worker._run()) - - self.assertTrue(self.worker.log.info.called) - self.assertTrue(self.worker.notify.called) - self.assertFalse(self.worker.servers) - self.assertTrue(conn.closing.called) - - @mock.patch('gunicorn.workers._gaiohttp.os') - @mock.patch('gunicorn.workers._gaiohttp.asyncio.sleep') - def test__run_exc(self, m_sleep, m_os): - m_os.getpid.return_value = 1 - m_os.getppid.return_value = 1 - - self.worker.servers = [mock.Mock()] - self.worker.ppid = 1 - self.worker.alive = True - self.worker.sockets = [] - self.worker.log = mock.Mock() - self.worker.loop = mock.Mock() - self.worker.notify = mock.Mock() - - slp = asyncio.Future(loop=self.loop) - slp.set_exception(KeyboardInterrupt) - m_sleep.return_value = slp - - self.loop.run_until_complete(self.worker._run()) - self.assertTrue(m_sleep.called) - self.assertTrue(self.worker.servers[0].close.called) - - def test_close_wsgi_app(self): - self.worker.ppid = 1 - self.worker.alive = False - self.worker.servers = [mock.Mock()] - self.worker.connections = {} - self.worker.sockets = [] - self.worker.log = mock.Mock() - self.worker.loop = self.loop - self.worker.loop.create_server = mock.Mock() - self.worker.notify = mock.Mock() - - self.worker.wsgi = mock.Mock() - self.worker.wsgi.close.return_value = asyncio.Future(loop=self.loop) - self.worker.wsgi.close.return_value.set_result(1) - - self.loop.run_until_complete(self.worker._run()) - self.assertTrue(self.worker.wsgi.close.called) - - self.worker.wsgi = mock.Mock() - self.worker.wsgi.close.return_value = asyncio.Future(loop=self.loop) - self.worker.wsgi.close.return_value.set_exception(ValueError()) - - self.loop.run_until_complete(self.worker._run()) - self.assertTrue(self.worker.wsgi.close.called) - - def test_wrp(self): - conn = object() - tracking = {} - meth = mock.Mock() - wrp = _wrp(conn, meth, tracking) - wrp() - - self.assertIn(id(conn), tracking) - self.assertTrue(meth.called) - - meth = mock.Mock() - wrp = _wrp(conn, meth, tracking, False) - wrp() - - self.assertNotIn(1, tracking) - self.assertTrue(meth.called) diff --git a/tox.ini b/tox.ini index d2868ddb..47249d6e 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,6 @@ commands = gunicorn \ tests/test_arbiter.py \ tests/test_config.py \ - tests/test_gaiohttp.py \ tests/test_http.py \ tests/test_invalid_requests.py \ tests/test_logger.py \