10104. Pool-level retry logic based on GOAWAY context
11115. Various race conditions between GOAWAY and request processing
1212"""
13+
1314from __future__ import annotations
1415
16+ from typing import Any
17+
1518import hpack
1619import hyperframe .frame
1720import pytest
1821
1922import httpcore
2023
21-
2224# =============================================================================
2325# Tests for ConnectionGoingAway Exception Properties
2426# =============================================================================
@@ -466,24 +468,11 @@ async def test_http2_draining_connection_goaway_after_complete_response():
466468 assert exc_info .value .is_safe_to_retry is True
467469
468470
469-
470-
471471# =============================================================================
472472# Custom Mock Backend for Retry Tests
473473# =============================================================================
474474
475475
476- class AsyncMockStreamWithFailure (httpcore .AsyncMockStream ):
477- """A mock stream that can simulate different behaviors for testing retries."""
478-
479- def __init__ (self , buffers_by_connection : list [list [bytes ]], http2 : bool = False ):
480- self ._all_buffers = buffers_by_connection
481- self ._connection_index = 0
482- super ().__init__ (
483- buffers_by_connection [0 ] if buffers_by_connection else [], http2 = http2
484- )
485-
486-
487476class AsyncMockBackendWithRetry (httpcore .AsyncMockBackend ):
488477 """A mock backend that returns different data for each connection."""
489478
@@ -499,8 +488,8 @@ async def connect_tcp(
499488 port : int ,
500489 timeout : float | None = None ,
501490 local_address : str | None = None ,
502- socket_options = None ,
503- ):
491+ socket_options : Any = None ,
492+ ) -> httpcore . AsyncMockStream :
504493 if self ._connection_index < len (self ._all_buffers ):
505494 buffer = list (self ._all_buffers [self ._connection_index ])
506495 self ._connection_index += 1
@@ -754,16 +743,15 @@ async def test_http2_goaway_connection_closed_after_graceful_goaway():
754743# =============================================================================
755744
756745
757- class MockConnectionGracefulGoaway :
746+ class MockConnectionGracefulGoaway ( httpcore . AsyncConnectionInterface ) :
758747 """
759748 Mock connection that raises ConnectionGoingAway with specific properties
760749 to test the graceful shutdown + no side effects retry path.
761750 """
762751
763- def __init__ (self , origin : httpcore .Origin , succeed_on_retry : bool = True ) :
752+ def __init__ (self , origin : httpcore .Origin ) -> None :
764753 self ._origin = origin
765754 self ._calls = 0
766- self ._succeed_on_retry = succeed_on_retry
767755
768756 def can_handle_request (self , origin : httpcore .Origin ) -> bool :
769757 return origin == self ._origin
@@ -780,7 +768,9 @@ def is_closed(self) -> bool:
780768 def has_expired (self ) -> bool :
781769 return False
782770
783- async def handle_async_request (self , request : httpcore .Request ) -> httpcore .Response :
771+ async def handle_async_request (
772+ self , request : httpcore .Request
773+ ) -> httpcore .Response :
784774 self ._calls += 1
785775 if self ._calls == 1 :
786776 # First call: raise ConnectionGoingAway with:
@@ -796,16 +786,7 @@ async def handle_async_request(self, request: httpcore.Request) -> httpcore.Resp
796786 headers_sent = False , # no side effects
797787 body_sent = False ,
798788 )
799- if self ._succeed_on_retry :
800- return httpcore .Response (200 , content = b"Success after retry!" )
801- raise httpcore .ConnectionGoingAway (
802- "Another GOAWAY" ,
803- last_stream_id = 5 ,
804- error_code = 0 ,
805- request_stream_id = 1 ,
806- headers_sent = False ,
807- body_sent = False ,
808- )
789+ return httpcore .Response (200 , content = b"Success after retry!" )
809790
810791 async def aclose (self ) -> None :
811792 pass
@@ -824,16 +805,17 @@ async def test_connection_pool_retries_graceful_shutdown_no_headers_sent():
824805
825806 This tests line 258 in connection_pool.py.
826807 """
827- origin = httpcore .Origin (b"https" , b"example.com" , 443 )
828808
829809 # Create a custom pool that returns our mock connection
830810 class TestPool (httpcore .AsyncConnectionPool ):
831- def __init__ (self ):
811+ def __init__ (self ) -> None :
832812 super ().__init__ ()
833- self ._mock_connections : list = []
813+ self ._mock_connections : list [ MockConnectionGracefulGoaway ] = []
834814
835- def create_connection (self , origin : httpcore .Origin ):
836- conn = MockConnectionGracefulGoaway (origin , succeed_on_retry = True )
815+ def create_connection (
816+ self , origin : httpcore .Origin
817+ ) -> MockConnectionGracefulGoaway :
818+ conn = MockConnectionGracefulGoaway (origin )
837819 self ._mock_connections .append (conn )
838820 return conn
839821
@@ -843,6 +825,4 @@ def create_connection(self, origin: httpcore.Origin):
843825 assert response .content == b"Success after retry!"
844826
845827 # Verify the first connection was called twice (retry happened)
846- assert pool ._mock_connections [0 ]._calls == 2
847-
848-
828+ assert pool ._mock_connections [0 ]._calls == 2 # type: ignore[attr-defined]
0 commit comments