mirror of
https://github.com/frappe/gunicorn.git
synced 2026-01-14 11:09:11 +08:00
remove gaiohttp worker (#1971)
* remove gaiohttp worker worker is deprecated and won't work on latest version.
This commit is contained in:
parent
2ea5fbdc86
commit
97a45805f8
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
@ -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']
|
||||
@ -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)
|
||||
Loading…
x
Reference in New Issue
Block a user