From c2a2cfb977376d850dc14377c2bcce2b629dab9d Mon Sep 17 00:00:00 2001 From: "Michiel W. Beijen" Date: Tue, 2 Jun 2026 08:39:14 +0200 Subject: [PATCH] Release HTTP/2 semaphore permit on NoAvailableStreamIDError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `handle_async_request` / `handle_request` acquires a permit from `_max_streams_semaphore` before calling `_h2_state.get_next_available_stream_id()`. If the stream-ID space is exhausted and `NoAvailableStreamIDError` is raised, the exception handler sets `_used_all_stream_ids` and decrements `_request_count` but never releases the permit — leaking it permanently. In practice `_used_all_stream_ids = True` prevents further requests on the connection, so the visible impact is limited, but the resource cleanup is still wrong. Add the missing `release()` call in both async and sync paths. Ports encode/httpcore#1061 (bysiber), via codeberg.org/httpxyz/httpcorexyz@e88f30b (first of two fixes bundled in that commit; the second lands in a separate PR). Co-Authored-By: Kadir Can Ozden <101993364+bysiber@users.noreply.github.com> --- src/httpcore2/httpcore2/_async/http2.py | 1 + src/httpcore2/httpcore2/_sync/http2.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/httpcore2/httpcore2/_async/http2.py b/src/httpcore2/httpcore2/_async/http2.py index ae3e764c..156b3ea0 100644 --- a/src/httpcore2/httpcore2/_async/http2.py +++ b/src/httpcore2/httpcore2/_async/http2.py @@ -123,6 +123,7 @@ async def handle_async_request(self, request: Request) -> Response: except h2.exceptions.NoAvailableStreamIDError: # pragma: no cover self._used_all_stream_ids = True self._request_count -= 1 + await self._max_streams_semaphore.release() raise ConnectionNotAvailable() try: diff --git a/src/httpcore2/httpcore2/_sync/http2.py b/src/httpcore2/httpcore2/_sync/http2.py index 0512c4a2..ca3fc215 100644 --- a/src/httpcore2/httpcore2/_sync/http2.py +++ b/src/httpcore2/httpcore2/_sync/http2.py @@ -123,6 +123,7 @@ def handle_request(self, request: Request) -> Response: except h2.exceptions.NoAvailableStreamIDError: # pragma: no cover self._used_all_stream_ids = True self._request_count -= 1 + self._max_streams_semaphore.release() raise ConnectionNotAvailable() try: