44import hpack
55import hyperframe .frame
66import pytest
7- import trio as concurrency
87
98import httpcore
9+ from tests .concurrency import async_open_nursery as open_nursery
10+ from tests .current_time import async_current_time as current_time , async_sleep as sleep
1011
1112
1213@pytest .mark .anyio
@@ -515,7 +516,7 @@ async def fetch(pool, domain, info_list):
515516 max_connections = 1 , network_backend = network_backend
516517 ) as pool :
517518 info_list : typing .List [str ] = []
518- async with concurrency . open_nursery () as nursery :
519+ async with open_nursery () as nursery :
519520 for domain in ["a.com" , "b.com" , "c.com" , "d.com" , "e.com" ]:
520521 nursery .start_soon (fetch , pool , domain , info_list )
521522
@@ -561,7 +562,7 @@ async def fetch(pool, domain, info_list):
561562 max_connections = 1 , network_backend = network_backend , http2 = True
562563 ) as pool :
563564 info_list : typing .List [str ] = []
564- async with concurrency . open_nursery () as nursery :
565+ async with open_nursery () as nursery :
565566 for domain in ["a.com" , "a.com" , "a.com" , "a.com" , "a.com" ]:
566567 nursery .start_soon (fetch , pool , domain , info_list )
567568
@@ -603,7 +604,7 @@ async def fetch(pool, domain, info_list):
603604 max_connections = 1 , network_backend = network_backend , http2 = True
604605 ) as pool :
605606 info_list : typing .List [str ] = []
606- async with concurrency . open_nursery () as nursery :
607+ async with open_nursery () as nursery :
607608 for domain in ["a.com" , "a.com" , "a.com" , "a.com" , "a.com" ]:
608609 nursery .start_soon (fetch , pool , domain , info_list )
609610
@@ -662,7 +663,14 @@ async def test_connection_pool_closed_while_request_in_flight():
662663async def test_connection_pool_timeout ():
663664 """
664665 Ensure that exceeding max_connections can cause a request to timeout.
666+
667+ 100 concurrent requests will be sent with a limit of 1 connection.
668+ The request time is fixed to 6ms, and there is a pool timeout of 50ms.
669+
670+ Less than 9 requests will succeed and the rest won't have a chance to get
671+ a connection before 50ms, PoolTimeout will be raised instead.
665672 """
673+
666674 network_backend = httpcore .AsyncMockBackend (
667675 [
668676 b"HTTP/1.1 200 OK\r \n " ,
@@ -671,18 +679,41 @@ async def test_connection_pool_timeout():
671679 b"\r \n " ,
672680 b"Hello, world!" ,
673681 ]
682+ * 100
674683 )
675684
685+ successes : int = 0
686+ max_wait : float = 0
687+
688+ async def fetch (pool ):
689+ nonlocal successes , max_wait
690+
691+ start_time = current_time ()
692+ extensions = {"timeout" : {"pool" : 0.05 }}
693+
694+ try :
695+ async with pool .stream (
696+ "GET" , "https://example.com/" , extensions = extensions
697+ ) as response :
698+ await sleep (0.006 ) # we block the connection for that time
699+ await response .aread ()
700+ successes += 1
701+ except httpcore .PoolTimeout :
702+ pass
703+ finally :
704+ max_wait = max (current_time () - start_time , max_wait )
705+
676706 async with httpcore .AsyncConnectionPool (
677707 network_backend = network_backend , max_connections = 1
678708 ) as pool :
679- # Send a request to a pool that is configured to only support a single
680- # connection, and then ensure that a second concurrent request
681- # fails with a timeout.
682- async with pool .stream ("GET" , "https://example.com/" ):
683- with pytest .raises (httpcore .PoolTimeout ):
684- extensions = {"timeout" : {"pool" : 0.0001 }}
685- await pool .request ("GET" , "https://example.com/" , extensions = extensions )
709+ async with open_nursery () as nursery :
710+ for _ in range (100 ):
711+ nursery .start_soon (fetch , pool )
712+
713+ assert 1 <= successes < 9 , "less than 9 requests should succeed"
714+
715+ # Theoretically, the whole operation should take 50ms + 6ms; we should stay within that range
716+ assert max_wait <= 0.2
686717
687718
688719@pytest .mark .anyio
0 commit comments