@@ -302,3 +302,56 @@ func (s) TestCancelWhileServerWaitingForFlowControl(t *testing.T) {
302302 t .Fatalf ("Failed to read from the stream: %v" , err )
303303 }
304304}
305+
306+ // Tests that when a client sends a HEADERS frame with EndStream=true, the
307+ // server-side stream receives an io.EOF on Recv() and does not hang waiting
308+ // for data frames.
309+ func (s ) TestHeadersEndStreamNoHang (t * testing.T ) {
310+ receivedErr := make (chan error , 1 )
311+ ss := & stubserver.StubServer {
312+ FullDuplexCallF : func (stream testgrpc.TestService_FullDuplexCallServer ) error {
313+ _ , err := stream .Recv ()
314+ receivedErr <- err
315+ return nil
316+ },
317+ }
318+ if err := ss .Start (nil ); err != nil {
319+ t .Fatalf ("Error starting endpoint server: %v" , err )
320+ }
321+ defer ss .Stop ()
322+
323+ conn , err := net .DialTimeout ("tcp" , ss .Address , defaultTestTimeout )
324+ if err != nil {
325+ t .Fatalf ("Failed to dial: %v" , err )
326+ }
327+ defer conn .Close ()
328+
329+ st := newServerTesterFromConn (t , conn )
330+ st .greet ()
331+
332+ // Send HEADERS with EndStream = true and no grpc-timeout header.
333+ st .writeHeaders (http2.HeadersFrameParam {
334+ StreamID : 1 ,
335+ BlockFragment : st .encodeHeader (
336+ ":method" , "POST" ,
337+ ":path" , "/grpc.testing.TestService/FullDuplexCall" ,
338+ ":authority" , "localhost" ,
339+ "content-type" , "application/grpc" ,
340+ "te" , "trailers" ,
341+ ),
342+ EndStream : true ,
343+ EndHeaders : true ,
344+ })
345+
346+ ctx , cancel := context .WithTimeout (context .Background (), defaultTestTimeout )
347+ defer cancel ()
348+
349+ select {
350+ case err := <- receivedErr :
351+ if err != io .EOF {
352+ t .Fatalf ("Streaming handler expected io.EOF, got %v" , err )
353+ }
354+ case <- ctx .Done ():
355+ t .Fatalf ("Timed out waiting for Recv() on the server to complete: %v" , ctx .Err ())
356+ }
357+ }
0 commit comments