3321 Commits

Author SHA1 Message Date
Benoit Chesneau
89a0a46722 Add HTTP/2 core protocol implementation
Core classes for HTTP/2 server-side protocol handling:

- HTTP2Stream: Stream state management matching RFC 7540 Section 5.1
  - StreamState enum for proper lifecycle tracking
  - Request/response tracking and body buffering
  - Pseudo-header extraction for :method, :path, etc.
  - Proper state transitions for half-close semantics

- HTTP2Request: Request interface compatibility layer
  - Wraps HTTP2Stream for worker consumption
  - HTTP2Body provides file-like interface for request body
  - Converts HTTP/2 pseudo-headers to standard attributes
  - Transforms lowercase headers to uppercase for WSGI
  - Adds HOST header from :authority pseudo-header

- HTTP2ServerConnection: h2 library integration
  - Lazy import of h2 for graceful degradation
  - Connection initialization with configurable settings
  - Stream management for concurrent requests
  - Event handling for HEADERS, DATA, RST_STREAM, GOAWAY
  - Response sending with proper frame generation
  - Flow control window management with chunked data sending

- get_parser() extension for HTTP/2 dispatch
2026-01-27 09:57:01 +01:00
Benoit Chesneau
c711d9fb6f Add HTTP/2 dependency and configuration
- Add optional h2 dependency for HTTP/2 support
- Add http2 module skeleton with availability check and errors
- Add HTTP/2 configuration settings (max_concurrent_streams,
  initial_window_size, max_frame_size, max_header_list_size)
