Skip to content

Commit 85296a4

Browse files
committed
add tests for _assign_requests_to_connections
1 parent c9106c7 commit 85296a4

2 files changed

Lines changed: 238 additions & 0 deletions

File tree

tests/_async/test_connection_pool.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,3 +892,122 @@ async def test_keepalive_idle_connections():
892892
repr(pool)
893893
== "<AsyncConnectionPool [Requests: 0 active, 0 queued | Connections: 0 active, 1 idle]>"
894894
)
895+
896+
897+
@pytest.mark.anyio
898+
async def test_idle_but_not_available_connection_closing():
899+
"""
900+
Test that a connection that is idle but not available gets closed.
901+
This is a pathological edge case that shouldn't occur in reality, but the connection pool
902+
handles it anyway.
903+
"""
904+
905+
class MockIdleNotAvailableConnection(httpcore.AsyncConnectionInterface):
906+
def __init__(self) -> None:
907+
self._origin = httpcore.Origin(b"https", b"example.com", 443)
908+
909+
def is_available(self) -> bool:
910+
return False # Not available
911+
912+
def is_idle(self) -> bool:
913+
return True # But is idle - this is the edge case
914+
915+
def is_closed(self) -> bool:
916+
return False
917+
918+
def has_expired(self) -> bool:
919+
return False
920+
921+
network_backend = httpcore.AsyncMockBackend([])
922+
923+
async with httpcore.AsyncConnectionPool(
924+
network_backend=network_backend,
925+
) as pool:
926+
# Replace the connection list with our pathological mock connection
927+
mock_conn = MockIdleNotAvailableConnection()
928+
929+
pool._connections = [mock_conn]
930+
931+
# This should move the idle-but-not-available connection to closing_conns
932+
closing_conns = pool._assign_requests_to_connections()
933+
934+
# Verify the connection is marked for closing
935+
assert len(closing_conns) == 1
936+
assert closing_conns[0] is mock_conn
937+
938+
# Verify it's no longer in the connection pool
939+
assert len(pool._connections) == 0
940+
941+
942+
@pytest.mark.anyio
943+
async def test_active_http2_connection_keepalive_preservation():
944+
"""
945+
Test that active HTTP/2 connections with capacity are preserved during keepalive enforcement.
946+
This tests line 421 where active HTTP/2 connections are kept during keepalive enforcement.
947+
"""
948+
949+
class MockActiveHTTP2Connection(httpcore.AsyncConnectionInterface):
950+
def __init__(self) -> None:
951+
self._origin = httpcore.Origin(b"https", b"example.com", 443)
952+
953+
def is_available(self) -> bool:
954+
return True # Available with capacity
955+
956+
def is_idle(self) -> bool:
957+
return False # Not idle (active HTTP/2 connection with streams)
958+
959+
def is_closed(self) -> bool:
960+
return False
961+
962+
def has_expired(self) -> bool:
963+
return False
964+
965+
def get_available_stream_capacity(self) -> int:
966+
return 5 # HTTP/2 connection with available stream capacity
967+
968+
async def aclose(self):
969+
pass
970+
971+
class MockIdleConnection(httpcore.AsyncConnectionInterface):
972+
def __init__(self) -> None:
973+
self._origin = httpcore.Origin(b"https", b"example.com", 443)
974+
975+
def is_available(self) -> bool:
976+
return True
977+
978+
def is_idle(self) -> bool:
979+
return True # This is an idle connection
980+
981+
def is_closed(self) -> bool:
982+
return False
983+
984+
def has_expired(self) -> bool:
985+
return False
986+
987+
def get_available_stream_capacity(self) -> int:
988+
return 1
989+
990+
network_backend = httpcore.AsyncMockBackend([])
991+
992+
async with httpcore.AsyncConnectionPool(
993+
max_keepalive_connections=0, # Force keepalive limit to 0
994+
http2=True,
995+
network_backend=network_backend,
996+
) as pool:
997+
# Create one idle connection and one active HTTP/2 connection
998+
idle_conn = MockIdleConnection()
999+
active_http2_conn = MockActiveHTTP2Connection()
1000+
1001+
with pool._optional_thread_lock:
1002+
pool._connections = [idle_conn, active_http2_conn]
1003+
1004+
# This should close idle connections but preserve the active HTTP/2 connection
1005+
closing_conns = pool._assign_requests_to_connections()
1006+
1007+
# Verify idle connection is marked for closing
1008+
assert len(closing_conns) == 1
1009+
assert idle_conn in closing_conns
1010+
1011+
# Verify the active HTTP/2 connection is preserved (line 421)
1012+
assert len(pool._connections) == 1
1013+
assert active_http2_conn in pool._connections

