316 Commits

Author SHA1 Message Date
Benoit Chesneau
8d08aaa2cb Fix --limit-request-line 0 to mean unlimited
Per documentation, limit_request_line=0 means unlimited. The code was
incorrectly treating 0 as "use default max" by checking <= 0 instead
of < 0.

For the fast C parser (gunicorn_h1c), which doesn't support 0 as
unlimited, pass a large value (1MB) instead. This applies to both
WSGI workers (http/message.py) and ASGI workers (asgi/protocol.py).

Fixes #3563
2026-03-26 23:42:14 +01:00
Benoit Chesneau
d40a374547 Fix pytest-asyncio configuration and treq_asgi hex escapes
- Add asyncio_mode = "auto" to pytest configuration for async tests
- Update treq_asgi.py badrequest class to support hex escapes
2026-03-26 17:59:40 +01:00
Benoit Chesneau
da8bd4850a Remove unused AsyncRequest class
AsyncRequest was the legacy pull-based async HTTP parser, now replaced
by the push-based CallbackRequest/PythonProtocol. Remove the unused
code and associated tests.
2026-03-26 16:08:35 +01:00
Benoit Chesneau
b00f125755 Integrate gunicorn_h1c 0.6.3 with InvalidChunkExtension support
Update to gunicorn_h1c >= 0.6.3 which adds InvalidChunkExtension
validation for rejecting chunk extensions with bare CR bytes per
RFC 9112.

Changes:
- Update pyproject.toml to require gunicorn_h1c >= 0.6.3
- Add InvalidChunkExtension exception to gunicorn/asgi/parser.py
- Handle InvalidChunkExtension from both Python and C parsers in protocol.py
- Add chunk extension validation tests
- Update treq.py badrequest class to support hex escapes
2026-03-26 15:46:51 +01:00
Benoit Chesneau
d43acb8fe0 Update to gunicorn_h1c >= 0.6.2 for asgi_headers support
- Use asgi_headers property (lowercase names) from fast parser
- Bump version to 25.3.0
- Update changelog with all changes for this release
2026-03-26 15:45:11 +01:00
Benoit Chesneau
cbd27e82a2
Merge pull request #3559 from benleembruggen/fix/http2-asgi-body-duplication
fix: prevent HTTP/2 ASGI body duplication in receive()
2026-03-26 14:38:53 +01:00
Benoit Chesneau
389438fb5a Require gunicorn_h1c >= 0.6.0 for finish() method support
Update minimum version requirement for the fast HTTP parser to 0.6.0
which includes the finish() method for EOF handling in chunked encoding.
2026-03-26 14:12:50 +01:00
Benoit Chesneau
1f8e60c199 Add finish() method to ASGI callback parser for EOF handling
Handle chunked encoding edge case where connection closes before
final CRLF after zero-chunk. Skip WSGI-specific tests (casefold,
underscore headers) that don't apply to ASGI.
2026-03-26 12:13:50 +01:00
Benoit Chesneau
ffcebce4a7 Fix ASGI callback parser header validation
Add security checks to PythonProtocol per RFC 9110/9112:
- Reject duplicate Content-Length headers
- Reject CL + TE combinations
- Reject chunked in HTTP/1.0
- Reject stacked chunked encoding
- Validate Transfer-Encoding values
- Strict chunk size validation

Add PROXY protocol v1/v2 support to callback parser.

Add treq-based test infrastructure for ASGI parser.
2026-03-26 06:32:15 +01:00
Ben Leembruggen
8fba44cf02 fix: prevent HTTP/2 ASGI request body duplication
receive_data() stores every DATA frame in both _body_chunks (list)
and request_body (BytesIO). The receive() closure in
_handle_http2_request() has two read paths: a streaming path that
pops from _body_chunks, and a fast path that reads from BytesIO.

After the streaming path consumed the body, the fast path could
re-read the same data from BytesIO because body_received was never
set in the streaming return path. This caused the application to
receive a doubled request body (e.g. 18 bytes sent, 36 bytes
received), breaking JSON parsing with "Extra data" errors.

Fix: set body_received = True in the streaming path when
_body_complete is True, preventing the fast path from re-reading
already-consumed data.

