mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-02 18:51:31 +08:00
chore: remove eventlet worker; add h2 and uvloop to test deps
Eventlet was deprecated for 26.0 and is now removed: - Delete gunicorn/workers/geventlet.py and its registry entry - Drop eventlet from config help text, HTTP/2 unsupported-worker messages, and the dirty client docstring - Drop the eventlet optional-dependency, the eventlet entry in the testing extra, and the eventlet-only filterwarnings ignore - Drop the EventletWorkerAlpn test class - Drop the freebsd CI ignore for the (now non-existent) test_geventlet.py - Drop eventlet from the issue-triage discussion template - Drop eventlet from README, install/design/http2/settings/news docs; rewrite the news.md entry from 'deprecated' to 'removed in this release' Add h2 and uvloop to requirements_test.txt so a plain 'pip install -r requirements_test.txt' run reaches feature parity with 'pip install .[testing]' for those two deps. The container suite previously skipped 87 HTTP/2 tests for missing h2 and 1 for uvloop; the in-process suite skips drop from 67 to 40.
This commit is contained in:
parent
68843c8893
commit
201df19a80
1
.github/DISCUSSION_TEMPLATE/issue-triage.yml
vendored
1
.github/DISCUSSION_TEMPLATE/issue-triage.yml
vendored
@ -95,7 +95,6 @@ body:
|
|||||||
- sync (default)
|
- sync (default)
|
||||||
- gthread
|
- gthread
|
||||||
- gevent
|
- gevent
|
||||||
- eventlet
|
|
||||||
- tornado
|
- tornado
|
||||||
- asgi (beta)
|
- asgi (beta)
|
||||||
- custom
|
- custom
|
||||||
|
|||||||
3
.github/workflows/freebsd.yml
vendored
3
.github/workflows/freebsd.yml
vendored
@ -43,5 +43,4 @@ jobs:
|
|||||||
pip install pytest pytest-cov pytest-asyncio coverage
|
pip install pytest pytest-cov pytest-asyncio coverage
|
||||||
pip install -e .
|
pip install -e .
|
||||||
pytest --cov=gunicorn -v tests/ \
|
pytest --cov=gunicorn -v tests/ \
|
||||||
--ignore=tests/workers/test_ggevent.py \
|
--ignore=tests/workers/test_ggevent.py
|
||||||
--ignore=tests/workers/test_geventlet.py
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ gunicorn myapp:app --worker-class asgi
|
|||||||
- **HTTP/2 support** (beta) with multiplexed streams
|
- **HTTP/2 support** (beta) with multiplexed streams
|
||||||
- **Dirty Arbiters** (beta) for heavy workloads (ML models, long-running tasks)
|
- **Dirty Arbiters** (beta) for heavy workloads (ML models, long-running tasks)
|
||||||
- uWSGI binary protocol for nginx integration
|
- uWSGI binary protocol for nginx integration
|
||||||
- Multiple worker types: sync, gthread, gevent, eventlet, asgi
|
- Multiple worker types: sync, gthread, gevent, asgi
|
||||||
- Graceful worker process management
|
- Graceful worker process management
|
||||||
- Compatible with Python 3.9+
|
- Compatible with Python 3.9+
|
||||||
|
|
||||||
|
|||||||
@ -95,23 +95,6 @@ Choose a worker type based on your application's needs.
|
|||||||
gunicorn myapp:app -k gevent --worker-connections 1000
|
gunicorn myapp:app -k gevent --worker-connections 1000
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Eventlet (Deprecated)"
|
|
||||||
|
|
||||||
!!! warning "Deprecated"
|
|
||||||
The eventlet worker is **deprecated** and will be removed in Gunicorn 26.0.
|
|
||||||
Eventlet itself is [no longer actively maintained](https://eventlet.readthedocs.io/en/latest/asyncio/migration.html).
|
|
||||||
Please migrate to `gevent`, `gthread`, or another supported worker type.
|
|
||||||
|
|
||||||
**Greenlet-based** async worker using [Eventlet](http://eventlet.net/).
|
|
||||||
|
|
||||||
- Similar capabilities to Gevent
|
|
||||||
- Handles high concurrency for I/O-bound apps
|
|
||||||
- Some libraries may need compatibility patches
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gunicorn myapp:app -k eventlet --worker-connections 1000
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "Tornado"
|
=== "Tornado"
|
||||||
|
|
||||||
Worker for [Tornado](https://www.tornadoweb.org/) applications.
|
Worker for [Tornado](https://www.tornadoweb.org/) applications.
|
||||||
@ -132,7 +115,6 @@ Choose a worker type based on your application's needs.
|
|||||||
| `gthread` | Thread pool | ✅ | Mixed workloads, moderate concurrency |
|
| `gthread` | Thread pool | ✅ | Mixed workloads, moderate concurrency |
|
||||||
| ASGI workers | AsyncIO | ✅ | Modern async frameworks (FastAPI, etc.) |
|
| ASGI workers | AsyncIO | ✅ | Modern async frameworks (FastAPI, etc.) |
|
||||||
| `gevent` | Greenlets | ✅ | I/O-bound, WebSockets, streaming |
|
| `gevent` | Greenlets | ✅ | I/O-bound, WebSockets, streaming |
|
||||||
| `eventlet` | Greenlets | ✅ | **Deprecated** - use `gevent` instead |
|
|
||||||
| `tornado` | Tornado IOLoop | ✅ | Native Tornado applications |
|
| `tornado` | Tornado IOLoop | ✅ | Native Tornado applications |
|
||||||
|
|
||||||
!!! tip "Quick Decision Guide"
|
!!! tip "Quick Decision Guide"
|
||||||
|
|||||||
@ -109,7 +109,6 @@ Not all workers support HTTP/2:
|
|||||||
| `sync` | No | Single-threaded, cannot multiplex streams |
|
| `sync` | No | Single-threaded, cannot multiplex streams |
|
||||||
| `gthread` | Yes | Recommended for HTTP/2 |
|
| `gthread` | Yes | Recommended for HTTP/2 |
|
||||||
| `gevent` | Yes | Requires gevent |
|
| `gevent` | Yes | Requires gevent |
|
||||||
| `eventlet` | Yes | **Deprecated** - will be removed in 26.0 |
|
|
||||||
| `asgi` | Yes | For async frameworks |
|
| `asgi` | Yes | For async frameworks |
|
||||||
| `tornado` | No | Tornado handles its own protocol |
|
| `tornado` | No | Tornado handles its own protocol |
|
||||||
|
|
||||||
|
|||||||
@ -94,7 +94,6 @@ pip install gunicorn[gevent,setproctitle]
|
|||||||
| `gunicorn[gevent]` | Gevent-based greenlet workers |
|
| `gunicorn[gevent]` | Gevent-based greenlet workers |
|
||||||
| `gunicorn[gthread]` | Threaded workers |
|
| `gunicorn[gthread]` | Threaded workers |
|
||||||
| `gunicorn[tornado]` | Tornado-based workers (not recommended) |
|
| `gunicorn[tornado]` | Tornado-based workers (not recommended) |
|
||||||
| `gunicorn[eventlet]` | **Deprecated** - will be removed in 26.0 |
|
|
||||||
|
|
||||||
See the [design docs](design.md) for guidance on choosing worker types.
|
See the [design docs](design.md) for guidance on choosing worker types.
|
||||||
|
|
||||||
|
|||||||
@ -230,7 +230,7 @@
|
|||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
- Fix HTTP/2 ALPN negotiation for gevent and eventlet workers when
|
- Fix HTTP/2 ALPN negotiation for the gevent worker when
|
||||||
`do_handshake_on_connect` is False (the default). The TLS handshake is now
|
`do_handshake_on_connect` is False (the default). The TLS handshake is now
|
||||||
explicitly performed before checking `selected_alpn_protocol()`.
|
explicitly performed before checking `selected_alpn_protocol()`.
|
||||||
|
|
||||||
@ -250,11 +250,12 @@
|
|||||||
|
|
||||||
- Fix ASGI: quick shutdown on SIGINT/SIGQUIT, graceful on SIGTERM
|
- Fix ASGI: quick shutdown on SIGINT/SIGQUIT, graceful on SIGTERM
|
||||||
|
|
||||||
### Deprecations
|
### Removals
|
||||||
|
|
||||||
- **Eventlet Worker**: The `eventlet` worker is deprecated and will be removed in
|
- **Eventlet Worker**: The `eventlet` worker has been removed. Eventlet itself
|
||||||
Gunicorn 26.0. Eventlet itself is [no longer actively maintained](https://eventlet.readthedocs.io/en/latest/asyncio/migration.html).
|
is [no longer actively maintained](https://eventlet.readthedocs.io/en/latest/asyncio/migration.html);
|
||||||
Please migrate to `gevent`, `gthread`, or another supported worker type.
|
the worker was deprecated in 25.x and is now gone. Migrate to `gevent`,
|
||||||
|
`gthread`, or one of the ASGI workers.
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
@ -329,7 +330,6 @@
|
|||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- **eventlet**: Require eventlet >= 0.40.3 (CVE-2021-21419, CVE-2025-58068)
|
|
||||||
- **gevent**: Require gevent >= 24.10.1 (CVE-2023-41419, CVE-2024-3219)
|
- **gevent**: Require gevent >= 24.10.1 (CVE-2023-41419, CVE-2024-3219)
|
||||||
- **tornado**: Require tornado >= 6.5.0 (CVE-2025-47287)
|
- **tornado**: Require tornado >= 6.5.0 (CVE-2025-47287)
|
||||||
|
|
||||||
|
|||||||
@ -1793,7 +1793,6 @@ libraries may be installed using setuptools' ``extras_require`` feature.
|
|||||||
A string referring to one of the following bundled classes:
|
A string referring to one of the following bundled classes:
|
||||||
|
|
||||||
* ``sync``
|
* ``sync``
|
||||||
* ``eventlet`` - **DEPRECATED: will be removed in 26.0**. Requires eventlet >= 0.40.3
|
|
||||||
* ``gevent`` - Requires gevent >= 24.10.1 (or install it via
|
* ``gevent`` - Requires gevent >= 24.10.1 (or install it via
|
||||||
``pip install gunicorn[gevent]``)
|
``pip install gunicorn[gevent]``)
|
||||||
* ``tornado`` - Requires tornado >= 6.5.0 (or install it via
|
* ``tornado`` - Requires tornado >= 6.5.0 (or install it via
|
||||||
@ -1837,7 +1836,7 @@ This setting only affects the Gthread worker type.
|
|||||||
|
|
||||||
The maximum number of simultaneous clients.
|
The maximum number of simultaneous clients.
|
||||||
|
|
||||||
This setting only affects the ``gthread``, ``eventlet`` and ``gevent`` worker types.
|
This setting only affects the ``gthread`` and ``gevent`` worker types.
|
||||||
|
|
||||||
### `max_requests`
|
### `max_requests`
|
||||||
|
|
||||||
|
|||||||
@ -719,7 +719,6 @@ class WorkerClass(Setting):
|
|||||||
A string referring to one of the following bundled classes:
|
A string referring to one of the following bundled classes:
|
||||||
|
|
||||||
* ``sync``
|
* ``sync``
|
||||||
* ``eventlet`` - **DEPRECATED: will be removed in 26.0**. Requires eventlet >= 0.40.3
|
|
||||||
* ``gevent`` - Requires gevent >= 24.10.1 (or install it via
|
* ``gevent`` - Requires gevent >= 24.10.1 (or install it via
|
||||||
``pip install gunicorn[gevent]``)
|
``pip install gunicorn[gevent]``)
|
||||||
* ``tornado`` - Requires tornado >= 6.5.0 (or install it via
|
* ``tornado`` - Requires tornado >= 6.5.0 (or install it via
|
||||||
@ -773,7 +772,7 @@ class WorkerConnections(Setting):
|
|||||||
desc = """\
|
desc = """\
|
||||||
The maximum number of simultaneous clients.
|
The maximum number of simultaneous clients.
|
||||||
|
|
||||||
This setting only affects the ``gthread``, ``eventlet`` and ``gevent`` worker types.
|
This setting only affects the ``gthread`` and ``gevent`` worker types.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ class DirtyClient:
|
|||||||
|
|
||||||
Provides both sync and async APIs. The sync API is for traditional
|
Provides both sync and async APIs. The sync API is for traditional
|
||||||
sync workers (sync, gthread), while the async API is for async
|
sync workers (sync, gthread), while the async API is for async
|
||||||
workers (asgi, gevent, eventlet).
|
workers (asgi, gevent).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, socket_path, timeout=30.0):
|
def __init__(self, socket_path, timeout=30.0):
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
# supported gunicorn workers.
|
# supported gunicorn workers.
|
||||||
SUPPORTED_WORKERS = {
|
SUPPORTED_WORKERS = {
|
||||||
"sync": "gunicorn.workers.sync.SyncWorker",
|
"sync": "gunicorn.workers.sync.SyncWorker",
|
||||||
"eventlet": "gunicorn.workers.geventlet.EventletWorker", # DEPRECATED: will be removed in 26.0
|
|
||||||
"gevent": "gunicorn.workers.ggevent.GeventWorker",
|
"gevent": "gunicorn.workers.ggevent.GeventWorker",
|
||||||
"gevent_wsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker",
|
"gevent_wsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker",
|
||||||
"gevent_pywsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker",
|
"gevent_pywsgi": "gunicorn.workers.ggevent.GeventPyWSGIWorker",
|
||||||
|
|||||||
@ -1,217 +0,0 @@
|
|||||||
#
|
|
||||||
# This file is part of gunicorn released under the MIT license.
|
|
||||||
# See the NOTICE for more information.
|
|
||||||
|
|
||||||
# DEPRECATION NOTICE: The eventlet worker is deprecated and will be removed
|
|
||||||
# in Gunicorn 26.0. Eventlet itself is deprecated and no longer maintained.
|
|
||||||
# Please migrate to gevent, gthread, or another supported worker type.
|
|
||||||
# See: https://eventlet.readthedocs.io/en/latest/asyncio/migration.html
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"The eventlet worker is deprecated and will be removed in Gunicorn 26.0. "
|
|
||||||
"Please migrate to gevent, gthread, or another supported worker type. "
|
|
||||||
"See: https://docs.gunicorn.org/en/stable/design.html#choosing-a-worker-type",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2
|
|
||||||
)
|
|
||||||
|
|
||||||
# NOTE: eventlet import and monkey_patch() must happen before any other imports
|
|
||||||
# to ensure all standard library modules are properly patched.
|
|
||||||
try:
|
|
||||||
import eventlet
|
|
||||||
except ImportError:
|
|
||||||
raise RuntimeError("eventlet worker requires eventlet 0.40.3 or higher")
|
|
||||||
else:
|
|
||||||
from packaging.version import parse as parse_version
|
|
||||||
if parse_version(eventlet.__version__) < parse_version('0.40.3'):
|
|
||||||
raise RuntimeError("eventlet worker requires eventlet 0.40.3 or higher")
|
|
||||||
|
|
||||||
# Perform monkey patching early, before importing other modules.
|
|
||||||
# This ensures that all subsequent imports get the patched versions.
|
|
||||||
# NOTE: hubs.use_hub() must NOT be called here - it creates OS resources
|
|
||||||
# (like kqueue on macOS) that don't survive fork. It must be called in
|
|
||||||
# each worker process after fork, in the patch() method.
|
|
||||||
eventlet.monkey_patch()
|
|
||||||
|
|
||||||
from functools import partial # noqa: E402
|
|
||||||
import sys # noqa: E402
|
|
||||||
|
|
||||||
from eventlet import hubs, greenthread # noqa: E402
|
|
||||||
from eventlet.greenio import GreenSocket # noqa: E402
|
|
||||||
import eventlet.wsgi # noqa: E402
|
|
||||||
import greenlet # noqa: E402
|
|
||||||
|
|
||||||
from gunicorn.workers.base_async import AsyncWorker # noqa: E402
|
|
||||||
from gunicorn.sock import ssl_wrap_socket # noqa: E402
|
|
||||||
|
|
||||||
# ALREADY_HANDLED is removed in 0.30.3+ now it's `WSGI_LOCAL.already_handled: bool`
|
|
||||||
# https://github.com/eventlet/eventlet/pull/544
|
|
||||||
EVENTLET_WSGI_LOCAL = getattr(eventlet.wsgi, "WSGI_LOCAL", None)
|
|
||||||
EVENTLET_ALREADY_HANDLED = getattr(eventlet.wsgi, "ALREADY_HANDLED", None)
|
|
||||||
|
|
||||||
|
|
||||||
def _eventlet_socket_sendfile(self, file, offset=0, count=None):
|
|
||||||
# Based on the implementation in gevent which in turn is slightly
|
|
||||||
# modified from the standard library implementation.
|
|
||||||
if self.gettimeout() == 0:
|
|
||||||
raise ValueError("non-blocking sockets are not supported")
|
|
||||||
if offset:
|
|
||||||
file.seek(offset)
|
|
||||||
blocksize = min(count, 8192) if count else 8192
|
|
||||||
total_sent = 0
|
|
||||||
# localize variable access to minimize overhead
|
|
||||||
file_read = file.read
|
|
||||||
sock_send = self.send
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
if count:
|
|
||||||
blocksize = min(count - total_sent, blocksize)
|
|
||||||
if blocksize <= 0:
|
|
||||||
break
|
|
||||||
data = memoryview(file_read(blocksize))
|
|
||||||
if not data:
|
|
||||||
break # EOF
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
sent = sock_send(data)
|
|
||||||
except BlockingIOError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
total_sent += sent
|
|
||||||
if sent < len(data):
|
|
||||||
data = data[sent:]
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return total_sent
|
|
||||||
finally:
|
|
||||||
if total_sent > 0 and hasattr(file, 'seek'):
|
|
||||||
file.seek(offset + total_sent)
|
|
||||||
|
|
||||||
|
|
||||||
def _eventlet_serve(sock, handle, concurrency):
|
|
||||||
"""
|
|
||||||
Serve requests forever.
|
|
||||||
|
|
||||||
This code is nearly identical to ``eventlet.convenience.serve`` except
|
|
||||||
that it attempts to join the pool at the end, which allows for gunicorn
|
|
||||||
graceful shutdowns.
|
|
||||||
"""
|
|
||||||
pool = eventlet.greenpool.GreenPool(concurrency)
|
|
||||||
server_gt = eventlet.greenthread.getcurrent()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
conn, addr = sock.accept()
|
|
||||||
gt = pool.spawn(handle, conn, addr)
|
|
||||||
gt.link(_eventlet_stop, server_gt, conn)
|
|
||||||
conn, addr, gt = None, None, None
|
|
||||||
except eventlet.StopServe:
|
|
||||||
sock.close()
|
|
||||||
pool.waitall()
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def _eventlet_stop(client, server, conn):
|
|
||||||
"""
|
|
||||||
Stop a greenlet handling a request and close its connection.
|
|
||||||
|
|
||||||
This code is lifted from eventlet so as not to depend on undocumented
|
|
||||||
functions in the library.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
client.wait()
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
except greenlet.GreenletExit:
|
|
||||||
pass
|
|
||||||
except Exception:
|
|
||||||
greenthread.kill(server, *sys.exc_info())
|
|
||||||
|
|
||||||
|
|
||||||
def patch_sendfile():
|
|
||||||
# As of eventlet 0.25.1, GreenSocket.sendfile doesn't exist,
|
|
||||||
# meaning the native implementations of socket.sendfile will be used.
|
|
||||||
# If os.sendfile exists, it will attempt to use that, failing explicitly
|
|
||||||
# if the socket is in non-blocking mode, which the underlying
|
|
||||||
# socket object /is/. Even the regular _sendfile_use_send will
|
|
||||||
# fail in that way; plus, it would use the underlying socket.send which isn't
|
|
||||||
# properly cooperative. So we have to monkey-patch a working socket.sendfile()
|
|
||||||
# into GreenSocket; in this method, `self.send` will be the GreenSocket's
|
|
||||||
# send method which is properly cooperative.
|
|
||||||
if not hasattr(GreenSocket, 'sendfile'):
|
|
||||||
GreenSocket.sendfile = _eventlet_socket_sendfile
|
|
||||||
|
|
||||||
|
|
||||||
class EventletWorker(AsyncWorker):
|
|
||||||
|
|
||||||
def patch(self):
|
|
||||||
# NOTE: eventlet.monkey_patch() is called at module import time to
|
|
||||||
# ensure all imports are properly patched. However, hubs.use_hub()
|
|
||||||
# must be called here (after fork) because it creates OS resources
|
|
||||||
# like kqueue that don't survive fork.
|
|
||||||
hubs.use_hub()
|
|
||||||
patch_sendfile()
|
|
||||||
|
|
||||||
def is_already_handled(self, respiter):
|
|
||||||
# eventlet >= 0.30.3
|
|
||||||
if getattr(EVENTLET_WSGI_LOCAL, "already_handled", None):
|
|
||||||
raise StopIteration()
|
|
||||||
# eventlet < 0.30.3
|
|
||||||
if respiter == EVENTLET_ALREADY_HANDLED:
|
|
||||||
raise StopIteration()
|
|
||||||
return super().is_already_handled(respiter)
|
|
||||||
|
|
||||||
def init_process(self):
|
|
||||||
self.log.warning(
|
|
||||||
"The eventlet worker is DEPRECATED and will be removed in Gunicorn 26.0. "
|
|
||||||
"Please migrate to gevent, gthread, or another supported worker type."
|
|
||||||
)
|
|
||||||
self.patch()
|
|
||||||
super().init_process()
|
|
||||||
|
|
||||||
def handle_quit(self, sig, frame):
|
|
||||||
eventlet.spawn(super().handle_quit, sig, frame)
|
|
||||||
|
|
||||||
def handle_usr1(self, sig, frame):
|
|
||||||
eventlet.spawn(super().handle_usr1, sig, frame)
|
|
||||||
|
|
||||||
def timeout_ctx(self):
|
|
||||||
return eventlet.Timeout(self.cfg.keepalive or None, False)
|
|
||||||
|
|
||||||
def handle(self, listener, client, addr):
|
|
||||||
if self.cfg.is_ssl:
|
|
||||||
client = ssl_wrap_socket(client, self.cfg)
|
|
||||||
super().handle(listener, client, addr)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
acceptors = []
|
|
||||||
for sock in self.sockets:
|
|
||||||
gsock = GreenSocket(sock)
|
|
||||||
gsock.setblocking(1)
|
|
||||||
hfun = partial(self.handle, gsock)
|
|
||||||
acceptor = eventlet.spawn(_eventlet_serve, gsock, hfun,
|
|
||||||
self.worker_connections)
|
|
||||||
|
|
||||||
acceptors.append(acceptor)
|
|
||||||
eventlet.sleep(0.0)
|
|
||||||
|
|
||||||
while self.alive:
|
|
||||||
self.notify()
|
|
||||||
eventlet.sleep(1.0)
|
|
||||||
|
|
||||||
self.notify()
|
|
||||||
t = None
|
|
||||||
try:
|
|
||||||
with eventlet.Timeout(self.cfg.graceful_timeout) as t:
|
|
||||||
for a in acceptors:
|
|
||||||
a.kill(eventlet.StopServe())
|
|
||||||
for a in acceptors:
|
|
||||||
a.wait()
|
|
||||||
except eventlet.Timeout as te:
|
|
||||||
if te != t:
|
|
||||||
raise
|
|
||||||
for a in acceptors:
|
|
||||||
a.kill()
|
|
||||||
@ -81,7 +81,7 @@ class TornadoWorker(Worker):
|
|||||||
if 'h2' in self.cfg.http_protocols:
|
if 'h2' in self.cfg.http_protocols:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"HTTP/2 is not supported by the tornado worker. "
|
"HTTP/2 is not supported by the tornado worker. "
|
||||||
"Use gthread, gevent, eventlet, or asgi workers for HTTP/2 support. "
|
"Use gthread, gevent, or asgi workers for HTTP/2 support. "
|
||||||
"Falling back to HTTP/1.1 only."
|
"Falling back to HTTP/1.1 only."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -117,7 +117,7 @@ class SyncWorker(base.Worker):
|
|||||||
if 'h2' in self.cfg.http_protocols:
|
if 'h2' in self.cfg.http_protocols:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"HTTP/2 is not supported by the sync worker. "
|
"HTTP/2 is not supported by the sync worker. "
|
||||||
"Use gthread, gevent, eventlet, or asgi workers for HTTP/2 support. "
|
"Use gthread, gevent, or asgi workers for HTTP/2 support. "
|
||||||
"Falling back to HTTP/1.1 only."
|
"Falling back to HTTP/1.1 only."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,6 @@ Changelog = "https://gunicorn.org/news/"
|
|||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
gevent = ["gevent>=24.10.1"]
|
gevent = ["gevent>=24.10.1"]
|
||||||
eventlet = ["eventlet>=0.40.3"]
|
|
||||||
tornado = ["tornado>=6.5.0"]
|
tornado = ["tornado>=6.5.0"]
|
||||||
gthread = []
|
gthread = []
|
||||||
setproctitle = ["setproctitle"]
|
setproctitle = ["setproctitle"]
|
||||||
@ -56,7 +55,6 @@ http2 = ["h2>=4.1.0"]
|
|||||||
fast = ["gunicorn_h1c>=0.6.5"]
|
fast = ["gunicorn_h1c>=0.6.5"]
|
||||||
testing = [
|
testing = [
|
||||||
"gevent>=24.10.1",
|
"gevent>=24.10.1",
|
||||||
"eventlet>=0.40.3",
|
|
||||||
"h2>=4.1.0",
|
"h2>=4.1.0",
|
||||||
"coverage",
|
"coverage",
|
||||||
"pytest",
|
"pytest",
|
||||||
@ -82,11 +80,6 @@ testpaths = ["tests/"]
|
|||||||
addopts = "--assert=plain --cov=gunicorn --cov-report=xml"
|
addopts = "--assert=plain --cov=gunicorn --cov-report=xml"
|
||||||
asyncio_mode = "auto"
|
asyncio_mode = "auto"
|
||||||
asyncio_default_fixture_loop_scope = "function"
|
asyncio_default_fixture_loop_scope = "function"
|
||||||
filterwarnings = [
|
|
||||||
# Eventlet patches select module, which breaks asyncio event loop cleanup
|
|
||||||
# This is expected behavior when testing eventlet worker
|
|
||||||
"ignore::pytest.PytestUnraisableExceptionWarning",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.setuptools]
|
||||||
zip-safe = false
|
zip-safe = false
|
||||||
|
|||||||
@ -4,3 +4,5 @@ pytest>=7.2.0
|
|||||||
pytest-cov
|
pytest-cov
|
||||||
pytest-asyncio
|
pytest-asyncio
|
||||||
gunicorn_h1c>=0.6.5
|
gunicorn_h1c>=0.6.5
|
||||||
|
h2>=4.1.0
|
||||||
|
uvloop>=0.19.0
|
||||||
|
|||||||
@ -211,7 +211,7 @@ class TestAlpnProtocolMap:
|
|||||||
class TestAsyncWorkerAlpnHandshake:
|
class TestAsyncWorkerAlpnHandshake:
|
||||||
"""Test that AsyncWorker performs handshake before ALPN check.
|
"""Test that AsyncWorker performs handshake before ALPN check.
|
||||||
|
|
||||||
This is critical for gevent and eventlet workers where do_handshake_on_connect
|
This is critical for the gevent worker where do_handshake_on_connect
|
||||||
may be False, causing ALPN negotiation to not complete until first I/O.
|
may be False, causing ALPN negotiation to not complete until first I/O.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -353,76 +353,3 @@ class TestGeventWorkerAlpn:
|
|||||||
mock_super.assert_called_once()
|
mock_super.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
class TestEventletWorkerAlpn:
|
|
||||||
"""Test ALPN handling in EventletWorker."""
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def eventlet_worker(self):
|
|
||||||
"""Create an EventletWorker instance for testing."""
|
|
||||||
try:
|
|
||||||
import eventlet
|
|
||||||
except (ImportError, AttributeError):
|
|
||||||
pytest.skip("eventlet not available")
|
|
||||||
|
|
||||||
from gunicorn.workers.geventlet import EventletWorker
|
|
||||||
|
|
||||||
worker = EventletWorker.__new__(EventletWorker)
|
|
||||||
worker.cfg = mock.MagicMock()
|
|
||||||
worker.cfg.keepalive = 2
|
|
||||||
worker.cfg.do_handshake_on_connect = False
|
|
||||||
worker.cfg.http_protocols = ["h2", "h1"]
|
|
||||||
worker.cfg.is_ssl = True
|
|
||||||
worker.alive = True
|
|
||||||
worker.log = mock.MagicMock()
|
|
||||||
worker.wsgi = mock.MagicMock()
|
|
||||||
worker.nr = 0
|
|
||||||
worker.max_requests = 1000
|
|
||||||
worker.worker_connections = 1000
|
|
||||||
|
|
||||||
return worker
|
|
||||||
|
|
||||||
def test_eventlet_inherits_async_worker(self):
|
|
||||||
"""Test that EventletWorker inherits from AsyncWorker."""
|
|
||||||
try:
|
|
||||||
import eventlet
|
|
||||||
except (ImportError, AttributeError):
|
|
||||||
pytest.skip("eventlet not available")
|
|
||||||
|
|
||||||
from gunicorn.workers.geventlet import EventletWorker
|
|
||||||
from gunicorn.workers.base_async import AsyncWorker
|
|
||||||
|
|
||||||
assert issubclass(EventletWorker, AsyncWorker)
|
|
||||||
|
|
||||||
def test_eventlet_handle_wraps_ssl_then_calls_super(self, eventlet_worker):
|
|
||||||
"""Test that EventletWorker.handle() wraps SSL then calls super()."""
|
|
||||||
from gunicorn.workers import geventlet
|
|
||||||
|
|
||||||
mock_client = mock.MagicMock()
|
|
||||||
mock_wrapped = mock.MagicMock()
|
|
||||||
mock_listener = mock.MagicMock()
|
|
||||||
|
|
||||||
with mock.patch.object(geventlet, 'ssl_wrap_socket', return_value=mock_wrapped):
|
|
||||||
with mock.patch('gunicorn.workers.base_async.AsyncWorker.handle') as mock_super:
|
|
||||||
eventlet_worker.handle(mock_listener, mock_client, ('127.0.0.1', 8000))
|
|
||||||
|
|
||||||
# Verify super().handle() was called with the wrapped socket
|
|
||||||
mock_super.assert_called_once()
|
|
||||||
call_args = mock_super.call_args[0]
|
|
||||||
assert call_args[1] == mock_wrapped # Second arg is the client socket
|
|
||||||
|
|
||||||
def test_eventlet_alpn_works_with_handshake_fix(self, eventlet_worker):
|
|
||||||
"""Test that ALPN detection works after handshake fix for eventlet."""
|
|
||||||
from gunicorn.workers import geventlet
|
|
||||||
|
|
||||||
mock_ssl_socket = mock.Mock(spec=ssl.SSLSocket)
|
|
||||||
mock_ssl_socket.selected_alpn_protocol.return_value = "h2"
|
|
||||||
mock_listener = mock.MagicMock()
|
|
||||||
|
|
||||||
with mock.patch.object(geventlet, 'ssl_wrap_socket', return_value=mock_ssl_socket):
|
|
||||||
with mock.patch.object(eventlet_worker, 'handle_http2') as mock_h2:
|
|
||||||
eventlet_worker.handle(mock_listener, mock.MagicMock(), ('127.0.0.1', 8000))
|
|
||||||
|
|
||||||
# Verify handshake was called (by base_async.handle)
|
|
||||||
mock_ssl_socket.do_handshake.assert_called_once()
|
|
||||||
# Verify HTTP/2 handler was invoked
|
|
||||||
mock_h2.assert_called_once()
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user