From ac5664d2281533eacafd64f5cc7d5edcdaccab60 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 25 Jan 2026 10:31:31 -0800 Subject: [PATCH] document using gevent for async --- docs/async-await.rst | 28 ++++---- docs/deploying/eventlet.rst | 80 ++--------------------- docs/deploying/gevent.rst | 14 ++-- docs/deploying/gunicorn.rst | 36 ++++------- docs/deploying/index.rst | 1 - docs/deploying/uwsgi.rst | 20 +++--- docs/gevent.rst | 125 ++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + docs/installation.rst | 5 +- 9 files changed, 168 insertions(+), 142 deletions(-) create mode 100644 docs/gevent.rst diff --git a/docs/async-await.rst b/docs/async-await.rst index 16b61945e2..bb3338029a 100644 --- a/docs/async-await.rst +++ b/docs/async-await.rst @@ -23,12 +23,6 @@ method in views that inherit from the :class:`flask.views.View` class, as well as all the HTTP method handlers in views that inherit from the :class:`flask.views.MethodView` class. -.. admonition:: Using ``async`` with greenlet - - When using gevent or eventlet to serve an application or patch the - runtime, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is - required. - Performance ----------- @@ -78,15 +72,15 @@ Flask based on the `ASGI`_ standard instead of WSGI. This allows it to handle many concurrent requests, long running requests, and websockets without requiring multiple worker processes or threads. -It has also already been possible to run Flask with Gevent or Eventlet -to get many of the benefits of async request handling. These libraries -patch low-level Python functions to accomplish this, whereas ``async``/ -``await`` and ASGI use standard, modern Python capabilities. Deciding -whether you should use Flask, Quart, or something else is ultimately up -to understanding the specific needs of your project. +It has also already been possible to :doc:`run Flask with Gevent ` to +get many of the benefits of async request handling. Gevent patches low-level +Python functions to accomplish this, whereas ``async``/``await`` and ASGI use +standard, modern Python capabilities. Deciding whether you should use gevent +with Flask, or Quart, or something else is ultimately up to understanding the +specific needs of your project. -.. _Quart: https://github.com/pallets/quart -.. _ASGI: https://asgi.readthedocs.io/en/latest/ +.. _Quart: https://quart.palletsprojects.com +.. _ASGI: https://asgi.readthedocs.io Extensions @@ -120,6 +114,6 @@ implemented async support, or make a feature request or PR to them. Other event loops ----------------- -At the moment Flask only supports :mod:`asyncio`. It's possible to -override :meth:`flask.Flask.ensure_sync` to change how async functions -are wrapped to use a different library. +At the moment Flask only supports :mod:`asyncio`. It's possible to override +:meth:`flask.Flask.ensure_sync` to change how async functions are wrapped to use +a different library. See :ref:`gevent-asyncio` for an example. diff --git a/docs/deploying/eventlet.rst b/docs/deploying/eventlet.rst index 8a718b22f7..d4c9e81786 100644 --- a/docs/deploying/eventlet.rst +++ b/docs/deploying/eventlet.rst @@ -1,80 +1,8 @@ +:orphan: + eventlet ======== -Prefer using :doc:`gunicorn` with eventlet workers rather than using -`eventlet`_ directly. Gunicorn provides a much more configurable and -production-tested server. - -`eventlet`_ allows writing asynchronous, coroutine-based code that looks -like standard synchronous Python. It uses `greenlet`_ to enable task -switching without writing ``async/await`` or using ``asyncio``. - -:doc:`gevent` is another library that does the same thing. Certain -dependencies you have, or other considerations, may affect which of the -two you choose to use. - -eventlet provides a WSGI server that can handle many connections at once -instead of one per worker process. You must actually use eventlet in -your own code to see any benefit to using the server. - -.. _eventlet: https://eventlet.net/ -.. _greenlet: https://greenlet.readthedocs.io/en/latest/ - - -Installing ----------- - -When using eventlet, greenlet>=1.0 is required, otherwise context locals -such as ``request`` will not work as expected. When using PyPy, -PyPy>=7.3.7 is required. - -Create a virtualenv, install your application, then install -``eventlet``. - -.. code-block:: text - - $ cd hello-app - $ python -m venv .venv - $ . .venv/bin/activate - $ pip install . # install your application - $ pip install eventlet - - -Running -------- - -To use eventlet to serve your application, write a script that imports -its ``wsgi.server``, as well as your app or app factory. - -.. code-block:: python - :caption: ``wsgi.py`` - - import eventlet - from eventlet import wsgi - from hello import create_app - - app = create_app() - wsgi.server(eventlet.listen(("127.0.0.1", 8000)), app) - -.. code-block:: text - - $ python wsgi.py - (x) wsgi starting up on http://127.0.0.1:8000 - - -Binding Externally ------------------- - -eventlet should not be run as root because it would cause your -application code to run as root, which is not secure. However, this -means it will not be possible to bind to port 80 or 443. Instead, a -reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used -in front of eventlet. - -You can bind to all external IPs on a non-privileged port by using -``0.0.0.0`` in the server arguments shown in the previous section. -Don't do this when using a reverse proxy setup, otherwise it will be -possible to bypass the proxy. +`Eventlet is no longer maintained.`__ Use :doc:`/deploying/gevent` instead. -``0.0.0.0`` is not a valid address to navigate to, you'd use a specific -IP address in your browser. +__ https://eventlet.readthedocs.io diff --git a/docs/deploying/gevent.rst b/docs/deploying/gevent.rst index 448b93e78c..8810d21cd3 100644 --- a/docs/deploying/gevent.rst +++ b/docs/deploying/gevent.rst @@ -7,15 +7,12 @@ configurable and production-tested servers. `gevent`_ allows writing asynchronous, coroutine-based code that looks like standard synchronous Python. It uses `greenlet`_ to enable task -switching without writing ``async/await`` or using ``asyncio``. - -:doc:`eventlet` is another library that does the same thing. Certain -dependencies you have, or other considerations, may affect which of the -two you choose to use. +switching without writing ``async/await`` or using ``asyncio``. This is +not the same as Python's ``async/await``, or the ASGI server spec. gevent provides a WSGI server that can handle many connections at once -instead of one per worker process. You must actually use gevent in your -own code to see any benefit to using the server. +instead of one per worker process. See :doc:`/gevent` for more +information about enabling it in your application. .. _gevent: https://www.gevent.org/ .. _greenlet: https://greenlet.readthedocs.io/en/latest/ @@ -24,8 +21,7 @@ own code to see any benefit to using the server. Installing ---------- -When using gevent, greenlet>=1.0 is required, otherwise context locals -such as ``request`` will not work as expected. When using PyPy, +When using gevent, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is required. Create a virtualenv, install your application, then install ``gevent``. diff --git a/docs/deploying/gunicorn.rst b/docs/deploying/gunicorn.rst index c50edc2326..089cb914c1 100644 --- a/docs/deploying/gunicorn.rst +++ b/docs/deploying/gunicorn.rst @@ -8,7 +8,7 @@ multiple worker implementations for performance tuning. * It does not support Windows (but does run on WSL). * It is easy to install as it does not require additional dependencies or compilation. -* It has built-in async worker support using gevent or eventlet. +* It has built-in async worker support using gevent. This page outlines the basics of running Gunicorn. Be sure to read its `documentation`_ and use ``gunicorn --help`` to understand what features @@ -93,20 +93,19 @@ otherwise it will be possible to bypass the proxy. IP address in your browser. -Async with gevent or eventlet ------------------------------ +Async with gevent +----------------- -The default sync worker is appropriate for many use cases. If you need -asynchronous support, Gunicorn provides workers using either `gevent`_ -or `eventlet`_. This is not the same as Python's ``async/await``, or the -ASGI server spec. You must actually use gevent/eventlet in your own code -to see any benefit to using the workers. +The default sync worker is appropriate for most use cases. If you need numerous, +long running, concurrent connections, Gunicorn provides an asynchronous worker +using `gevent`_. This is not the same as Python's ``async/await``, or the ASGI +server spec. See :doc:`/gevent` for more information about enabling it in your +application. -When using either gevent or eventlet, greenlet>=1.0 is required, -otherwise context locals such as ``request`` will not work as expected. -When using PyPy, PyPy>=7.3.7 is required. +.. _gevent: https://www.gevent.org/ -To use gevent: +When using gevent, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is +required. .. code-block:: text @@ -115,16 +114,3 @@ To use gevent: Listening at: http://127.0.0.1:8000 (x) Using worker: gevent Booting worker with pid: x - -To use eventlet: - -.. code-block:: text - - $ gunicorn -k eventlet 'hello:create_app()' - Starting gunicorn 20.1.0 - Listening at: http://127.0.0.1:8000 (x) - Using worker: eventlet - Booting worker with pid: x - -.. _gevent: https://www.gevent.org/ -.. _eventlet: https://eventlet.net/ diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 4135596a4f..dcef8abe08 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -36,7 +36,6 @@ discusses platforms that can manage this for you. mod_wsgi uwsgi gevent - eventlet asgi WSGI servers have HTTP servers built-in. However, a dedicated HTTP diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index 1f9d5eca00..aa80b6fb1a 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -119,15 +119,16 @@ IP address in your browser. Async with gevent ----------------- -The default sync worker is appropriate for many use cases. If you need -asynchronous support, uWSGI provides a `gevent`_ worker. This is not the -same as Python's ``async/await``, or the ASGI server spec. You must -actually use gevent in your own code to see any benefit to using the -worker. +The default sync worker is appropriate for most use cases. If you need numerous, +long running, concurrent connections, uWSGI provides an asynchronous worker +using `gevent`_. This is not the same as Python's ``async/await``, or the ASGI +server spec. See :doc:`/gevent` for more information about enabling it in your +application. -When using gevent, greenlet>=1.0 is required, otherwise context locals -such as ``request`` will not work as expected. When using PyPy, -PyPy>=7.3.7 is required. +.. _gevent: https://www.gevent.org/ + +When using gevent, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is +required. .. code-block:: text @@ -140,6 +141,3 @@ PyPy>=7.3.7 is required. spawned uWSGI worker 1 (pid: x, cores: 100) spawned uWSGI http 1 (pid: x) *** running gevent loop engine [addr:x] *** - - -.. _gevent: https://www.gevent.org/ diff --git a/docs/gevent.rst b/docs/gevent.rst new file mode 100644 index 0000000000..3ead05c5f6 --- /dev/null +++ b/docs/gevent.rst @@ -0,0 +1,125 @@ +Async with Gevent +================= + +`Gevent`_ patches Python's standard library to run within special async workers +called `greenlets`_. Gevent has existed since long before Python's native +asyncio was available, and Flask has always worked with it. + +.. _gevent: https://www.gevent.org +.. _greenlets: https://greenlet.readthedocs.io + +Gevent is a reliable way to handle numerous, long lived, concurrent connections, +and to achieve similar capabilities to ASGI and asyncio. This works without +needing to write ``async def`` or ``await`` anywhere, but relies on gevent and +greenlet's low level manipulation of the Python interpreter. + +Deciding whether you should use gevent with Flask, or `Quart`_, or something +else, is ultimately up to understanding the specific needs of your project. + +.. _quart: https://quart.palletsprojects.com + + +Enabling gevent +--------------- + +You need to apply gevent's patching as early as possible in your code. This +enables gevent's underlying event loop and converts many Python internals to run +inside it. Add the following at the top of your project's module or top +``__init__.py``: + +.. code-block:: python + + import gevent.monkey + gevent.monkey.patch_all() + +When deploying in production, use :doc:`/deploying/gunicorn` or +:doc:`/deploying/uwsgi` with a gevent worker, as described on those pages. + +To run concurrent tasks within your own code, such as views, use +|gevent.spawn|_: + +.. |gevent.spawn| replace:: ``gevent.spawn()`` +.. _gevent.spawn: https://www.gevent.org/api/gevent.html#gevent.spawn + +.. code-block:: python + + @app.post("/send") + def send_email(): + gevent.spawn(email.send, to="example@example.example", text="example") + return "Email is being sent." + +If you need to access :data:`request` or other Flask context globals within the +spawned function, decorate the function with :func:`.stream_with_context` or +:func:`.copy_current_request_context`. Prefer passing the exact data you need +when spawning the function, rather than using the decorators. + +.. note:: + + When using gevent, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 + is required. + + +.. _gevent-asyncio: + +Combining with ``async``/``await`` +---------------------------------- + +Gevent's patching does not interact well with Flask's built-in asyncio support. +If you want to use Gevent and asyncio in the same app, you'll need to override +:meth:`flask.Flask.async_to_sync` to run async functions inside gevent. + +.. code-block:: python + + import gevent.monkey + gevent.monkey.patch_all() + + import asyncio + from flask import Flask, request + + loop = asyncio.EventLoop() + gevent.spawn(loop.run_forever) + + class GeventFlask(Flask): + def async_to_sync(self, func): + def run(*args, **kwargs): + coro = func(*args, **kwargs) + future = asyncio.run_coroutine_threadsafe(coro, loop) + return future.result() + + return run + + app = GeventFlask(__name__) + + @app.get("/") + async def greet(): + await asyncio.sleep(1) + return f"Hello, {request.args.get("name", "World")}!" + +This starts an asyncio event loop in a gevent worker. Async functions are +scheduled on that event loop. This may still have limitations, and may need to +be modified further when using other asyncio implementations. + + +libuv +~~~~~ + +`libuv`_ is another event loop implementation that `gevent supports`_. There's +also a project called `uvloop`_ that enables libuv in asyncio. If you want to +use libuv, use gevent's support, not uvloop. It may be possible to further +modify the ``async_to_sync`` code from the previous section to work with uvloop, +but that's not currently known. + +.. _libuv: https://libuv.org/ +.. _gevent supports: https://www.gevent.org/loop_impls.html +.. _uvloop: https://uvloop.readthedocs.io/ + +To enable gevent's libuv support, add the following at the *very* top of your +code, before ``gevent.monkey.patch_all()``: + +.. code-block:: python + + import gevent + gevent.config.loop = "libuv" + + import gevent.monkey + gevent.monkey.patch_all() diff --git a/docs/index.rst b/docs/index.rst index 161085a72a..a428950fa3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -61,6 +61,7 @@ community-maintained extensions to add even more functionality. patterns/index web-security deploying/index + gevent async-await diff --git a/docs/installation.rst b/docs/installation.rst index 90a314bf14..4ea27f5e2f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -51,9 +51,8 @@ use them if you install them. greenlet ~~~~~~~~ -You may choose to use gevent or eventlet with your application. In this -case, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is -required. +You may choose to use :doc:`/gevent` with your application. In this case, +greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is required. These are not minimum supported versions, they only indicate the first versions that added necessary features. You should use the latest