- Add ALPN support to SSL context for HTTP/2 negotiation
2026-01-27 09:57:01 +01:00
Benoit Chesneau
1fe9e5816e
Merge pull request #3460 from benoitc/feature/dirty-arbiters
feat: add dirty arbiters for long-running blocking operations
2026-01-27 09:45:05 +01:00
Benoit Chesneau
891990befa docs(dirty): update streaming from future to current feature 2026-01-26 09:39:46 +01:00
Benoit Chesneau
bbae34b951
Fix setproctitle initialization with systemd socket activation (#3465)
Force early setproctitle initialization by calling getproctitle()
immediately after import. This ensures setproctitle captures the
argv/environ memory layout before systemd.listen_fds() modifies
the environment by removing LISTEN_FDS and LISTEN_PID.

Without this fix, if LISTEN_FDS is the first environment variable,
setproctitle fails to detect argv correctly and silently fails.

Fixes #3430
2026-01-25 15:18:04 +01:00
Benoit Chesneau
29830ccc2f Increase CI timeout for signal integration tests
PyPy signal handling can be slower in CI environments, so increase
the timeout from 30 to 60 seconds to avoid flaky test failures.
2026-01-25 15:08:05 +01:00
Benoit Chesneau
6575d86251 Add docker integration tests for simple ASGI (HTTP protocol)
Tests the ASGI worker with direct HTTP requests without uWSGI protocol.
Includes tests for GET, POST, query strings, path handling, keepalive,
large bodies, and custom headers.
2026-01-25 15:03:12 +01:00
Benoit Chesneau
47b1cb82b8 Fix missing _expected_100_continue attribute in UWSGIRequest
The wsgi.create() function expects req._expected_100_continue but
UWSGIRequest didn't have this attribute, causing an AttributeError.
Set to False since uWSGI runs behind a frontend server that handles
100-continue negotiation.
2026-01-25 14:51:40 +01:00
Benoit Chesneau
8663740907
Add uWSGI protocol support to ASGI worker (#3467)
Add uWSGI protocol support to ASGI worker

- Implements AsyncUWSGIRequest class extending sync UWSGIRequest to reuse parsing logic with async I/O
- ASGI protocol handler selects between HTTP and uWSGI based on --protocol config option
- Allows gunicorn's ASGI worker to receive requests from nginx using uwsgi_pass directive
- Includes unit tests and Docker integration tests
2026-01-25 14:45:07 +01:00
Benoit Chesneau
35559a87bd test: add conftest.py to fix tests module import path 2026-01-25 10:38:07 +01:00
Benoit Chesneau
d85be79102 docs: regenerate settings.md with dirty arbiter options 2026-01-25 10:33:38 +01:00
Benoit Chesneau
634290fc75 fix(dirty): resolve pylint warnings in dirty module 2026-01-25 10:29:52 +01:00
Benoit Chesneau
cc39ed922e examples(dirty): add streaming chat demo with SSE
Add a lightweight chat simulator demonstrating dirty worker streaming:
- Token-by-token SSE streaming via async generators
- FastAPI endpoint with browser UI
- Multiple canned responses based on keywords
- Docker deployment with docker-compose
- Integration tests for SSE protocol

Update docs/content/dirty.md to link to both examples.
2026-01-25 10:26:12 +01:00
Benoit Chesneau
ae8665c4d5 docs(dirty): expand architecture, signal handling, and health monitoring
- Enhance architecture diagram with Unix socket paths, signal flow, and
  heartbeat indicators
- Add process relationships table
- Expand signal handling section with flow diagram, comprehensive signal
  reference table, and async handling explanation
- Add new "Liveness and Health Monitoring" section covering heartbeat
  mechanism, timeout detection, parent death detection, orphan cleanup,
  and respawn behavior
- Add link to embedding service example
2026-01-25 10:23:25 +01:00
Benoit Chesneau
5e3c07d11d test(dirty): add Docker-based parent death integration tests
Add comprehensive Docker integration tests verifying dirty arbiter
lifecycle under realistic conditions:
- Parent death detection via ppid monitoring
- Orphan cleanup on restart
- Dirty arbiter respawning after crash
- Graceful shutdown with SIGTERM

Also fix race condition in manage_workers() by checking self.alive
before spawning new workers during shutdown.
2026-01-25 10:23:25 +01:00
Benoit Chesneau
79f85af55e fix(dirty): detect parent death and self-terminate
Add ppid monitoring to dirty arbiter's worker monitor loop. If the
main arbiter dies unexpectedly (SIGKILL, crash, OOM), the dirty
arbiter detects the parent change and shuts itself down gracefully.

This complements the existing orphan cleanup on startup.
2026-01-25 10:23:25 +01:00
Benoit Chesneau
b67ff0b31d test: fix warnings and flaky tests in dirty arbiter tests
- Close coroutines in mocked asyncio.run to prevent "never awaited" warning
- Fix flaky integration tests with proper async cleanup and try/finally
- Add uvloop to testing dependencies so uvloop test runs
- Add pytest warning filter for eventlet/asyncio incompatibility
2026-01-25 10:23:25 +01:00
Benoit Chesneau
e21d23bfa6 fix(dirty): add orphan cleanup via well-known PID file
When the main arbiter crashes and restarts, orphaned dirty arbiters
may continue running. This adds detection and cleanup:

- Add well-known PID file location based on proc_name
- Dirty arbiter writes PID on startup, removes on exit
- Main arbiter checks for orphans on fresh start (not USR2)
- Uses self.proc_name for USR2 compatibility (myapp vs myapp.2)

During USR2 upgrade, old and new dirty arbiters coexist with
separate PID files, preventing the old from removing the new's file.
2026-01-25 10:23:25 +01:00
Benoit Chesneau
f6418d4eb0 feat(dirty): add streaming support and async client benchmarks
Add support for streaming responses when dirty app actions return
generators (sync or async). This enables real-time delivery of
incremental results for use cases like LLM token generation.

Features:
- Streaming protocol with chunk/end/error message types
- Worker support for sync and async generators
- Arbiter forwarding of streaming messages
- Deadline-based timeout handling
- Async client streaming API

Protocol:
- Chunk messages (type: "chunk") contain partial data
- End messages (type: "end") signal stream completion
- Error messages can occur mid-stream

New files:
- benchmarks/dirty_streaming.py: Streaming benchmark suite
- tests/dirty/test_*_streaming*.py: Streaming test coverage
- docs/content/dirty.md: Streaming documentation with examples
2026-01-25 10:23:25 +01:00
Benoit Chesneau
62a29bd0e1 test(dirty): add multi-app routing tests
Add tests to verify that when multiple dirty apps are configured,
messages are correctly routed to the appropriate app based on app_path.

New files:
- tests/support_dirty_apps.py: CounterApp and EchoApp test apps
- tests/dirty/test_multi_app_routing.py: 13 routing tests covering
  app loading, routing, state separation, error handling, and
  concurrent requests
2026-01-25 10:23:25 +01:00
Benoit Chesneau
0e05c824e9 feat(examples): add FastAPI embedding service with Docker testing
Add a complete example demonstrating dirty workers with sentence-transformers
for text embeddings via FastAPI:

- EmbeddingApp DirtyApp that loads and manages the ML model
- FastAPI endpoints for /embed and /health
- Docker and docker-compose configuration
- Integration tests with numpy similarity checks
- GitHub Actions CI workflow
2026-01-25 10:23:25 +01:00
Benoit Chesneau
ce2e06ceba refactor(dirty): replace per-worker locks with queues
Replace lock-based request serialization with queue-based approach:
- Each worker now has a dedicated asyncio.Queue and consumer task
- route_request() submits (request, future) to queue and awaits future
- Consumer task processes requests sequentially per worker
- No lock contention - pure async queue operations

Benefits:
- Clearer separation of concerns
- Better visibility into request backlog (queue.qsize())
- Eliminates lock contention under high concurrency

Changes:
- worker_locks dict replaced with worker_queues and worker_consumers
- Added _start_worker_consumer() to create queue and consumer per worker
- Added _execute_on_worker() for actual worker communication
- Updated _cleanup_worker() to cancel consumer tasks
- Updated stop() to cancel all consumers before shutdown

Benchmark results (4 workers, isolated):
- throughput_10ms: 333 req/s, 0 failures
- overload_10ms (200 clients): 334 req/s, 0 failures
- All tests pass with perfect round-robin distribution
2026-01-25 10:23:25 +01:00
Benoit Chesneau
56cc094b68 feat(dirty): add benchmark suite and fix arbiter concurrency
Add comprehensive benchmark suite for stress testing the dirty pool:
- dirty_bench_app.py: Configurable benchmark app with sleep/cpu/mixed/payload tasks
- dirty_benchmark.py: Main runner with isolated and integrated test modes
- dirty_bench_wsgi.py: WSGI app for HTTP integration testing
- dirty_bench_gunicorn.py: Gunicorn config for integration benchmarks

Fix arbiter concurrency issues:
- Add per-worker locks to serialize requests and prevent read conflicts
- Implement round-robin worker selection for linear throughput scaling

The benchmark suite supports:
- Quick smoke tests (--quick)
- Full isolated benchmarks (--isolated)
- Configuration sweeps (--config-sweep)
- Payload size tests (--payload-tests)
- Integration tests with wrk (--integrated)
2026-01-25 10:23:25 +01:00
Benoit Chesneau
9b0e87deb8 docs(dirty): clarify application initialization sequence 2026-01-25 10:23:25 +01:00
Benoit Chesneau
c914f336b8 docs: add dirty arbiters to navigation and changelog 2026-01-25 10:23:25 +01:00
Benoit Chesneau
3d9382b07c refactor: address PR review comments
1. Split respawning logic from reap_dirty_arbiter() into manage_dirty_arbiter()
   to avoid respawning during shutdown/re-exec (follows reap_workers pattern)

2. Reduce public API surface in __all__:
   - Keep errors, DirtyApp, client functions as public
   - Internal protocol helpers remain importable from submodules
   - DirtyArbiter and set_dirty_socket_path kept for gunicorn core
2026-01-25 10:21:18 +01:00
Benoit Chesneau
06aba09251 feat(dirty): add thread pool with execution timeout control
- Use dirty_threads config for thread pool size (default: 1)
- Enforce dirty_timeout at worker level via asyncio.wait_for
- Heartbeat runs independently, not blocked by task execution
- Document thread safety and state persistence in docstrings
2026-01-25 10:21:18 +01:00
Benoit Chesneau
21f769ce16 fix: resolve lint issues in dirty arbiter modules 2026-01-25 10:21:18 +01:00
Benoit Chesneau
77222b8017 feat: add dirty arbiters for long-running blocking operations
Introduce Dirty Arbiters - a separate process pool for executing
long-running, blocking operations (AI model loading, heavy computation)
without blocking HTTP workers. Inspired by Erlang's dirty schedulers.

Key features:
- Completely separate from HTTP workers - can be killed/restarted independently
- Stateful - loaded resources persist in dirty worker memory
- Message-passing IPC via Unix sockets with JSON serialization
- Explicit execute() API from HTTP workers
- Asyncio-based for clean concurrent handling

Architecture:
- DirtyArbiter: manages the dirty worker pool, routes requests
- DirtyWorker: executes functions, maintains state, handles requests
- DirtyClient: sync/async API for HTTP workers to call dirty apps
- DirtyProtocol: length-prefixed JSON messages over Unix sockets
- DirtyApp: base class for dirty applications

Configuration options:
- dirty_apps: list of import paths for dirty applications
- dirty_workers: number of dirty workers (default: 0)
- dirty_timeout: task timeout in seconds (default: 300)
- dirty_graceful_timeout: shutdown timeout (default: 30)

Lifecycle hooks:
- on_dirty_starting(arbiter)
- dirty_post_fork(arbiter, worker)
- dirty_worker_init(worker)
- dirty_worker_exit(arbiter, worker)

Includes comprehensive test suite with 164 tests covering:
- Protocol encoding/decoding
- Worker and arbiter lifecycle
- Client sync/async APIs
- Signal handling
- Error handling and timeouts
- Integration tests
2026-01-25 10:21:18 +01:00
Benoit Chesneau
be6f3b97ab Disable setproctitle on macOS to prevent segfaults
setproctitle causes segfaults on macOS due to fork() safety issues
introduced in newer macOS versions. The mere import of setproctitle
can trigger crashes in forked worker processes.

Fixes #3021
2026-01-25 09:57:20 +01:00
Paul J. Dorn
481dbf2e9b
Publish full exception when the application fails to load (#3462)
* Python3: refactor returned traceback

Exceptions provide __traceback__ reference since Python 3.0
(and creating cyclic references has not been big deal since Python 2.2)

* --reload: publish entire exception, not just traceback

This is dangerous insofar as the exception text is more
likely to contain secrets than the quoted lines from traceback are.

However, the difference between the two is minor compared to the
primary danger of enabling this on a production machine, so focus
on that instead!
2026-01-25 09:41:39 +01:00
Benoit Chesneau
98156f9ef6
Merge pull request #3463 from pajod/patch-100-continue
Restrict 100 Continue resonses to reasonable count (1) & situation (HTTP/1.1)
2026-01-25 09:37:49 +01:00
Paul J. Dorn
6d61afab3e forgotten import 2026-01-25 04:11:04 +01:00
Paul J. Dorn
ba336daabe duplicate 100-continue patch for asgi 2026-01-25 03:47:49 +01:00
Paul J. Dorn
88d503ba1c HTTP/1.0 - ignore Expect: 100-continue
* ignore on HTTP/1.0 (would possibly confuse a client or proxy)
* refuse requests with unknown expectations

https://datatracker.ietf.org/doc/html/rfc9110#section-10.1.1
2026-01-24 21:59:02 +01:00
Benoit Chesneau
f0952e5874 docs: add sponsors section to website homepage 2026-01-24 02:19:14 +01:00
Benoit Chesneau
375e79e95b release: bump version to 24.1.1 2026-01-24 02:13:42 +01:00
Benoit Chesneau
ad0c12de98 docs: add sponsors section to README 2026-01-24 02:08:28 +01:00
Benoit Chesneau
70200eef46 chore: add GitHub Sponsors funding configuration 2026-01-24 01:24:44 +01:00
Benoit Chesneau
6841804116 docs: remove incorrect PR reference from Docker changelog entry 2026-01-24 00:02:56 +01:00
Benoit Chesneau
abce0ca9cb docs: add 24.1.1 changelog entry for forwarded_allow_ips fix 2026-01-23 23:53:29 +01:00
Benoit Chesneau
e9a3f30a0f
fix: keep forwarded_allow_ips as strings for backward compatibility (#3459)
The CIDR network support added in 24.1.0 changed forwarded_allow_ips
and proxy_allow_ips from string lists to ipaddress.ip_network objects.
This broke external tools like uvicorn that expect strings.

This fix validates IP/CIDR format during config parsing but keeps the
string representation. Network objects are cached in Config methods
(forwarded_allow_networks() and proxy_allow_networks()) for efficient
IP checking without repeated conversions.

Also uses strict mode for ip_network validation to detect mistakes like
192.168.1.1/24 where host bits are set (should be 192.168.1.0/24).

Fixes #3458
2026-01-23 23:51:25 +01:00
Benoit Chesneau
d73ff4b1d8 docs: update main changelog with 24.1.0 2026-01-23 22:16:37 +01:00
Benoit Chesneau
53f2c31012 ci: allow docs deploy on workflow_dispatch 2026-01-23 22:14:05 +01:00
Benoit Chesneau
eab5f0b1a5 ci: trigger Docker publish on tags with or without v prefix 2026-01-23 21:54:49 +01:00
Benoit Chesneau
a20d3fb220 docs: add Docker image to 24.1.0 changelog 2026-01-23 21:49:50 +01:00
Benoit Chesneau
7ef34796ae docs: add SIGCLD fix to changelog 2026-01-23 21:26:35 +01:00
Benoit Chesneau
3179789f46 fix: handle SIGCLD alias for SIGCHLD on Linux
On Linux, SIGCLD and SIGCHLD are aliases for the same signal number (17).
The SIG_NAMES dict iteration order can map to either name, causing
"Unhandled signal: cld" errors when workers fail during boot.

Fixes #3453
2026-01-23 21:25:07 +01:00
Benoit Chesneau
a3a59b2f56 chore: update version placeholder to 24.1.0 2026-01-23 19:27:14 +01:00
Benoit Chesneau
8b86f6c36d fix: install gunicorn from source in Docker image 2026-01-23 19:26:39 +01:00