- 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.
- Add guides/gunicornc.md with usage examples and command reference
- Update mkdocs.yml navigation to include Control Interface guide
- Update 2026-news.md and news.md changelog with 25.2.0 release
- Regenerate reference/settings.md with control socket settings
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
New Features:
- Dirty Stash: global shared state between workers (#3503)
- Dirty Binary Protocol: TLV encoding for efficient IPC (#3500)
Documentation:
- Fix Markdown formatting in /configure
* 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
- Update test_protocol.py example to use binary protocol
- Add test_binary_data_handling example showing raw bytes transfer
- Update dirty.md to document binary TLV protocol format
- Replace JSON references with binary protocol
- Add Binary Protocol section with header and TLV encoding details
- Add sponsor banner at top of README
- Add Revolut and Open Collective to FUNDING.yml
- Add dedicated sponsor page in docs
- Add sponsor heart link in footer
- Change title from "The Python WSGI Server" to "Serve Python Apps on the Web"
- Remove "Version {{ version }}" text from hero section
- Remove unused .hero__version CSS
- 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
Closes#3484
When a client disconnects during an ASGI request, the worker now:
1. Sends http.disconnect message to the app's receive queue
2. Allows a configurable grace period for cleanup (default: 3 seconds)
3. Only cancels the task after the grace period expires
This follows the ASGI HTTP Connection Scope spec which defines
http.disconnect as the message apps should receive when clients
disconnect: https://asgi.readthedocs.io/en/latest/specs/www.html#disconnect-receive-event
The grace period prevents CancelledError from propagating to async
database operations, allowing SQLAlchemy and other async DB libraries
to properly reset their connection pools.
New config option: --asgi-disconnect-grace-period (default: 3 seconds)
- Fix mobile side menu not displaying on iPad by keeping site_nav
content but hiding sidebar on desktop via CSS
- Fix broken links in asgi.md and guides/http2.md
- Add HTTP/2 guide to navigation
- Add explicit do_handshake() in base_async.py before ALPN check
when do_handshake_on_connect is False
- Mark eventlet worker as deprecated (removal in 26.0)
- Add HTTP/2 gevent example with Docker and tests
- Update documentation to reflect eventlet deprecation
- Remove eventlet websocket example (gevent version exists)
The ALPN fix ensures HTTP/2 works correctly with gevent and eventlet
workers when do_handshake_on_connect config is False (the default).
Without explicit handshake, selected_alpn_protocol() returns None.
- Add three tuning profiles: Conservative, Balanced, High Concurrency
- Document pros/cons and trade-offs for each HTTP/2 setting
- Add Linux kernel tuning parameters for high concurrency
- Add Docker-specific tuning recommendations
- Include benchmark results from h2load testing
Add complete HTTP/2 example in examples/http2_features/:
- ASGI app showing priority access and trailer sending
- Test script using raw h2 library for HTTP/2 testing
- Docker setup for easy testing
- Documentation update referencing the example
The example demonstrates:
- Reading http.response.priority extension in ASGI scope
- Sending http.response.trailers messages
- Multiple streams on the same connection
- Guard early_hints callback against calls after response started
- Fix :authority precedence over Host header per RFC 9113 section 8.3.1
- Add nginx early_hints documentation link
- Use standard port 443 in curl examples
- Add docs/content/guides/http2.md with comprehensive HTTP/2 guide
- Quick start and requirements
- Configuration options and settings
- Worker compatibility matrix
- HTTP 103 Early Hints usage for WSGI and ASGI
- Production deployment with nginx
- Troubleshooting guide
- Update docs/content/asgi.md to reflect HTTP/2 support
- Update docs/content/2026-news.md with 25.0.0 release featuring
HTTP/2 and Early Hints
- Regenerate docs/content/reference/settings.md with HTTP/2 settings