Fixes #3558
2026-03-26 14:15:14 +11:00
Benoit Chesneau
c32903f0eb Update Docker images to Python 3.14 2026-03-25 13:33:49 +01:00
r266-tech
f8fca7a72f
fix: add __iter__ and __next__ to FileWrapper for PEP 3333 compliance (#3550)
* fix: add __iter__ and __next__ to FileWrapper for PEP 3333 compliance

The WSGI spec (PEP 3333) requires that wsgi.file_wrapper return an
iterable object. Gunicorn's FileWrapper only implemented __getitem__,
which technically makes it iterable via old-style iteration but breaks
code that explicitly relies on the iterator protocol (e.g., calling
iter() or using next()).

This adds __iter__ (returning self) and __next__ to make FileWrapper
a proper iterator, maintaining backward compatibility with existing
__getitem__-based usage.

Fixes #3396

* Fix lint: move imports to top of file

---------

Co-authored-by: contributor <noreply@users.noreply.github.com>
Co-authored-by: Benoit Chesneau <bchesneau@gmail.com>
2026-03-24 22:38:16 +01:00
Benoit Chesneau
0ad47db800
Use user-writable default path for control socket (#3551)
The previous default /run/gunicorn.ctl requires root permissions.
Now uses $XDG_RUNTIME_DIR/gunicorn.ctl if available, otherwise
$HOME/.gunicorn/gunicorn.ctl. This works on Linux, FreeBSD, OpenBSD,
and macOS without requiring elevated privileges.

- Add _get_default_control_socket() helper in config.py
- Create parent directory automatically with 0o700 permissions
- Update gunicornc CLI to use the same default path
- Add unit tests for path selection and directory creation
2026-03-23 20:08:03 +01:00
Benoit Chesneau
3568af1388 Skip SIGINT shutdown test on PyPy
SIGINT handling differs on PyPy and can cause flaky test failures.
The SIGTERM test covers the same graceful shutdown behavior reliably.
2026-03-23 14:10:05 +01:00
Benoit Chesneau
f9ca296d21 Fix WebSocket and body receiver issues in ASGI protocol
- Fix body receiver timeout handling to prevent infinite loops
- Add WebSocket data forwarding via callbacks instead of StreamReader
- Fix HTTP/2 stream race condition where DATA frames arrive before first read
- Update WebSocketProtocol constructor (removed reader parameter)
2026-03-23 13:38:47 +01:00
Benoit Chesneau
241c479701 Fix WebSocket race condition in callback-based _read_exact()
Add double-check after clearing _data_event to prevent deadlock when
data arrives between clear() and wait(). The race condition occurred
when:
1. Task A checks buffer, needs more data
2. Task A clears _data_event
3. Task B (feed_data) sets event
4. Task A awaits on cleared event - deadlock

The fix re-checks the buffer after clear() to catch data that arrived
in the race window.

Also adds tests for edge cases: race condition simulation, EOF during
wait, fragmented message reassembly, and control frames during
fragmentation.
2026-03-23 13:08:57 +01:00
Benoit Chesneau
f76a4942c3 Remove eventlet from test dependencies 2026-03-22 17:50:32 +01:00
Benoit Chesneau
4bff2f37d1 Add raw_path to mock requests in tests 2026-03-22 17:07:08 +01:00
Benoit Chesneau
ba1aaa5e33 Fix non-ASCII URL handling in ASGI worker
Percent-decode path to UTF-8 and preserve raw_path as original bytes
per ASGI spec. Fixes #3543
2026-03-22 16:35:28 +01:00
Benoit Chesneau
4ce6aa1f3e Fix limit handling and add default max limit tests
- LimitRequestLine now accepts optional max_size parameter
- Use default max limits when limit_request_line or limit_request_field_size is 0
- Add tests validating default max enforcement (8190 bytes)
- Handle alternate exceptions from fast parser in test_invalid_requests
2026-03-22 16:17:55 +01:00
Benoit Chesneau
03cc85ef48 Integrate gunicorn_h1c 0.4.1 exception types and limit parameters
Require gunicorn_h1c >= 0.4.1 for fast parser mode. Add new exception
types and limit parameters to PythonProtocol for parity with C parser.
Update tests to parametrize across both parser implementations.
2026-03-22 13:43:18 +01:00
Benoit Chesneau
f308e7abfa Add parametrized tests for ASGI callback parsers
Add test suite that exercises both PythonProtocol and H1CProtocol
implementations with identical test cases using pytest parametrization.
Tests cover request line parsing, headers, body handling (Content-Length
and chunked), connection handling, parser reset, and callback behavior.
2026-03-22 09:23:47 +01:00
Benoit Chesneau
ae7653057f Add callback-based HTTP parser for ASGI protocol
Add PythonProtocol class that mirrors H1CProtocol callback interface:
- Callbacks: on_message_begin, on_url, on_header, on_headers_complete,
  on_body, on_message_complete
- Properties: method, path, http_version, headers, content_length,
  is_chunked, should_keep_alive
- Methods: feed(data), reset()
- Supports Content-Length and chunked transfer encoding

Add CallbackRequest adapter for building requests from parser state.
Works with both H1CProtocol (C extension) and PythonProtocol.

Add unit tests for PythonProtocol and CallbackRequest.
2026-03-21 23:24:23 +01:00
Benoit Chesneau
7818401182 Optimize ASGI protocol for 16x performance improvement
- Replace datetime.now() with time.monotonic() for request timing
- Add access_log_enabled property to skip log work when disabled
- Rewrite BodyReceiver with Future-based waiting (no create_task)
- Remove StreamReader for HTTP/1.1, use direct bytearray buffering
- Add BufferReader wrapper for FastAsyncRequest compatibility
- Use pre-cached chunk prefixes in _send_body()
- Convert async methods to sync where no await needed
- Batch response writes (headers + body in single write)

Performance: 4,200 -> 69,500 req/s
2026-03-21 22:20:05 +01:00
Benoit Chesneau
fa967743c0 Optimize ASGI performance with fast parser integration
Wire HttpParser to ASGI hot path, replacing AsyncRequest.parse() with
direct buffer-based parsing. Add FastAsyncRequest wrapper for body
reading. Replace per-request Queue/Task with BodyReceiver for on-demand
body reading. Keep headers as bytes end-to-end to avoid conversion
overhead. Add backpressure control and keepalive timer. Cache response
status lines and Date header.

Benchmark shows 3x improvement: ~875K req/s for simple GET (was ~340K).
2026-03-21 11:36:46 +01:00
Benoit Chesneau
7f175fb171 Add fast HTTP parser support for WSGI workers
- Integrate gunicorn_h1c fast parser into WSGI Request class
- Add _check_fast_parser() and _parse_fast() methods
- Tests use Python parser for consistent validation behavior
- Update config description to reflect all worker types
2026-03-21 09:29:01 +01:00
Benoit Chesneau
24e7ced609
Merge pull request #3520 from benoitc/fix/control-socket-issues
fix(ctl): prevent fork deadlock and file watcher triggers
2026-03-06 23:58:57 +01:00
Benoit Chesneau
089ad45818 fix(ctl): prevent fork deadlock in control socket server
- Use os.register_at_fork() to properly handle fork() with asyncio
- Start control server after initial workers spawn, not before
- Change default socket path to /run/gunicorn.ctl (like BIRD)
- Add integration tests for sync, gthread, and gevent workers

Fixes #3509
2026-02-26 20:54:35 +01:00
Benoit Chesneau
cef6b337d8
Merge pull request #3519 from benoitc/fix/gthread-slow-client-resilience
fix(gthread): prevent thread pool exhaustion from slow clients
2026-02-20 10:18:08 +01:00
Benoit Chesneau
b5f127e99b fix(gthread): prevent thread pool exhaustion from slow clients
Add a timeout when waiting for initial request data in worker threads.
If no data arrives within 5 seconds, the connection is deferred back
to the main poller instead of blocking the thread indefinitely.

This fixes a regression from v24 where connections were submitted
directly to the thread pool after accept(). In v23, connections were
registered with the poller first and only submitted when data arrived.

The new hybrid approach maintains the performance benefits for fast
clients (immediate processing) while protecting against slow-client
scenarios that can exhaust the thread pool and cause health check
timeouts.

Changes:
- Add _DEFER sentinel and DEFAULT_WORKER_DATA_TIMEOUT constant
- Add TConn.wait_for_data() method using selectors
- Add TConn.data_ready flag to track data availability
- Add pending_conns deque for deferred connections
- Add on_pending_socket_readable() callback
- Add murder_pending() to clean up timed-out pending connections
- Modify handle() to wait for data with timeout before processing
- Modify finish_request() to handle _DEFER and register with poller

Fixes #3518
2026-02-19 15:21:01 +01:00
Benoit Chesneau
b3b98b1322 perf(asgi): optimize HTTP parser for improved performance
- Read chunk size lines and trailers in 64-byte blocks instead of 1 byte
  at a time, pushing back excess data to the unreader buffer
- Reuse BytesIO buffers with truncate/seek instead of creating new
  objects to reduce GC pressure in AsyncUnreader
- Use bytearray.find() directly instead of converting to bytes first
  in header parsing loop
- Use index-based iteration for header parsing instead of list.pop(0)
  which is O(n) per pop vs O(1) for index access

Add tests for the optimized parsing code paths.
2026-02-18 10:00:46 +01:00
Benoit Chesneau
63df19bd5c fix(tests): use process groups for reliable signal handling in PyPy
- Use preexec_fn=os.setsid to create new process group
- Send signals to process group with os.killpg() instead of single process
- Add explicit timeout and graceful-timeout to gunicorn command
- Fixes test failures on PyPy 3.10 where signals weren't propagating properly
2026-02-13 11:02:10 +01:00
Benoit Chesneau
cd77bcc941 fix(tests): increase wait time for all server tests 2026-02-13 10:48:41 +01:00
Benoit Chesneau
02ea9855c1 fix(tests): improve server test reliability on FreeBSD 2026-02-13 10:37:22 +01:00
Benoit Chesneau
7486baa0ad fix: remove unused imports 2026-02-13 02:35:02 +01:00
Benoit Chesneau
e05e40d19b feat(ctl): add message-based dirty worker management
Replace signal-based dirty add/remove with protocol messages:
- Add MSG_TYPE_MANAGE to dirty protocol for worker management
- Add MANAGE_OP_ADD and MANAGE_OP_REMOVE operation codes
- Add handle_manage_request() in DirtyArbiter
- Update handlers to send messages instead of SIGTTIN/SIGTTOU signals

New workers only load apps that haven't reached their worker limits.
When all apps are at their limits, returns reason in response.
Only increment num_workers when a worker is actually spawned.
2026-02-13 02:25:37 +01:00
Benoit Chesneau
3e6d6b94c5 feat(ctl): query dirty arbiter for worker info in 'show all'
Add MSG_TYPE_STATUS to dirty protocol to allow querying the dirty
arbiter for its workers. The control socket now connects to the
dirty arbiter socket to retrieve worker information.
2026-02-13 01:52:43 +01:00
Benoit Chesneau
9f7000ff63 feat(ctl): add 'show all' command for process overview
Displays complete hierarchy: arbiter PID, web workers with their
PIDs/status, dirty arbiter PID, and dirty workers with their apps.
2026-02-13 01:47:45 +01:00
Benoit Chesneau
a57507c4e5 feat(ctl): add gunicornc control interface
Add a control socket server and CLI client for runtime management
of Gunicorn instances, similar to birdc for BIRD routing daemon.

Features:
- Control socket server running in arbiter process (asyncio/threaded)
- gunicornc CLI with interactive and single-command modes
- JSON protocol with length-prefixed framing
- Commands: show workers/stats/config/listeners/dirty, worker add/remove/kill,
  dirty add/remove, reload, reopen, shutdown
- Stats tracking (uptime, workers spawned/killed, reloads)
- Configurable socket path and permissions

New config options:
- control_socket: Unix socket path (default: gunicorn.ctl)
- control_socket_mode: Socket permissions (default: 0o600)
- --no-control-socket: Disable control socket
2026-02-13 01:38:17 +01:00
Benoit Chesneau
2639215aa3 feat(dirty): add TTIN/TTOU signal support for dynamic worker scaling
Add support for SIGTTIN and SIGTTOU signals to the dirty arbiter,
allowing dynamic scaling of dirty workers at runtime without restarting
gunicorn.

Changes:
- Add TTIN/TTOU to DirtyArbiter.SIGNALS
- Add num_workers instance variable for dynamic count
- Add _get_minimum_workers() to enforce app worker constraints
- Add signal handlers for TTIN (increase) and TTOU (decrease)
- Update manage_workers() to use dynamic count
- Add documentation for dynamic scaling
- Add unit tests for signal handling
- Add Docker integration tests

The minimum worker constraint ensures TTOU cannot reduce workers below
what apps require (e.g., if an app has workers=3, minimum is 3).

Closes #3489
2026-02-12 23:52:12 +01:00
Benoit Chesneau
709a6ad159
feat(dirty): add stash - global shared state between workers (#3503)
* feat(dirty): add stash - global shared state between workers

Add a simple key-value store (stash) that allows dirty workers to share
state through the arbiter. Tables are stored directly in arbiter memory
for fast access and simplicity.

Features:
- Auto-create tables on first access
- Dict-like interface via stash.table()
- Pattern matching for keys (glob patterns)
- Module-level API: stash.put(), stash.get(), stash.delete(), etc.

Usage:
    from gunicorn.dirty import stash

    stash.put("sessions", "user:1", {"name": "Alice"})
    user = stash.get("sessions", "user:1")

    # Or dict-like
    sessions = stash.table("sessions")
    sessions["user:1"] = {"name": "Alice"}

New files:
- gunicorn/dirty/stash.py - Client API and StashTable class
- Protocol additions for MSG_TYPE_STASH and STASH_OP_* codes

Note: Tables are ephemeral - lost if arbiter restarts.

* test(dirty): add tests for stash protocol and encoding

Test coverage for:
- Stash message creation and encoding
- Protocol constants (MSG_TYPE_STASH, STASH_OP_*)
- Error classes (StashError, StashTableNotFoundError, StashKeyNotFoundError)
- StashTable dict-like interface
- Edge cases: unicode, complex values, special patterns

* example(dirty): add stash usage example and integration tests

- Add SessionApp to dirty_app.py demonstrating stash usage
- Add /session/* endpoints to wsgi_app.py
- Add test_stash_integration.py with Docker tests
- Update docker-compose.yml with stash-test service
- Fix: Set GUNICORN_DIRTY_SOCKET in dirty arbiter for worker access

* docs(dirty): add stash documentation
2026-02-12 21:45:49 +01:00
Benoit Chesneau
68ce658f5d fix(dirty): convert dict int keys to strings in TLV encoder
JSON serializes all dict keys as strings, so for compatibility the TLV
encoder should do the same. This fixes an error when tasks return dicts
with integer keys (e.g., aggregation results grouped by numeric ID).
2026-02-11 23:39:53 +01:00
Benoit Chesneau
477b7479cc feat(dirty): update client for binary protocol
Update client and streaming tests to work with the binary protocol:
- Update MockStreamWriter/MockStreamReader to use BinaryProtocol
- Replace string request IDs with integers
- Update test assertions to decode binary protocol messages
- Use HEADER_SIZE and decode_header/decode_message instead of old API
2026-02-11 23:12:44 +01:00
Benoit Chesneau
98b1b649c2 feat(dirty): update arbiter for binary protocol
Update arbiter tests to work with the binary protocol:
- Update MockStreamWriter to decode binary messages
- Import binary protocol constants from module level
2026-02-11 23:03:40 +01:00
Benoit Chesneau
6d2139bb6c feat(dirty): update worker for binary protocol
Update worker tests to work with the binary protocol:
- Use integer request IDs instead of strings
- Update MockStreamWriter to decode binary messages
- Import binary protocol constants from module level
2026-02-11 23:01:21 +01:00
Benoit Chesneau
1665857c0e feat(dirty): implement binary protocol
Replace JSON-based protocol with binary format using 16-byte header:
- Magic bytes (GD), version, message type, payload length, request ID
- TLV-encoded payloads for efficient binary data transfer
- No base64 encoding needed for binary data
- Backwards compatible API (DirtyProtocol alias, dict-based interface)

Header format inspired by OpenBSD msgctl/msgsnd.
2026-02-11 22:58:43 +01:00
Benoit Chesneau
0e0dc669c8 feat(dirty): add TLV binary encoder/decoder
Implement TLV (Type-Length-Value) serialization layer for the binary
dirty worker protocol. This enables efficient binary data transfer
without base64 encoding overhead.

Supported types:
- None, bool, int64, float64
- bytes (raw binary, no encoding needed)
- string (UTF-8)
- list, dict (nested structures)

Inspired by OpenBSD msgctl/msgsnd message format.
2026-02-11 22:55:03 +01:00
Benoit Chesneau
9508df658d test: increase CI timeout for signal tests on PyPy 2026-02-06 09:00:29 +01:00
Benoit Chesneau
95b7ffeeaa chore: prepare release 25.0.2
- Bump version to 25.0.2
- Update copyright year to 2026 in LICENSE and NOTICE
- Add license headers to all Python source files
- Add changelog entry for 25.0.2
2026-02-06 08:21:18 +01:00
Benoit Chesneau
e780508f56 fix: resolve ASGI concurrent request failures through nginx proxy
- Fix nginx config to use keepalive with upstream (was sending
  Connection: close which caused premature connection closure)
- Add _safe_write() to handle socket errors (EPIPE, ECONNRESET,
  ENOTCONN) gracefully when client disconnects
- Fix ASGI scope server/client to always be 2-tuples for IPv6
  compatibility (IPv6 sockets return 4-tuples)
- Add write_eof() before close() to ensure buffered data is flushed
- Bind to [::] for dual-stack IPv4/IPv6 support in test containers
2026-02-06 01:57:28 +01:00