Conversation
Owner
|
Thanks for this, and for the write up. The timeline in your description made the Your fix is the right one. Merged in #3687 with tests and a changelog entry. |
luketainton
pushed a commit
to luketainton/luke_rsu
that referenced
this pull request
Sep 1, 2026
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [gunicorn](https://github.com/benoitc/gunicorn) ([changelog](https://gunicorn.org/news/)) | `>=23,<24` → `>=26,<27` |  |  | --- >⚠️ **Warning** > > Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/11) for more information. --- ### Release Notes <details> <summary>benoitc/gunicorn (gunicorn)</summary> ### [`v26.2.0`](https://github.com/benoitc/gunicorn/releases/tag/26.2.0): gunicorn 26.2.0 [Compare Source](benoitc/gunicorn@26.1.0...26.2.0) Cleartext HTTP/2 lands, and an HTTP/2 security fix. #### Cleartext HTTP/2 (h2c) `http2_cleartext` accepts `prior-knowledge`, `upgrade`, `both` or `off` (the default). Prior knowledge serves a connection that opens with the HTTP/2 preface; `upgrade` honours an HTTP/1.1 `Upgrade: h2c` request. Both work on the gthread, gevent and asgi workers. This is for deployments where TLS is terminated by a proxy that speaks HTTP/2 upstream, so the hop into gunicorn no longer drops to HTTP/1.1. Only peers in `forwarded_allow_ips` are considered; everyone else is served HTTP/1.x exactly as if the setting were off. Each mechanism is enabled separately, so turning one on does not turn the other on. Do not expose a cleartext HTTP/2 port to the internet. #### Security `HTTP2Request` built its headers straight from the stream, so nothing the HTTP/1 path enforces applied over HTTP/2: the underscore and `header_map` policy, duplicate `Host` and `Content-Type`, control characters in values, and the `forwarded_allow_ips` trust gate. An untrusted client could set `SCRIPT_NAME` and forge `HTTP_*` entries in the WSGI environ, and decide `wsgi.url_scheme` through `:scheme`. Both request classes now share one policy mixin, and the scheme comes from the transport. If you serve HTTP/2, this is the reason to upgrade. #### Other HTTP/2 fixes WSGI responses were buffered whole before anything was sent; they stream now. HEAD, 204 and 304 no longer carry a body. Events read while blocked on a flow-control window were discarded, losing requests and body data outright. `sendfile()` is refused on HTTP/2 responses rather than bypassing framing. #### Request bodies dropped on Upgrade requests On the ASGI worker with the fast parser, any request carrying an `Upgrade` header reached the application with an empty body, whatever the header's value and with HTTP/2 switched off entirely. Fixed in `gunicorn_h1c` 0.6.9, which the `fast` extra now requires. Full changelog: <https://gunicorn.org/news/> ### [`v26.1.0`](https://github.com/benoitc/gunicorn/releases/tag/26.1.0): gunicorn 26.1.0 [Compare Source](benoitc/gunicorn@26.0.0...26.1.0) ##### New Features - **Glob patterns in `reload_extra_files`**: entries containing `*`, `?` or `[` are treated as patterns, so `ui/*/config.json` watches every view's config without listing them one by one. Patterns are re-expanded on every reload check rather than once at startup, so a file created later starts being watched without restarting gunicorn, and `**` recurses. A pattern matching nothing warns instead of failing, since with live expansion it may match later ([#​1643](benoitc/gunicorn#1643), [#​3662](benoitc/gunicorn#3662)). ##### Security - **Dependency floors raised past known advisories**: every declared floor was checked against the advisory database. `tornado`, `h2`, `setuptools` and `pymdown-extensions` permitted vulnerable versions and now require the first clean release; `pytest` and `httpx` were unpinned and now carry floors. The `tornado` example pinned `tornado<6`, which was both the source of several advisories and older than the `>=6.5.0` the tornado worker needs, so the example could not run as pinned. ##### Bug Fixes - **SIGHUP did not reload the logger configuration**: `Arbiter.reload()` re-read the configuration file but kept using the logger built at startup, calling only `reopen_files()` on its existing handlers. Changes to `logconfig`, `logconfig_dict`, `logconfig_json` and `loglevel` were ignored until a full restart, which in containers meant replacing the pod. The existing logger now re-runs its setup on reload, so new handlers, formats and levels take effect while the process identity and its listeners are preserved, and re-running the setup no longer stacks duplicate syslog handlers. An invalid log configuration on reload is not fatal either: the error is reported on stderr, the previous working configuration is restored and the master keeps running with it ([#​3353](benoitc/gunicorn#3353)). - **Truncated chunked bodies accepted**: RFC 9112 section 7.1.2 ends a chunked body with `0 CRLF CRLF`, the second CRLF being the mandatory empty trailer section. `ChunkedReader.parse_chunk_size()` swallowed the `NoMoreData` raised while scanning for it, so a body cut short right after the last chunk line was treated as complete instead of rejected. It now raises `ChunkMissingTerminator` ([#​3382](benoitc/gunicorn#3382), [#​3685](benoitc/gunicorn#3685)). - **`--spew` crashed on dynamically generated code**: the trace hook indexed the 2-tuple returned by `inspect.getsourcelines()` by line number rather than indexing the list of lines, so a frame with no `__file__` raised `AttributeError: 'int' object has no attribute 'rstrip'` on line 1 and `IndexError` beyond it. The tuple is now unpacked and offset by the source's starting line ([#​3344](benoitc/gunicorn#3344), [#​3495](benoitc/gunicorn#3495)). - **Duplicate `Host` and `Content-Type` headers accepted**: RFC 9110 section 5.3 allows only one of each, and a repeat cannot be merged into a list, so the message means different things to gunicorn and to anything downstream. Both are now rejected with `InvalidHeader`. The check lives in the policy hook shared by both parsers, so the pure-Python and fast parsers agree. Duplicate `Content-Length` was already rejected and is unchanged ([#​3366](benoitc/gunicorn#3366), [#​3548](benoitc/gunicorn#3548)). - **Non-worker children reported as failed workers**: `reap_workers()` reaps every child through `waitpid(-1)`, including processes the kernel reparented onto gunicorn when it runs as PID 1 in a container, but it logged the exit status before checking whether the pid was ever a worker. An unrelated process produced `Worker (pid:N) exited with code M` and triggered alerts. More seriously, such a process exiting with code 3 or 4 raised `HaltServer` and shut the server down. Ownership is now established first: the dirty arbiter is reported as itself, unknown children are reaped silently at debug level, and only real workers can halt the server ([#​3220](benoitc/gunicorn#3220), [#​3566](benoitc/gunicorn#3566)). - **Dirty arbiter exits were invisible on SIGCHLD**: `handle_chld()` called `reap_workers()` first, whose `waitpid(-1)` claimed the dirty arbiter before `reap_dirty_arbiter()` could identify it, so the latter always hit `ECHILD` and its reporting never ran. The dirty arbiter is now reaped first, and `reap_workers()` recognises it if it exits mid-loop. - **Dirty arbiter returned stale responses after a worker timeout**: when a request reached `dirty_timeout` the arbiter answered the client with a timeout error but kept the worker connection open. The worker's late response was then the first message waiting on that socket, so the next request routed to the same worker received the previous request's result, and every request after it stayed one response behind. The connection is now closed on timeout, so the late answer is discarded with it ([#​3626](benoitc/gunicorn#3626)). - **ASGI connection count leaked on server-initiated close**: `nr_conns` was only decremented in `connection_lost()`, behind a guard keyed on the same flag `_close_transport()` sets first. Every close the server started (a `Connection: close` response, a keepalive timeout, an error abort) leaked one count, so `ASGIWorker._shutdown()` ran the full `graceful_timeout` and warned about connections that were already gone. The guard now uses its own flag, so the decrement and the rest of the cleanup run exactly once whichever side closes first ([#​3661](benoitc/gunicorn#3661)). - **Inotify reloader on cwd-relative extra files**: `reload_extra_files` entries with no directory part (for example `.env`) produced an empty dirname, and watching it raised `InotifyError` with `ENOENT`. The current directory is now watched as `.` ([#​3377](benoitc/gunicorn#3377), [#​3667](benoitc/gunicorn#3667)). - **StatsD zero-valued metrics**: gauges, counters, histograms and timers reporting `0` were silently dropped because the value was tested for truthiness. Only `None` is skipped now ([#​3676](benoitc/gunicorn#3676)). - **Spurious no-body warning from `sendfile()`**: a HEAD, 204 or 304 response served through `sendfile()` warned about dropped body bytes even when the file was empty and nothing was dropped. It now warns only when there are bytes to drop, matching `write()` ([#​3684](benoitc/gunicorn#3684)). - **Bare `except` in the gevent websocket example**: narrowed to `except Exception` ([#​3683](benoitc/gunicorn#3683)). - **ASGI `receive()` cancellation**: Let `asyncio.CancelledError` propagate from `BodyReceiver` instead of swallowing it and returning `http.disconnect`. Frameworks that cancel their disconnect listener after the response completes (Django) no longer see the cancel masked, so `request_finished` fires and `close_old_connections()` runs. Fixes idle database connections leaking since 25.1.0 ([#​3627](benoitc/gunicorn#3627), [#​3654](benoitc/gunicorn#3654)). - **Control socket leak on SIGHUP reload**: The control thread is now marked ready once its loop and server are live, and the stop paths wait on that readiness before scheduling shutdown. Reloads no longer leak one thread and its selector fd plus unix socket per worker, which eventually raised "too many open files" ([#​3648](benoitc/gunicorn#3648)). - **WSGI body framing on HEAD/1xx/204/304**: Mirror the ASGI strip-and-warn behavior on the WSGI path. `Content-Length` is stripped on 1xx/204 per RFC 9110 section 6.4.2, body bytes are dropped for no-body responses in both `write()` and `sendfile()`, and a single warning is logged per request ([#​3413](benoitc/gunicorn#3413)). ##### Refactoring - Pass log arguments to the logger instead of pre-formatting the worker termination message in `Arbiter.reap_workers()` ([#​3678](benoitc/gunicorn#3678)). ##### Changes - **`packaging` is no longer a runtime dependency**: it was only ever imported by the gevent worker, to compare gevent's version. It moved to the `gevent` and `testing` extras, so a plain `pip install gunicorn` pulls in nothing ([#​3643](benoitc/gunicorn#3643)). - **Fast HTTP Parser**: Require `gunicorn_h1c >= 0.6.6`, which rejects duplicate `Host` and `Content-Type` headers in the C parser itself. Gunicorn already refuses them on both the WSGI and ASGI paths, so this changes nothing that is reachable; it moves the rejection to where the bytes are read and lets the ASGI corpus exercise those cases against the fast parser directly. Full changelog: <https://gunicorn.org/2026-news/> ### [`v26.0.0`](https://github.com/benoitc/gunicorn/releases/tag/26.0.0) [Compare Source](benoitc/gunicorn@25.3.0...26.0.0) #### Breaking Changes - **Eventlet worker removed**: The `eventlet` worker class has been dropped. Migrate to `gevent`, `gthread`, or `tornado`. #### New Features - **ASGI Framework Compatibility Suite**: New end-to-end compatibility test harness covering Starlette, FastAPI, Litestar, Quart, Sanic, and BlackSheep. Current grid passes 438/444 tests (98%). - **ASGI Test Suite Expansion**: 134 additional ASGI unit tests covering protocol semantics, lifespan, websockets, and chunked framing. #### Security - **HTTP/1.1 Request-Target Validation** (RFC 9112 sections 3.2.3, 3.2.4): - Reject `authority-form` request-target outside `CONNECT` - Reject `asterisk-form` request-target outside `OPTIONS` - Reject `relative-reference` request-targets - **Header Field Hardening** (RFC 9110): - Reject control characters in header field-value (section 5.5) - Reject forbidden trailer field-names (section 6.5.1) - Reject `Content-Length` list form (RFC 9112 section 6.3) - **Request Smuggling Hardening**: - Tighten keepalive gate and scope `finish_body` byte cap - Keep `_body_receiver` alive across the keepalive smuggling gate so pipelined requests cannot re-enter a closed body - Address parser/protocol findings from a six-point WSGI/ASGI audit - **PROXY Protocol (ASGI)**: Enforce `proxy_allow_ips` and tighten v1/v2 parsing in the ASGI callback parser. - **Connection Draining**: Drain the connection on close per RFC 9112 section 9.6 to prevent reset-on-close truncation. #### Bug Fixes - **Body Framing on HEAD/204/304**: - Keep `Content-Length` on HEAD and 304 responses ([#​3621](benoitc/gunicorn#3621)) - Drop body framing on HEAD/204/304 even when the framework set it - Warn once when an ASGI app emits a body for a no-body response - **HTTP/2 ASGI**: - Fix `_handle_stream_ended` to set `_body_complete` in the async HTTP/2 handler so request bodies finalize correctly on stream end - Add `InvalidChunkExtension` mapping and fast-parser support in ASGI tests ([#​3565](benoitc/gunicorn#3565)) - **HTTP/1.1 100-Continue**: Stop adding `Transfer-Encoding: chunked` to 100-Continue interim responses. - **WebSocket Close Handshake** (RFC 6455): - Comply with the close handshake state machine - Close the transport after the close handshake completes - Fix binary send when the `text` key is `None` - **Early Hints**: Validate headers in the `early_hints` callback to match `process_headers`; pass only the header name to `InvalidHeader` ([#​3588](benoitc/gunicorn#3588)). - **ASGI Framework Fixes**: - Fix ASGI disconnect handling for Django-style apps - Fix Litestar request handling (use raw ASGI receive for body/headers) - Fix Litestar HTTP endpoints for compatibility tests - Fix Quart headers endpoint to normalize keys to lowercase - Fix Quart WebSocket close test app (missing `accept()`) - Fix duplicate `Transfer-Encoding` header for BlackSheep streaming #### Refactoring - Split `BodyReceiver._closed` into separate transport and body-wait flags for clearer keepalive/EOF semantics. #### Changes - **Fast HTTP Parser**: Require `gunicorn_h1c >= 0.6.5`. Drop the last `python_only` test markers; the C extension is now used wherever available (CPython only; PyPy continues to use the Python parser). - **Test Dependencies**: Add `h2` and `uvloop` to the `testing` extra; remove `eventlet`. - **Docker Build**: Bump GitHub Actions `docker/setup-qemu-action`, `docker/setup-buildx-action`, `docker/login-action`, `docker/build-push-action`, and `docker/metadata-action` to current major versions. **Full changelog**: <benoitc/gunicorn@25.3.0...26.0.0> ### [`v25.3.0`](https://github.com/benoitc/gunicorn/releases/tag/25.3.0): Gunicorn 25.3.0 [Compare Source](benoitc/gunicorn@25.2.0...25.3.0) #### Bug Fixes - **HTTP/2 ASGI Body Duplication**: Fix request body being received twice in HTTP/2 ASGI requests, causing JSON parsing errors with "Extra data" messages ([#​3558](benoitc/gunicorn#3558)) - **ASGI Chunked EOF Handling**: Add `finish()` method to callback parser to handle chunked encoding edge case where connection closes before final CRLF after zero-chunk - **HTTP/2 Documentation**: Fix `http_protocols` examples to use comma-separated string instead of list syntax ([#​3561](benoitc/gunicorn#3561)) - **Chunked Encoding**: Reject chunk extensions containing bare CR bytes per RFC 9112 ([#​3556](benoitc/gunicorn#3556)) - **Request Line Limit**: Fix `--limit-request-line 0` to mean unlimited as documented, instead of using default maximum. Works with both Python and fast C parser. ([#​3563](benoitc/gunicorn#3563)) #### Security - **ASGI Parser Header Validation**: Add security checks per RFC 9110/9112: - Reject duplicate Content-Length headers - Reject requests with both Content-Length and Transfer-Encoding - Reject chunked transfer encoding in HTTP/1.0 - Reject stacked chunked encoding - Validate Transfer-Encoding values - Strict chunk size validation #### Changes - **Fast HTTP Parser**: Update to gunicorn\_h1c >= 0.6.3 for `asgi_headers` property and `InvalidChunkExtension` validation for bare CR rejection - **ASGI PROXY Protocol**: Add PROXY protocol v1/v2 support to callback parser - **Docker Images**: Update to Python 3.14 ### [`v25.2.0`](https://github.com/benoitc/gunicorn/releases/tag/25.2.0): Gunicorn 25.2.0 [Compare Source](benoitc/gunicorn@25.1.0...25.2.0) ##### New Features - **Fast HTTP Parser (gunicorn\_h1c 0.4.1)**: Integrate new exception types and limit parameters from gunicorn\_h1c 0.4.1 for both WSGI and ASGI workers - Requires gunicorn\_h1c >= 0.4.1 for `http_parser='fast'` - Falls back to Python parser in `auto` mode if version not met - Proper HTTP status codes for limit errors (414, 431) ##### Bug Fixes - **uWSGI Async Workers**: Fix `InvalidUWSGIHeader: incomplete header` error when using gevent or gthread workers with uwsgi protocol behind nginx. ([#​3552](benoitc/gunicorn#3552), [PR #​3554](benoitc/gunicorn#3554)) - **FileWrapper Iterator Protocol**: Add `__iter__` and `__next__` methods to `FileWrapper` for full PEP 3333 compliance. ([#​3396](benoitc/gunicorn#3396), [PR #​3550](benoitc/gunicorn#3550)) ##### Performance - **ASGI HTTP Parser Optimizations**: Improve ASGI worker HTTP parsing performance - Callback-based parsing with direct `bytearray` buffer operations - Use `bytearray.find()` directly instead of converting to bytes first - Use index-based iteration for header parsing instead of `list.pop(0)` (O(1) vs O(n)) ### [`v25.1.0`](https://github.com/benoitc/gunicorn/releases/tag/25.1.0): Gunicorn 25.1.0 [Compare Source](benoitc/gunicorn@25.0.3...25.1.0) ##### New Features - **Control Interface (gunicornc)**: Add interactive control interface for managing running Gunicorn instances, similar to birdc for BIRD routing daemon ([PR #​3505](benoitc/gunicorn#3505)) - Unix socket-based communication with JSON protocol - Interactive mode with readline support and command history - Commands: `show all/workers/dirty/config/stats/listeners` - Worker management: `worker add/remove/kill`, `dirty add/remove` - Server control: `reload`, `reopen`, `shutdown` - New settings: `--control-socket`, `--control-socket-mode`, `--no-control-socket` - New CLI tool: `gunicornc` for connecting to control socket - See [Control Interface Guide](https://gunicorn.org/guides/gunicornc/) for details - **Dirty Stash**: Add global shared state between workers via `dirty.stash` ([PR #​3503](benoitc/gunicorn#3503)) - In-memory key-value store accessible by all workers - Supports get, set, delete, clear, keys, and has operations - Useful for sharing state like feature flags, rate limits, or cached data - **Dirty Binary Protocol**: Implement efficient binary protocol for dirty arbiter IPC using TLV (Type-Length-Value) encoding ([PR #​3500](benoitc/gunicorn#3500)) - More efficient than JSON for binary data - Supports all Python types: str, bytes, int, float, bool, None, list, dict - Better performance for large payloads - **Dirty TTIN/TTOU Signals**: Add dynamic worker scaling for dirty arbiters ([PR #​3504](benoitc/gunicorn#3504)) - Send SIGTTIN to increase dirty workers - Send SIGTTOU to decrease dirty workers - Respects minimum worker constraints from app configurations ##### Changes - **ASGI Worker**: Promoted from beta to stable - **Dirty Arbiters**: Now marked as beta feature ##### Documentation - Fix Markdown formatting in /configure documentation ### [`v25.0.3`](https://github.com/benoitc/gunicorn/releases/tag/25.0.3) [Compare Source](benoitc/gunicorn@25.0.2...25.0.3) #### What's Changed ##### Bug Fixes - Fix RuntimeError when StopIteration raised in ASGI coroutine ([#​3484](benoitc/gunicorn#3484)) - Fix passing maxsplit in re.split() as positional argument (deprecated in Python 3.13) ##### Documentation - Updated sponsorship section and homepage **Full Changelog**: <benoitc/gunicorn@25.0.2...25.0.3> ### [`v25.0.2`](https://github.com/benoitc/gunicorn/releases/tag/25.0.2) [Compare Source](benoitc/gunicorn@25.0.1...25.0.2) #### What's Changed ##### Bug Fixes - Fix ASGI concurrent request failures through nginx proxy - Graceful disconnect handling for ASGI worker - Lazy import dirty module for gevent compatibility ##### Other - Increase CI timeout for signal tests on PyPy - Remove trailing blank line in instrument/**init**.py **Full Changelog**: <benoitc/gunicorn@25.0.1...25.0.2> ### [`v25.0.1`](https://github.com/benoitc/gunicorn/releases/tag/25.0.1) [Compare Source](benoitc/gunicorn@25.0.0...25.0.1) #### Bug Fixes - Fix ASGI streaming responses (SSE) hanging: add chunked transfer encoding for HTTP/1.1 responses without Content-Length header. Without chunked encoding, clients wait for connection close to determine end-of-response. #### Changes - Update celery\_alternative example to use FastAPI with native ASGI worker and uvloop for async task execution #### Testing - Add ASGI compliance test suite with Docker-based integration tests covering HTTP, WebSocket, streaming, lifespan, framework integration (Starlette, FastAPI), HTTP/2, and concurrency scenarios ### [`v25.0.0`](https://github.com/benoitc/gunicorn/releases/tag/25.0.0): Gunicorn 25.0.0 [Compare Source](benoitc/gunicorn@24.1.1...25.0.0) #### New Features - **Dirty Arbiters**: Separate process pool for executing long-running, blocking operations (AI model loading, heavy computation) without blocking HTTP workers ([PR #​3460](benoitc/gunicorn#3460)) - Inspired by Erlang's dirty schedulers - Asyncio-based with Unix socket IPC - Stateful workers that persist loaded resources - New settings: `--dirty-app`, `--dirty-workers`, `--dirty-timeout`, `--dirty-threads`, `--dirty-graceful-timeout` - Lifecycle hooks: `on_dirty_starting`, `dirty_post_fork`, `dirty_worker_init`, `dirty_worker_exit` - **Per-App Worker Allocation for Dirty Arbiters**: Control how many dirty workers load each app for memory optimization with heavy models ([PR #​3473](benoitc/gunicorn#3473)) - Set `workers` class attribute on DirtyApp (e.g., `workers = 2`) - Or use config format `module:class:N` (e.g., `myapp:HeavyModel:2`) - Requests automatically routed to workers with the target app - New exception `DirtyNoWorkersAvailableError` for graceful error handling - Example: 8 workers × 10GB model = 80GB → with `workers=2`: 20GB (75% savings) - **HTTP/2 Support (Beta)**: Native HTTP/2 (RFC 7540) support for improved performance with modern clients ([PR #​3468](benoitc/gunicorn#3468)) - Multiplexed streams over a single connection - Header compression (HPACK) - Flow control and stream prioritization - Works with gthread, gevent, and ASGI workers - New settings: `--http-protocols`, `--http2-max-concurrent-streams`, `--http2-initial-window-size`, `--http2-max-frame-size`, `--http2-max-header-list-size` - Requires SSL/TLS and h2 library: `pip install gunicorn[http2]` - New example: `examples/http2_gevent/` with Docker and tests - **HTTP 103 Early Hints**: Support for RFC 8297 Early Hints to enable browsers to preload resources before the final response ([PR #​3468](benoitc/gunicorn#3468)) - WSGI: `environ['wsgi.early_hints'](headers)` callback - ASGI: `http.response.informational` message type - Works with both HTTP/1.1 and HTTP/2 - **uWSGI Protocol for ASGI Worker**: The ASGI worker now supports receiving requests via the uWSGI binary protocol from nginx ([PR #​3467](benoitc/gunicorn#3467)) #### Bug Fixes - Fix HTTP/2 ALPN negotiation for gevent and eventlet workers when `do_handshake_on_connect` is False (the default). The TLS handshake is now explicitly performed before checking `selected_alpn_protocol()`. - Fix setproctitle initialization with systemd socket activation ([#​3465](benoitc/gunicorn#3465)) - Fix `Expect: 100-continue` handling: ignore the header for HTTP/1.0 requests since 100-continue is only valid for HTTP/1.1+ ([PR #​3463](benoitc/gunicorn#3463)) - Fix missing `_expected_100_continue` attribute in UWSGIRequest - Disable setproctitle on macOS to prevent segfaults during process title updates - Publish full exception traceback when the application fails to load ([#​3462](benoitc/gunicorn#3462)) - Fix ASGI: quick shutdown on SIGINT/SIGQUIT, graceful on SIGTERM #### Deprecations - **Eventlet Worker**: The `eventlet` worker is deprecated and will be removed in Gunicorn 26.0. Eventlet itself is no longer actively maintained. Please migrate to `gevent`, `gthread`, or another supported worker type. #### Changes - Remove obsolete Makefile targets ([PR #​3471](benoitc/gunicorn#3471)) - Replace RST with markdown documentation format ### [`v24.1.1`](https://github.com/benoitc/gunicorn/releases/tag/24.1.1) [Compare Source](benoitc/gunicorn@24.1.0...24.1.1) #### Bug Fixes - Fix `forwarded_allow_ips` and `proxy_allow_ips` to remain as strings for backward compatibility with external tools like uvicorn. Network validation now uses strict mode to detect invalid CIDR notation (e.g., `192.168.1.1/24` where host bits are set) ([#​3458](benoitc/gunicorn#3458), [PR #​3459](benoitc/gunicorn#3459)) *** **Full Changelog**: <benoitc/gunicorn@24.1.0...24.1.1> ### [`v24.1.0`](https://github.com/benoitc/gunicorn/releases/tag/24.1.0): Gunicorn 24.1.0 [Compare Source](benoitc/gunicorn@24.0.0...24.1.0) #### New Features - **Official Docker Image**: Gunicorn now publishes official Docker images to GitHub Container Registry ([PR #​3454](benoitc/gunicorn#3454)) - Available at `ghcr.io/benoitc/gunicorn` - Based on Python 3.12 slim image - Uses recommended worker formula (2 × CPU + 1) - Configurable via environment variables - **PROXY Protocol v2 Support**: Extended PROXY protocol implementation to support the binary v2 format in addition to the existing text-based v1 format ([PR #​3451](benoitc/gunicorn#3451)) - New `--proxy-protocol` modes: `off`, `v1`, `v2`, `auto` - `auto` mode (default when enabled) detects v1 or v2 automatically - v2 binary format is more efficient and supports additional metadata - Works with HAProxy, AWS NLB/ALB, and other PROXY protocol v2 sources - **CIDR Network Support**: `--forwarded-allow-ips` and `--proxy-allow-from` now accept CIDR notation (e.g., `192.168.0.0/16`) for specifying trusted networks ([PR #​3449](benoitc/gunicorn#3449)) - **Socket Backlog Metric**: New `gunicorn.socket.backlog` gauge metric reports the current socket backlog size on Linux systems ([PR #​3450](benoitc/gunicorn#3450)) - **InotifyReloader Enhancement**: The inotify-based reloader now watches newly imported modules, not just those loaded at startup ([PR #​3447](benoitc/gunicorn#3447)) #### Bug Fixes - Fix signal handling regression where SIGCLD alias caused "Unhandled signal: cld" errors on Linux when workers fail during boot ([#​3453](benoitc/gunicorn#3453)) - Fix socket blocking mode on keepalive connections preventing SSL handshake failures with async workers ([PR #​3452](benoitc/gunicorn#3452)) - Use smaller buffer size in `finish_body()` for faster timeout detection on slow or abandoned connections ([PR #​3453](benoitc/gunicorn#3453)) - Handle `SSLWantReadError` in `finish_body()` to prevent worker hangs during SSL renegotiation ([PR #​3448](benoitc/gunicorn#3448)) - Log SIGTERM as info level instead of warning to reduce noise in orchestrated environments ([PR #​3446](benoitc/gunicorn#3446)) - Print exception details to stderr when worker fails to boot ([PR #​3443](benoitc/gunicorn#3443)) - Fix `unreader.unread()` to prepend data to buffer instead of appending ([PR #​3442](benoitc/gunicorn#3442)) - Prevent `RecursionError` when pickling Config objects ([PR #​3441](benoitc/gunicorn#3441)) - Use proper exception chaining with `raise from` in glogging.py ([PR #​3440](benoitc/gunicorn#3440)) #### Installation ```bash pip install gunicorn==24.1.0 ``` Or use the official Docker image: ```bash docker pull ghcr.io/benoitc/gunicorn:24.1.0 ``` ### [`v24.0.0`](https://github.com/benoitc/gunicorn/releases/tag/24.0.0) [Compare Source](benoitc/gunicorn@23.0.0...24.0.0) #### New Features - **ASGI Worker (Beta)**: Native asyncio-based ASGI support for running async Python frameworks like FastAPI, Starlette, and Quart without external dependencies - HTTP/1.1 with keepalive connections - WebSocket support - Lifespan protocol for startup/shutdown hooks - Optional uvloop for improved performance - **uWSGI Binary Protocol**: Support for receiving requests from nginx via `uwsgi_pass` directive - **Documentation Migration**: Migrated to MkDocs with Material theme #### 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) #### Install ``` pip install gunicorn==24.0.0 ``` </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC41Mi4wIiwidXBkYXRlZEluVmVyIjoiNDQuNTIuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsidHlwZS9kZXBlbmRlbmNpZXMiXX0=--> Reviewed-on: https://git.tainton.uk/repos/rsu/pulls/16 Co-authored-by: renovate[bot] <renovate-bot@git.tainton.uk>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When a request processed by a dirty worker reaches dirty_timeout, arbiter responds to a client before that worker writes its response. However, the client still writes its response, and arbiter will later return that response to wrong requests and clients.
Example timeline:
T=0: client sends a request R1.
T=0 (more or less): R1 gets put to a worker’s W1 queue.
T=0: worker starts processing R1.
T=1: client sends R2.
T=120 (our dirty_timeout value): arbiter gets to asyncio.TimeoutError in _execute_on_worker, returns DirtyTimeoutError to a client for R1.
T=120.0001: worker writes its response to R1 to the socket.
T=120.0002: worker starts processing R2.
T=125: arbiter reads response to R1 from the socket.
T=125: arbiter returns this R1 response to a R2 client.
Etc.: now arbiter will continue returning R(N) responses to an R(N+1) client.
This has fixed the problem in tests in our app, but I don’t know if it is the best possible approach (maybe this breaks other use cases where this connection should stay open on timeout errors?).
Alternative, I guess, there could be a check (in lines 555 and below) if
request_idmatches what’s been written by a worker.