@@ -325,8 +325,9 @@ async def test_a_grpc_connection_carrying_traffic_outlives_the_idle_bound():
325325 # Bytes in either direction keep the relay open: a server stream whose client sends
326326 # nothing is not idle.
327327 async def drip (reader , writer ):
328- for _ in range (6 ):
329- writer .write (b"." )
328+ writer .write (SETTINGS )
329+ for n in range (6 ):
330+ writer .write (h2_frame (0x6 , 0 , 0 , bytes ([n ]) * 8 ))
330331 await writer .drain ()
331332 await asyncio .sleep (0.2 )
332333 writer .close ()
@@ -339,7 +340,8 @@ async def drip(reader, writer):
339340 reader , writer = await asyncio .open_connection ("127.0.0.1" , listener .port )
340341 writer .write (b"PRI * HTTP/2.0\r \n \r \n SM\r \n \r \n " )
341342 await writer .drain ()
342- assert await asyncio .wait_for (reader .read (), 3 ) == b"......"
343+ got = read_frames (await asyncio .wait_for (reader .read (), 3 ))
344+ assert [f [3 ] for f in got if f [0 ] == 0x6 ] == [bytes ([n ]) * 8 for n in range (6 )]
343345 writer .close ()
344346 finally :
345347 await listener .stop ()
@@ -446,6 +448,7 @@ async def silent_upstream():
446448
447449 async def accept (reader , writer ):
448450 held .append (writer )
451+ writer .write (SETTINGS ) # a server's connection preface
449452 if state ["mid_frame" ] is not None :
450453 # Half a PING, the rest once the test sets the event: the server is mid-frame.
451454 ping = h2_frame (0x6 , 0 , 0 , b"12345678" )
@@ -549,7 +552,7 @@ async def test_a_call_past_the_limit_is_refused_alone(silent_upstream):
549552 payload = PREFACE + SETTINGS + b"" .join (opens (2 * n + 1 ) for n in range (calls + 1 ))
550553 closed , got = await exchange (await face (), payload , 1 )
551554 assert not closed
552- assert read_frames (got ) == [(0x3 , 0 , past , struct .pack (">I" , 0xB ))]
555+ assert [ f for f in read_frames (got ) if f [ 0 ] == 0x3 ] == [(0x3 , 0 , past , struct .pack (">I" , 0xB ))]
553556 upstream = read_frames (bytes (state ["received" ])[len (PREFACE ) :])
554557 assert upstream [- 2 :] == [(0x1 , 0x4 , past , b"\x82 " ), (0x3 , 0 , past , struct .pack (">I" , 0x8 ))]
555558 assert sum (1 for f in upstream if f [0 ] == 0x1 ) == calls + 1
@@ -569,10 +572,9 @@ async def test_a_refused_call_waits_for_the_end_of_its_header_block(silent_upstr
569572 assert [f [0 ] for f in upstream [- 3 :]] == [0x1 , 0x9 , 0x3 ]
570573
571574
572- async def test_a_burst_past_the_limit_loses_no_call (short_idle ):
575+ async def test_a_burst_past_the_limit_queues_and_loses_no_call (short_idle ):
573576 # 1100 concurrent calls on one channel to a server advertising no limit: the face
574- # resets only the calls past 1000, each with ENHANCE_YOUR_CALM (never REFUSED_STREAM,
575- # which a client would retry on its own); every call it let through is answered.
577+ # advertises its own 1000, so the client queues the rest, and every call is answered.
576578 async with grpc .aio .insecure_channel (f"127.0.0.1:{ short_idle .port } " ) as channel :
577579 say_call = channel .unary_unary (
578580 "/echo.v1.Echo/Say" ,
@@ -586,14 +588,65 @@ async def test_a_burst_past_the_limit_loses_no_call(short_idle):
586588 ],
587589 return_exceptions = True ,
588590 )
589- answered = [a for a in answers if not isinstance (a , Exception )]
590- refused = [a for a in answers if isinstance (a , Exception )]
591- assert all (a .text == "slow|maya" for a in answered )
592- assert len (answered ) == connect_gen .DEFAULT_MAX_OPEN_CALLS
593- assert all (
594- a .code () == grpc .StatusCode .RESOURCE_EXHAUSTED and "error code 11" in a .details ()
595- for a in refused
596- ), {a .details () for a in refused }
591+ failed = {str (a )[:200 ] for a in answers if isinstance (a , Exception )}
592+ assert not failed , failed
593+ assert all (a .text == "slow|maya" for a in answers )
594+
595+
596+ def _settings (frame ):
597+ """The (id, value) entries of a SETTINGS frame."""
598+ payload = frame [3 ]
599+ return [struct .unpack_from (">HI" , payload , at ) for at in range (0 , len (payload ), 6 )]
600+
601+
602+ async def _first_frames (upstream_preface , client_payload , monkeypatch = None ):
603+ """What the client receives, and what the server receives, when a fake gRPC server
604+ sends ``upstream_preface`` 0.3 s after the connection and reads everything after."""
605+ received = bytearray ()
606+
607+ async def accept (reader , writer ):
608+ await asyncio .sleep (0.3 )
609+ writer .write (upstream_preface )
610+ await writer .drain ()
611+ while chunk := await reader .read (65536 ):
612+ received .extend (chunk )
613+
614+ upstream = await asyncio .start_server (accept , "127.0.0.1" , 0 )
615+ listener = await connect_gen .serve (0 , upstream .sockets [0 ].getsockname ()[1 ], host = "127.0.0.1" )
616+ try :
617+ closed , got = await exchange (listener , client_payload , 1 )
618+ return read_frames (got ), read_frames (bytes (received )[len (PREFACE ) :])
619+ finally :
620+ await listener .stop ()
621+ upstream .close ()
622+
623+
624+ async def test_a_server_advertising_no_limit_is_given_the_faces ():
625+ # grpcio advertises no SETTINGS_MAX_CONCURRENT_STREAMS: the client is told the face's
626+ # DEFAULT_MAX_OPEN_CALLS in the server's own SETTINGS, so it queues within it.
627+ server_settings = h2_frame (0x4 , 0 , 0 , struct .pack (">HI" , 0x4 , 65535 ))
628+ to_client , _ = await _first_frames (server_settings , PREFACE + SETTINGS )
629+ assert to_client [0 ][0 ] == 0x4
630+ assert _settings (to_client [0 ]) == [(0x4 , 65535 ), (0x3 , connect_gen .DEFAULT_MAX_OPEN_CALLS )]
631+
632+
633+ async def test_a_server_limit_reaches_the_client_untouched ():
634+ server_settings = h2_frame (0x4 , 0 , 0 , struct .pack (">HI" , 0x3 , 7 ))
635+ to_client , _ = await _first_frames (server_settings , PREFACE + SETTINGS )
636+ assert _settings (to_client [0 ]) == [(0x3 , 7 )]
637+
638+
639+ async def test_the_client_is_read_once_the_servers_limit_is_known (monkeypatch ):
640+ # The client opens three calls at once; the server's SETTINGS (limit 2) arrives 0.3 s
641+ # later. The face waits for it, so the third call goes to the server, which refuses it
642+ # itself (retry-safe), rather than being reset by the face as past its own default.
643+ monkeypatch .setattr (connect_gen , "DEFAULT_MAX_OPEN_CALLS" , 2 )
644+ server_settings = h2_frame (0x4 , 0 , 0 , struct .pack (">HI" , 0x3 , 2 ))
645+ to_client , to_server = await _first_frames (
646+ server_settings , PREFACE + SETTINGS + opens (1 ) + opens (3 ) + opens (5 )
647+ )
648+ assert [f for f in to_client if f [0 ] == 0x3 ] == []
649+ assert [f [0 ] for f in to_server ] == [0x4 , 0x1 , 0x1 , 0x1 ]
597650
598651
599652def _literal_headers (headers ):
@@ -782,7 +835,8 @@ async def test_the_relay_ends_when_the_grpc_server_hangs_up(silent_upstream):
782835 break
783836 await asyncio .sleep (0.1 )
784837 assert not listener ._connections , "the face still holds the client's connection"
785- assert await asyncio .wait_for (reader .read (), 1 ) == b""
838+ # The server's SETTINGS, then the end.
839+ assert [f [0 ] for f in read_frames (await asyncio .wait_for (reader .read (), 1 ))] == [0x4 ]
786840 finally :
787841 writer .close ()
788842
@@ -896,7 +950,7 @@ async def test_the_even_stream_goaway_waits_for_the_servers_frame(silent_upstrea
896950 state ["mid_frame" ].set ()
897951 await frames .until (lambda : frames .eof , 3 )
898952 kinds = [f [0 ] for f in frames .frames ]
899- assert kinds == [0x6 , 0x7 ] # the server's frame whole, then the GOAWAY
953+ assert kinds == [0x4 , 0x6 , 0x7 ] # the server's frame whole, then the GOAWAY
900954 last , code = struct .unpack_from (">II" , frames .frames [- 1 ][3 ])
901955 assert (last , code ) == (1 , 0x1 )
902956 finally :
0 commit comments