Skip to content

chore: Sync upstream#10

Draft
ankush wants to merge 431 commits into
frappe:masterfrom
benoitc:master
Draft

chore: Sync upstream#10
ankush wants to merge 431 commits into
frappe:masterfrom
benoitc:master

Conversation

@ankush
Copy link
Copy Markdown
Member

@ankush ankush commented Nov 10, 2025

No description provided.

@ankush
Copy link
Copy Markdown
Member Author

ankush commented Nov 10, 2025

No production changes, will wait for a release.

@ankush ankush marked this pull request as draft November 10, 2025 06:05
benoitc and others added 15 commits February 1, 2026 16:54
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](actions/setup-python@v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
…ns/setup-python-6

chore(deps): bump actions/setup-python from 5 to 6
…ns/upload-artifact-6

chore(deps): bump actions/upload-artifact from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
…ns/checkout-6

chore(deps): bump actions/checkout from 4 to 6
Add chunked transfer encoding support to the ASGI worker for HTTP/1.1
streaming responses that don't have a Content-Length header. This fixes
SSE (Server-Sent Events) connections not closing properly.

Without chunked encoding or Content-Length, HTTP/1.1 clients wait for
the connection to close to determine end-of-response, causing streaming
endpoints to hang.

Also updates the celery_alternative example to use FastAPI with the
native ASGI worker and uvloop, demonstrating async task execution with
proper SSE streaming.
- Fix path expectation in test_scope_path_preserved (router strips /http prefix)
- Fix lifespan state check to use scope_state instead of module_state
- Add tolerance for partial failures in proxy concurrent test
- Add retry logic with proper assertions in HTTPS proxy FastAPI test
Update README.md and docs homepage with detailed support messaging
including sponsorship benefits and corporate contact information.
benoitc and others added 11 commits February 2, 2026 23:36
Closes #3482

The dirty module (which uses asyncio and concurrent.futures) was being
imported at gunicorn startup via gunicorn.arbiter. This caused
concurrent.futures to be imported before user code could call
gevent.monkey.patch_all(), breaking gevent's monkey-patching.

Changes:
- gunicorn/arbiter.py: Import DirtyArbiter and set_dirty_socket_path
  lazily inside spawn_dirty_arbiter() instead of at module level
- gunicorn/dirty/worker.py: Import ThreadPoolExecutor lazily inside
  run() method instead of at module level
- Add tests/workers/test_gevent_import_order.py with 5 tests verifying:
  - concurrent.futures is NOT imported when gunicorn.arbiter loads
  - gevent patching works correctly with gunicorn
  - Reproduces the exact scenario from the bug report gist

This ensures gevent's monkey.patch_all() can run before concurrent.futures
is imported, allowing proper patching of threading primitives.
…mport

fix: lazy import dirty module for gevent compatibility
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: graceful disconnect handling for ASGI worker
- 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
Consolidate the repeated pattern for normalizing socket addresses
to ASGI-compatible (host, port) tuples into a single utility function.
- 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
benoitc and others added 30 commits April 20, 2026 07:18
…c-0.6.4

chore: require gunicorn_h1c >=0.6.4 and drop python_only markers
gunicorn_h1c 0.6.5 ships the Content-Length list-form rejection
(h1c #8). The last python_only marker can now come off
rfc9112_smuggle_cl_list_form_01.
…er-h1c-0.6.5

chore: require gunicorn_h1c >=0.6.5 and drop last python_only marker
- WSGI fast parser now applies the same per-header policy as the Python
  parser (Expect, secure_scheme_headers, forwarded_allow_ips trust gate,
  forwarder_headers / header_map). Shared helpers extracted on Message.

- ASGI keepalive no longer resets the parser when the previous request
  body was not fully framed; the connection closes instead, preventing
  request smuggling on pipelined connections.

- BodyReceiver._wait_for_data timeout flips _closed and yields
  http.disconnect rather than synthesizing more_body=False. Timeout
  honors cfg.timeout.

- ASGI chunked encoding now skips HEAD, 204, and 304 (matches
  Response.is_chunked in the WSGI path) via a small helper.

- _setup_callback_parser passes proxy_protocol to PythonProtocol; auto
  falls back to the Python parser when proxy_protocol != off (the C
  parser does not implement PROXY framing). _effective_peername swaps
  the transport peer with the PROXY-supplied client address.

- Parser.finish_body accepts a deadline and a 64KiB byte cap; gthread
  passes a deadline and abandons keepalive on incomplete drain so a
  stalled client cannot tie up a worker thread.
- ASGI keepalive gate now keys on receiver._complete only. _closed is
  overloaded across transport disconnect and receive timeout; treating
  either as 'message complete' would re-enable the smuggling vector
  the previous PR was meant to close.
- Parser.finish_body's 64 KiB byte cap now applies only when an explicit
  deadline is given. Default invocations (notably __next__, used by
  base_async / sync workers) regain the prior unbounded drain so a
  partial drain does not silently desync the next request.
The previous assertion ran immediately after a 2s sleep and raced
the arbiter's socket re-creation on slow runners (observed flake on
FreeBSD 14.2 / Python 3.13). Replace with the wait_for_socket helper
already used elsewhere in the file.
fix: address six WSGI/ASGI parser and protocol findings
RFC 9110 forbids a body for HEAD requests and for 1xx/204/304 status
codes. PR #3614 stopped gunicorn from auto-applying chunked encoding
in those cases, but if the application explicitly emitted a
Content-Length or Transfer-Encoding header (and possibly body bytes),
gunicorn still passed them through. Now strip both headers, force
plain framing, and discard any body the app emits.
A framework bug — say, returning bytes from a HEAD or 204 handler — is
now logged at WARNING level the first time it happens for a request
so the misbehavior is visible without spamming on multi-chunk streams.
fix: drop body framing on HEAD/204/304 even when framework set it
_closed now means the client transport has gone away. Body-wait timeouts
flip a separate _body_wait_expired flag. Both still surface as
http.disconnect to the app, but downstream code can now distinguish 'the
socket is dead' from 'the body never finished framing in time' without
guessing which path set the flag.
…mantic-split

refactor: split BodyReceiver._closed into transport vs body-wait
The smuggling guard added in #3614 reads self._body_receiver after
_handle_http_request returns to refuse keepalive on a body that did
not finish framing.  But _handle_http_request's finally cleared the
receiver before returning, so the gate always saw None and let
keepalive proceed unconditionally.  Move the clear into the
connection loop's per-iteration cleanup (it already had one there
for the same purpose).

