Skip to content

Fix retrying interrupted HTTP/1.1 response cleanup - #1113

Open
FriendlyPasser wants to merge 2 commits into
encode:masterfrom
FriendlyPasser:fix/http11-retry-interrupted-close
Open

FriendlyPasser wants to merge 2 commits into
encode:masterfrom
FriendlyPasser:fix/http11-retry-interrupted-close

Conversation

@FriendlyPasser

@FriendlyPasser FriendlyPasser commented Sep 16, 2026

Copy link
Copy Markdown

Summary

Fixes encode/httpx#3782.

When an asyncio task is cancelled while consuming an HTTP/1.1 response, a second cancellation can interrupt response cleanup after HTTP11ConnectionByteStream sets _closed = True. The pool then attempts to close the stream again, but the flag makes that call return without finishing cleanup. The connection can remain active and eventually exhaust the pool.

Reset _closed if cleanup raises, then re-raise the original exception so the caller can retry. Successful close calls remain idempotent. The synchronous implementation is regenerated with the project's unasync script.

Regression tests interrupt cleanup both while acquiring the connection state lock and while closing the network stream. They check that cancellation still propagates, the underlying stream closes, pool bookkeeping is cleared, and another request can acquire the pool's single connection slot. Additional tests cover retrying a failed close and repeated successful close calls for both fully read and unread responses.

This is a scoped HTTP/1.1 cleanup retry fix, not a general guarantee against arbitrary repeated cancellation elsewhere in client or pool shutdown.

The build configuration explicitly selects core metadata 2.4 for both distribution formats, keeping isolated Hatchling builds compatible with the existing distribution validator.

Validation

  • All 8 new parametrized test cases fail against the unmodified base and pass with this change.
  • Python 3.12.14: 222 passed, 6 xpassed for the full suite. The XPASS cases are existing HTTP/2 cancellation tests.
  • scripts/check passes, including formatting, mypy, Ruff, and unasync consistency.
  • scripts/build passes: wheel/sdist creation, Twine checks, and documentation generation.
  • The repository coverage check passes at 100%.
  • Local HTTPX 0.28.1 integration check with AnyIO 4.12.1 and h11 0.16.0: 20 double-cancellation iterations leave no connections behind and a subsequent request succeeds. The unmodified base exhausts a three-connection pool after three iterations.

Checklist

  • I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly. No public API or documented behavior changes are required for this cleanup fix.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Potential Issue: AsyncClient.stream double-cancel may leak active connections

1 participant