@@ -596,8 +596,9 @@ async def test_a_grpc_connection_carrying_traffic_outlives_the_idle_bound():
596596 # Bytes in either direction keep the relay open: a server stream whose client sends
597597 # nothing is not idle.
598598 async def drip (reader , writer ):
599- for _ in range (6 ):
600- writer .write (b"." )
599+ writer .write (SETTINGS )
600+ for n in range (6 ):
601+ writer .write (h2_frame (0x6 , 0 , 0 , bytes ([n ]) * 8 ))
601602 await writer .drain ()
602603 await asyncio .sleep (0.2 )
603604 writer .close ()
@@ -610,7 +611,8 @@ async def drip(reader, writer):
610611 reader , writer = await asyncio .open_connection ("127.0.0.1" , listener .port )
611612 writer .write (b"PRI * HTTP/2.0\r \n \r \n SM\r \n \r \n " )
612613 await writer .drain ()
613- assert await asyncio .wait_for (reader .read (), 3 ) == b"......"
614+ got = read_frames (await asyncio .wait_for (reader .read (), 3 ))
615+ assert [f [3 ] for f in got if f [0 ] == 0x6 ] == [bytes ([n ]) * 8 for n in range (6 )]
614616 writer .close ()
615617 finally :
616618 await listener .stop ()
@@ -717,6 +719,7 @@ async def silent_upstream():
717719
718720 async def accept (reader , writer ):
719721 held .append (writer )
722+ writer .write (SETTINGS ) # a server's connection preface
720723 if state ["mid_frame" ] is not None :
721724 # Half a PING, the rest once the test sets the event: the server is mid-frame.
722725 ping = h2_frame (0x6 , 0 , 0 , b"12345678" )
@@ -820,7 +823,7 @@ async def test_a_call_past_the_limit_is_refused_alone(silent_upstream):
820823 payload = PREFACE + SETTINGS + b"" .join (opens (2 * n + 1 ) for n in range (calls + 1 ))
821824 closed , got = await exchange (await face (), payload , 1 )
822825 assert not closed
823- assert read_frames (got ) == [(0x3 , 0 , past , struct .pack (">I" , 0xB ))]
826+ assert [ f for f in read_frames (got ) if f [ 0 ] == 0x3 ] == [(0x3 , 0 , past , struct .pack (">I" , 0xB ))]
824827 upstream = read_frames (bytes (state ["received" ])[len (PREFACE ) :])
825828 assert upstream [- 2 :] == [(0x1 , 0x4 , past , b"\x82 " ), (0x3 , 0 , past , struct .pack (">I" , 0x8 ))]
826829 assert sum (1 for f in upstream if f [0 ] == 0x1 ) == calls + 1
@@ -840,10 +843,9 @@ async def test_a_refused_call_waits_for_the_end_of_its_header_block(silent_upstr
840843 assert [f [0 ] for f in upstream [- 3 :]] == [0x1 , 0x9 , 0x3 ]
841844
842845
843- async def test_a_burst_past_the_limit_loses_no_call (short_idle ):
846+ async def test_a_burst_past_the_limit_queues_and_loses_no_call (short_idle ):
844847 # 1100 concurrent calls on one channel to a server advertising no limit: the face
845- # resets only the calls past 1000, each with ENHANCE_YOUR_CALM (never REFUSED_STREAM,
846- # which a client would retry on its own); every call it let through is answered.
848+ # advertises its own 1000, so the client queues the rest, and every call is answered.
847849 async with grpc .aio .insecure_channel (f"127.0.0.1:{ short_idle .port } " ) as channel :
848850 say_call = channel .unary_unary (
849851 "/echo.v1.Echo/Say" ,
@@ -857,14 +859,65 @@ async def test_a_burst_past_the_limit_loses_no_call(short_idle):
857859 ],
858860 return_exceptions = True ,
859861 )
860- answered = [a for a in answers if not isinstance (a , Exception )]
861- refused = [a for a in answers if isinstance (a , Exception )]
862- assert all (a .text == "slow|maya" for a in answered )
863- assert len (answered ) == connect_gen .DEFAULT_MAX_OPEN_CALLS
864- assert all (
865- a .code () == grpc .StatusCode .RESOURCE_EXHAUSTED and "error code 11" in a .details ()
866- for a in refused
867- ), {a .details () for a in refused }
862+ failed = {str (a )[:200 ] for a in answers if isinstance (a , Exception )}
863+ assert not failed , failed
864+ assert all (a .text == "slow|maya" for a in answers )
865+
866+
867+ def _settings (frame ):
868+ """The (id, value) entries of a SETTINGS frame."""
869+ payload = frame [3 ]
870+ return [struct .unpack_from (">HI" , payload , at ) for at in range (0 , len (payload ), 6 )]
871+
872+
873+ async def _first_frames (upstream_preface , client_payload , monkeypatch = None ):
874+ """What the client receives, and what the server receives, when a fake gRPC server
875+ sends ``upstream_preface`` 0.3 s after the connection and reads everything after."""
876+ received = bytearray ()
877+
878+ async def accept (reader , writer ):
879+ await asyncio .sleep (0.3 )
880+ writer .write (upstream_preface )
881+ await writer .drain ()
882+ while chunk := await reader .read (65536 ):
883+ received .extend (chunk )
884+
885+ upstream = await asyncio .start_server (accept , "127.0.0.1" , 0 )
886+ listener = await connect_gen .serve (0 , upstream .sockets [0 ].getsockname ()[1 ], host = "127.0.0.1" )
887+ try :
888+ closed , got = await exchange (listener , client_payload , 1 )
889+ return read_frames (got ), read_frames (bytes (received )[len (PREFACE ) :])
890+ finally :
891+ await listener .stop ()
892+ upstream .close ()
893+
894+
895+ async def test_a_server_advertising_no_limit_is_given_the_faces ():
896+ # grpcio advertises no SETTINGS_MAX_CONCURRENT_STREAMS: the client is told the face's
897+ # DEFAULT_MAX_OPEN_CALLS in the server's own SETTINGS, so it queues within it.
898+ server_settings = h2_frame (0x4 , 0 , 0 , struct .pack (">HI" , 0x4 , 65535 ))
899+ to_client , _ = await _first_frames (server_settings , PREFACE + SETTINGS )
900+ assert to_client [0 ][0 ] == 0x4
901+ assert _settings (to_client [0 ]) == [(0x4 , 65535 ), (0x3 , connect_gen .DEFAULT_MAX_OPEN_CALLS )]
902+
903+
904+ async def test_a_server_limit_reaches_the_client_untouched ():
905+ server_settings = h2_frame (0x4 , 0 , 0 , struct .pack (">HI" , 0x3 , 7 ))
906+ to_client , _ = await _first_frames (server_settings , PREFACE + SETTINGS )
907+ assert _settings (to_client [0 ]) == [(0x3 , 7 )]
908+
909+
910+ async def test_the_client_is_read_once_the_servers_limit_is_known (monkeypatch ):
911+ # The client opens three calls at once; the server's SETTINGS (limit 2) arrives 0.3 s
912+ # later. The face waits for it, so the third call goes to the server, which refuses it
913+ # itself (retry-safe), rather than being reset by the face as past its own default.
914+ monkeypatch .setattr (connect_gen , "DEFAULT_MAX_OPEN_CALLS" , 2 )
915+ server_settings = h2_frame (0x4 , 0 , 0 , struct .pack (">HI" , 0x3 , 2 ))
916+ to_client , to_server = await _first_frames (
917+ server_settings , PREFACE + SETTINGS + opens (1 ) + opens (3 ) + opens (5 )
918+ )
919+ assert [f for f in to_client if f [0 ] == 0x3 ] == []
920+ assert [f [0 ] for f in to_server ] == [0x4 , 0x1 , 0x1 , 0x1 ]
868921
869922
870923def _literal_headers (headers ):
@@ -1053,7 +1106,8 @@ async def test_the_relay_ends_when_the_grpc_server_hangs_up(silent_upstream):
10531106 break
10541107 await asyncio .sleep (0.1 )
10551108 assert not listener ._connections , "the face still holds the client's connection"
1056- assert await asyncio .wait_for (reader .read (), 1 ) == b""
1109+ # The server's SETTINGS, then the end.
1110+ assert [f [0 ] for f in read_frames (await asyncio .wait_for (reader .read (), 1 ))] == [0x4 ]
10571111 finally :
10581112 writer .close ()
10591113
@@ -1167,7 +1221,7 @@ async def test_the_even_stream_goaway_waits_for_the_servers_frame(silent_upstrea
11671221 state ["mid_frame" ].set ()
11681222 await frames .until (lambda : frames .eof , 3 )
11691223 kinds = [f [0 ] for f in frames .frames ]
1170- assert kinds == [0x6 , 0x7 ] # the server's frame whole, then the GOAWAY
1224+ assert kinds == [0x4 , 0x6 , 0x7 ] # the server's frame whole, then the GOAWAY
11711225 last , code = struct .unpack_from (">II" , frames .frames [- 1 ][3 ])
11721226 assert (last , code ) == (1 , 0x1 )
11731227 finally :
0 commit comments