Adds an end-to-end regression test that pipelines a partial-body POST
followed by a smuggled GET and asserts the second request is not
served and the transport closes.
…egression

fix: keep _body_receiver alive across the keepalive smuggling gate
The previous test forced http_parser='python' to avoid a hard
dependency on gunicorn_h1c. Now run the same scenario under both
parser implementations so the smuggling guard is exercised on every
supported request-line/header path.
…ast-parser

test: parametrize smuggling regression across python and fast parsers
Three findings against the ASGI PROXY protocol path:

- High: an untrusted peer could send a PROXY v1/v2 header and have the
  client address surfaced to the app. _setup_callback_parser now passes
  proxy_protocol='off' to the parser when the peer is not in
  proxy_allow_ips. _effective_peername adds a defensive re-check.
- Medium: PROXY v1 TCP4/TCP6 addresses were copied as strings without
  validation. Validate with socket.inet_pton, mirroring the WSGI parser.
- Medium: PROXY v2 quietly mapped non-STREAM (DGRAM) protocols to
  UDP4/UDP6. gunicorn is an HTTP server; reject non-STREAM with
  InvalidProxyHeader, mirroring the WSGI parser.
RFC 9110 §6.4.2 forbids Content-Length only on 1xx and 204 responses.
HEAD MAY include the Content-Length the same GET would return, and 304
MAY include the Content-Length the unconditional response would carry.
WSGI preserves app-supplied Content-Length on those statuses; ASGI was
stripping it indiscriminately for any no-body response.

Split the predicate: _response_forbids_content_length() returns True
only for 1xx/204; _strip_body_framing_headers(headers, status) always
strips Transfer-Encoding (no body, no chunked terminator) and strips
Content-Length only when forbidden.
…nd-parsing

fix: enforce proxy_allow_ips and tighten PROXY parsing in ASGI
…h-on-head-and-304

fix: keep Content-Length on HEAD and 304 responses
- per_app_allocation: move host port from 8001 to 28001. OrbStack reserves
  8001 on macOS for vcom-tunnel which makes 'Bind: port already allocated'
  the default failure mode.
- dirty_ttin_ttou: pin BASE_URL to 127.0.0.1 instead of 'localhost'. macOS
  resolves 'localhost' to ::1 first; Docker Desktop / OrbStack only forward
  host ports on IPv4 so the IPv6 attempt resets and the test fixture treats
  the service as unhealthy.
- dirty_ttin_ttou: add setproctitle to the test image. The TTIN/TTOU tests
  count workers via 'pgrep -f dirty-worker', which only matches once
  gunicorn's util._setproctitle has actually renamed the processes.
- /unlimited and /limited handlers passed the data dict where the dirty
  client expected the action (method) name, surfacing as a 500 from
  getattr(self, action) on the dirty worker. Pass 'process' as the
  action so the call routes to DirtyApp.process(data).
- TestUnlimitedApps now bumps worker count via TTIN and polls both apps
  for readiness before each test. The preceding TTOU-spam test pins the
  worker count at the LimitedTask floor (2) and the arbiter takes a
  moment to rebind apps to the surviving workers; the previous tests
  raced that rebind and saw 'No workers available'.
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.
test: unblock docker fixtures on macOS hosts
…op-test-deps

chore: remove eventlet worker; add h2 and uvloop to test deps
- Bump version_info to (26, 0, 0)
- Update SECURITY.md supported releases (26.0.0, 25.3.0)
- Add 26.0.0 entry to news.md and 2026-news.md covering eventlet
  removal, ASGI framework compatibility suite, RFC 9110/9112
  request-target and header hardening, smuggling fixes, HEAD/204/304
  body framing, WebSocket close handshake compliance, HTTP/2 ASGI
  stream completion, early-hints validation, framework fixes
  (Django/Litestar/Quart/BlackSheep), and gunicorn_h1c >= 0.6.5
Mirror the ASGI strip-and-warn behavior (commits 2191832, 41ec752,
0d35d2a) on the WSGI path. Previously gunicorn would forward an
app-supplied Content-Length and body bytes for HEAD requests and
1xx/204/304 responses, violating RFC 9110 / RFC 9112.

- Add _response_omits_body() and _response_forbids_content_length()
  helpers on Response.
- After process_headers, strip Content-Length and clear
  response_length on 1xx/204 (RFC 9110 §6.4.2 forbids it). HEAD and
  304 keep app-supplied Content-Length.
- write() and sendfile() drop body bytes for no-body responses and
  log a single WARNING per request.
- is_chunked() now also covers 1xx via _omits_body.

Fixes #3413
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.