tests/_sync/test_connection_pool.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,3 +892,122 @@ def test_keepalive_idle_connections():
892892
repr(pool)
893893
== "<ConnectionPool [Requests: 0 active, 0 queued | Connections: 0 active, 1 idle]>"
894894
)
895+
896+
897+
898+
def test_idle_but_not_available_connection_closing():
899+
"""
900+
Test that a connection that is idle but not available gets closed.
901+
This is a pathological edge case that shouldn't occur in reality, but the connection pool
902+
handles it anyway.
903+
"""
904+
905+
class MockIdleNotAvailableConnection(httpcore.ConnectionInterface):
906+
def __init__(self) -> None:
907+
self._origin = httpcore.Origin(b"https", b"example.com", 443)
908+
909+
def is_available(self) -> bool:
910+
return False # Not available
911+
912+
def is_idle(self) -> bool:
913+
return True # But is idle - this is the edge case
914+
915+
def is_closed(self) -> bool:
916+
return False
917+
918+
def has_expired(self) -> bool:
919+
return False
920+
921+
network_backend = httpcore.MockBackend([])
922+
923+
with httpcore.ConnectionPool(
924+
network_backend=network_backend,
925+
) as pool:
926+
# Replace the connection list with our pathological mock connection
927+
mock_conn = MockIdleNotAvailableConnection()
928+
929+
pool._connections = [mock_conn]
930+
931+
# This should move the idle-but-not-available connection to closing_conns
932+
closing_conns = pool._assign_requests_to_connections()
933+
934+
# Verify the connection is marked for closing
935+
assert len(closing_conns) == 1
936+
assert closing_conns[0] is mock_conn
937+
938+
# Verify it's no longer in the connection pool
939+
assert len(pool._connections) == 0
940+
941+
942+
943+
def test_active_http2_connection_keepalive_preservation():
944+
"""
945+
Test that active HTTP/2 connections with capacity are preserved during keepalive enforcement.
946+
This tests line 421 where active HTTP/2 connections are kept during keepalive enforcement.
947+
"""
948+
949+
class MockActiveHTTP2Connection(httpcore.ConnectionInterface):
950+
def __init__(self) -> None:
951+
self._origin = httpcore.Origin(b"https", b"example.com", 443)
952+
953+
def is_available(self) -> bool:
954+
return True # Available with capacity
955+
956+
def is_idle(self) -> bool:
957+
return False # Not idle (active HTTP/2 connection with streams)
958+
959+
def is_closed(self) -> bool:
960+
return False
961+
962+
def has_expired(self) -> bool:
963+
return False
964+
965+
def get_available_stream_capacity(self) -> int:
966+
return 5 # HTTP/2 connection with available stream capacity
967+
968+
def close(self):
969+
pass
970+
971+
class MockIdleConnection(httpcore.ConnectionInterface):
972+
def __init__(self) -> None:
973+
self._origin = httpcore.Origin(b"https", b"example.com", 443)
974+
975+
def is_available(self) -> bool:
976+
return True
977+
978+
def is_idle(self) -> bool:
979+
return True # This is an idle connection
980+
981+
def is_closed(self) -> bool:
982+
return False
983+
984+
def has_expired(self) -> bool:
985+
return False
986+
987+
def get_available_stream_capacity(self) -> int:
988+
return 1
989+
990+
network_backend = httpcore.MockBackend([])
991+
992+
with httpcore.ConnectionPool(
993+
max_keepalive_connections=0, # Force keepalive limit to 0
994+
http2=True,
995+
network_backend=network_backend,
996+
) as pool:
997+
# Create one idle connection and one active HTTP/2 connection
998+
idle_conn = MockIdleConnection()
999+
active_http2_conn = MockActiveHTTP2Connection()
1000+
1001+
with pool._optional_thread_lock:
1002+
pool._connections = [idle_conn, active_http2_conn]
1003+
1004+
# This should close idle connections but preserve the active HTTP/2 connection
1005+
closing_conns = pool._assign_requests_to_connections()
1006+
1007+
# Verify idle connection is marked for closing
1008+
assert len(closing_conns) == 1
1009+
assert idle_conn in closing_conns
1010+
1011+
# Verify the active HTTP/2 connection is preserved (line 421)
1012+
assert len(pool._connections) == 1
1013+
assert active_http2_conn in pool._connections

0 commit comments

Comments
 (0)