mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-01 10:11:30 +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)
|
||||
- gthread
|
||||
- gevent
|
||||
- eventlet
|
||||
- tornado
|
||||
- asgi (beta)
|
||||
- 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 -e .
|
||||
pytest --cov=gunicorn -v tests/ \
|
||||
--ignore=tests/workers/test_ggevent.py \
|
||||
--ignore=tests/workers/test_geventlet.py
|
||||
--ignore=tests/workers/test_ggevent.py
|
||||
|
||||
@ -37,7 +37,7 @@ gunicorn myapp:app --worker-class asgi
|
||||
- **HTTP/2 support** (beta) with multiplexed streams
|
||||
- **Dirty Arbiters** (beta) for heavy workloads (ML models, long-running tasks)
|
||||
- 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
|
||||
- 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
|
||||
```
|
||||
|
||||
=== "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"
|
||||
|
||||
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 |
|
||||
| ASGI workers | AsyncIO | ✅ | Modern async frameworks (FastAPI, etc.) |
|
||||
| `gevent` | Greenlets | ✅ | I/O-bound, WebSockets, streaming |
|
||||
| `eventlet` | Greenlets | ✅ | **Deprecated** - use `gevent` instead |
|
||||
| `tornado` | Tornado IOLoop | ✅ | Native Tornado applications |
|
||||
|
||||
!!! tip "Quick Decision Guide"
|
||||
|
||||
@ -109,7 +109,6 @@ Not all workers support HTTP/2:
|
||||
| `sync` | No | Single-threaded, cannot multiplex streams |
|
||||
| `gthread` | Yes | Recommended for HTTP/2 |
|
||||
| `gevent` | Yes | Requires gevent |
|
||||
| `eventlet` | Yes | **Deprecated** - will be removed in 26.0 |
|
||||
| `asgi` | Yes | For async frameworks |
|
||||
| `tornado` | No | Tornado handles its own protocol |
|
||||
|
||||
|
||||
@ -94,7 +94,6 @@ pip install gunicorn[gevent,setproctitle]
|
||||
| `gunicorn[gevent]` | Gevent-based greenlet workers |
|
||||
| `gunicorn[gthread]` | Threaded workers |
|
||||
| `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.
|
||||
|
||||
|
||||
@ -230,7 +230,7 @@
|
||||
|
||||
### 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
|
||||
explicitly performed before checking `selected_alpn_protocol()`.
|
||||
|
||||
@ -250,11 +250,12 @@
|
||||
|
||||
- Fix ASGI: quick shutdown on SIGINT/SIGQUIT, graceful on SIGTERM
|
||||
|
||||
### Deprecations
|
||||
### Removals
|
||||
|
||||
- **Eventlet Worker**: 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.
|
||||
- **Eventlet Worker**: The `eventlet` worker has been removed. Eventlet itself
|
||||
is [no longer actively maintained](https://eventlet.readthedocs.io/en/latest/asyncio/migration.html);
|
||||
the worker was deprecated in 25.x and is now gone. Migrate to `gevent`,
|
||||
`gthread`, or one of the ASGI workers.
|
||||
|
||||
### Changes
|
||||
|
||||
@ -329,7 +330,6 @@
|
||||
|
||||
### 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)
|
||||
- **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:
|
||||
|
||||
* ``sync``
|
||||
* ``eventlet`` - **DEPRECATED: will be removed in 26.0**. Requires eventlet >= 0.40.3
|
||||
* ``gevent`` - Requires gevent >= 24.10.1 (or install it via
|
||||
``pip install gunicorn[gevent]``)
|
||||
* ``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.
|
||||
|
||||
This setting only affects the ``gthread``, ``eventlet`` and ``gevent`` worker types.
|
||||
This setting only affects the ``gthread`` and ``gevent`` worker types.
|
||||
|
||||
### `max_requests`
|
||||
|
||||
|
||||
@ -719,7 +719,6 @@ class WorkerClass(Setting):
|
||||
A string referring to one of the following bundled classes:
|
||||
|
||||
* ``sync``
|
||||
* ``eventlet`` - **DEPRECATED: will be removed in 26.0**. Requires eventlet >= 0.40.3
|
||||
* ``gevent`` - Requires gevent >= 24.10.1 (or install it via
|
||||
``pip install gunicorn[gevent]``)
|
||||
* ``tornado`` - Requires tornado >= 6.5.0 (or install it via
|
||||
@ -773,7 +772,7 @@ class WorkerConnections(Setting):
|
||||
desc = """\
|
||||
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
|
||||
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):
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
# supported gunicorn workers.
|
||||
SUPPORTED_WORKERS = {
|
||||
"sync": "gunicorn.workers.sync.SyncWorker",
|
||||
"eventlet": "gunicorn.workers.geventlet.EventletWorker", # DEPRECATED: will be removed in 26.0
|
||||
"gevent": "gunicorn.workers.ggevent.GeventWorker",
|
||||
"gevent_wsgi": "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:
|
||||
self.log.warning(
|
||||
"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."
|
||||
)
|
||||
|
||||
|
||||
@ -117,7 +117,7 @@ class SyncWorker(base.Worker):
|
||||
if 'h2' in self.cfg.http_protocols:
|
||||
self.log.warning(
|
||||
"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."
|
||||
)
|
||||
|
||||
|
||||
@ -48,7 +48,6 @@ Changelog = "https://gunicorn.org/news/"
|
||||
|
||||
[project.optional-dependencies]
|
||||
gevent = ["gevent>=24.10.1"]
|
||||
eventlet = ["eventlet>=0.40.3"]
|
||||
tornado = ["tornado>=6.5.0"]
|
||||
gthread = []
|
||||
setproctitle = ["setproctitle"]
|
||||
@ -56,7 +55,6 @@ http2 = ["h2>=4.1.0"]
|
||||
fast = ["gunicorn_h1c>=0.6.5"]
|
||||
testing = [
|
||||
"gevent>=24.10.1",
|
||||
"eventlet>=0.40.3",
|
||||
"h2>=4.1.0",
|
||||
"coverage",
|
||||
"pytest",
|
||||
@ -82,11 +80,6 @@ testpaths = ["tests/"]
|
||||
addopts = "--assert=plain --cov=gunicorn --cov-report=xml"
|
||||
asyncio_mode = "auto"
|
||||
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]
|
||||
zip-safe = false
|
||||
|
||||
@ -4,3 +4,5 @@ pytest>=7.2.0
|
||||
pytest-cov
|
||||
pytest-asyncio
|
||||
gunicorn_h1c>=0.6.5
|
||||
h2>=4.1.0
|
||||
uvloop>=0.19.0
|
||||
|
||||
@ -211,7 +211,7 @@ class TestAlpnProtocolMap:
|
||||
class TestAsyncWorkerAlpnHandshake:
|
||||
"""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.
|
||||
"""
|
||||
|
||||
@ -353,76 +353,3 @@ class TestGeventWorkerAlpn:
|
||||
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