Skip to content

Commit 3684229

Browse files
committed
refactor: resolved suggestions from purnesh
1 parent a0bb368 commit 3684229

File tree

2 files changed

+44
-105
lines changed

2 files changed

+44
-105
lines changed

internal/transport/http2_client.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ import (
5959
// atomically.
6060
var clientConnectionCounter uint64
6161

62+
const GoAwayLoopyWriterTimeout = 5 * time.Millisecond
63+
6264
var metadataFromOutgoingContextRaw = internal.FromOutgoingContextRaw.(func(context.Context) (metadata.MD, [][]string, bool))
6365

6466
// http2Client implements the ClientTransport interface with HTTP2.
@@ -1006,29 +1008,28 @@ func (t *http2Client) Close(err error) {
10061008
t.kpDormancyCond.Signal()
10071009
}
10081010
t.mu.Unlock()
1011+
var st *status.Status
10091012
// Per HTTP/2 spec, a GOAWAY frame must be sent before closing the
10101013
// connection. See https://httpwg.org/specs/rfc7540.html#GOAWAY.
10111014
t.controlBuf.put(&goAway{code: http2.ErrCodeNo, debugData: []byte("client transport shutdown"), closeConn: err})
1012-
timer := time.NewTimer(5 * time.Second)
1015+
timer := time.NewTimer(GoAwayLoopyWriterTimeout)
10131016
select {
10141017
case <-t.writerDone:
1018+
// Append info about previous goaway's if there were any, since this may be important
1019+
// for understanding the root cause for this connection to be closed.
1020+
_, goAwayDebugMessage := t.GetGoAwayReason()
1021+
if len(goAwayDebugMessage) > 0 {
1022+
st = status.Newf(codes.Unavailable, "closing transport due to: %v, received prior goaway: %v", err, goAwayDebugMessage)
1023+
err = st.Err()
1024+
} else {
1025+
st = status.New(codes.Unavailable, err.Error())
1026+
}
10151027
case <-timer.C:
10161028
t.logger.Warningf("timeout waiting for the loopy writer to be closed.")
10171029
}
10181030
t.cancel()
10191031
t.conn.Close()
10201032
channelz.RemoveEntry(t.channelz.ID)
1021-
// Append info about previous goaways if there were any, since this may be important
1022-
// for understanding the root cause for this connection to be closed.
1023-
_, goAwayDebugMessage := t.GetGoAwayReason()
1024-
1025-
var st *status.Status
1026-
if len(goAwayDebugMessage) > 0 {
1027-
st = status.Newf(codes.Unavailable, "closing transport due to: %v, received prior goaway: %v", err, goAwayDebugMessage)
1028-
err = st.Err()
1029-
} else {
1030-
st = status.New(codes.Unavailable, err.Error())
1031-
}
10321033

10331034
// Notify all active streams.
10341035
for _, s := range streams {

internal/transport/transport_test.go

Lines changed: 31 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ const (
9090
invalidHeaderField
9191
delayRead
9292
pingpong
93-
goAwayFrameSize = 42
9493
)
9594

95+
const goAwayFrameSize = 42
96+
9697
func (h *testStreamHandler) handleStreamAndNotify(s *Stream) {
9798
if h.notify == nil {
9899
return
@@ -2661,94 +2662,14 @@ func TestConnectionError_Unwrap(t *testing.T) {
26612662
// clientTransport.Close(), client sends a goaway to the server with the correct
26622663
// error code and debug data.
26632664
func (s) TestClientSendsAGoAwayFrame(t *testing.T) {
2664-
// Create a server.
2665-
lis, err := net.Listen("tcp", "localhost:0")
2666-
if err != nil {
2667-
t.Fatalf("Error while listening: %v", err)
2668-
}
2669-
defer lis.Close()
2670-
// greetDone is used to notify when server is done greeting the client.
2671-
greetDone := make(chan struct{})
2672-
// errorCh verifies that desired GOAWAY not received by server
2673-
errorCh := make(chan error)
2674-
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
2675-
defer cancel()
2676-
// Launch the server.
2677-
go func() {
2678-
sconn, err := lis.Accept()
2679-
if err != nil {
2680-
t.Errorf("Error while accepting: %v", err)
2681-
}
2682-
defer sconn.Close()
2683-
if _, err := io.ReadFull(sconn, make([]byte, len(clientPreface))); err != nil {
2684-
t.Errorf("Error while writing settings ack: %v", err)
2685-
return
2686-
}
2687-
sfr := http2.NewFramer(sconn, sconn)
2688-
if err := sfr.WriteSettings(); err != nil {
2689-
t.Errorf("Error while writing settings %v", err)
2690-
return
2691-
}
2692-
fr, _ := sfr.ReadFrame()
2693-
if _, ok := fr.(*http2.SettingsFrame); !ok {
2694-
t.Errorf("Expected settings frame, got %v", fr)
2695-
}
2696-
fr, _ = sfr.ReadFrame()
2697-
if fr, ok := fr.(*http2.SettingsFrame); !ok || !fr.IsAck() {
2698-
t.Errorf("Expected settings ACK frame, got %v", fr)
2699-
}
2700-
fr, _ = sfr.ReadFrame()
2701-
if fr, ok := fr.(*http2.HeadersFrame); !ok || !fr.Flags.Has(http2.FlagHeadersEndHeaders) {
2702-
t.Errorf("Expected Headers frame with END_HEADERS frame, got %v", fr)
2703-
}
2704-
close(greetDone)
2705-
2706-
frame, err := sfr.ReadFrame()
2707-
if err != nil {
2708-
return
2709-
}
2710-
switch fr := frame.(type) {
2711-
case *http2.GoAwayFrame:
2712-
// Records that the server successfully received a GOAWAY frame.
2713-
goAwayFrame := fr
2714-
if goAwayFrame.ErrCode == http2.ErrCodeNo {
2715-
t.Logf("Received goAway frame from client")
2716-
close(errorCh)
2717-
} else {
2718-
errorCh <- fmt.Errorf("received unexpected goAway frame: %v", err)
2719-
close(errorCh)
2720-
}
2721-
return
2722-
default:
2723-
errorCh <- fmt.Errorf("server received a frame other than GOAWAY: %v", err)
2724-
close(errorCh)
2725-
return
2726-
}
2727-
}()
2728-
2729-
ct, err := NewClientTransport(ctx, context.Background(), resolver.Address{Addr: lis.Addr().String()}, ConnectOptions{}, func(GoAwayReason) {})
2730-
if err != nil {
2731-
t.Fatalf("Error while creating client transport: %v", err)
2732-
}
2733-
_, err = ct.NewStream(ctx, &CallHdr{})
2734-
if err != nil {
2735-
t.Fatalf("failed to open stream: %v", err)
2736-
}
2737-
// Wait until server receives the headers and settings frame as part of greet.
2738-
<-greetDone
2739-
ct.Close(errors.New("manually closed by client"))
2740-
select {
2741-
case err := <-errorCh:
2742-
if err != nil {
2743-
t.Errorf("Error receiving the GOAWAY frame: %v", err)
2744-
}
2745-
case <-ctx.Done():
2746-
t.Errorf("Context timed out")
2747-
}
2665+
createClientServerConn(t, ConnectOptions{})
27482666
}
27492667

27502668
var writeHangSignal chan struct{}
27512669

2670+
// hangingConn is a net.Conn wrapper for testing, simulating hanging connections
2671+
// after a GOAWAY frame is sent, of which Write operations pause until explicitly signaled
2672+
// or a timeout occurs.
27522673
type hangingConn struct {
27532674
net.Conn
27542675
}
@@ -2761,8 +2682,11 @@ func (hc *hangingConn) Read(b []byte) (n int, err error) {
27612682
func (hc *hangingConn) Write(b []byte) (n int, err error) {
27622683
n, err = hc.Conn.Write(b)
27632684
if n == goAwayFrameSize { // GOAWAY frame
2764-
writeHangSignal = make(chan struct{})
2765-
time.Sleep(15 * time.Second)
2685+
timer := time.NewTimer(GoAwayLoopyWriterTimeout)
2686+
select {
2687+
case <-writeHangSignal:
2688+
case <-timer.C:
2689+
}
27662690
}
27672691
return n, err
27682692
}
@@ -2801,8 +2725,20 @@ func hangingDialer(_ context.Context, addr string) (net.Conn, error) {
28012725

28022726
// TestClientCloseTimeoutOnHang verifies that in the event of a graceful
28032727
// client transport shutdown, i.e., clientTransport.Close(), if the conn hung
2804-
// forever, client should still be close itself and do not wait for long.
2728+
// for LoopyWriterTimeout, client should still be close itself and should
2729+
// not wait for long.
28052730
func (s) TestClientCloseTimeoutOnHang(t *testing.T) {
2731+
writeHangSignal = make(chan struct{})
2732+
ctx, _, _ := createClientServerConn(t, ConnectOptions{Dialer: hangingDialer})
2733+
defer close(writeHangSignal)
2734+
select {
2735+
case <-writeHangSignal:
2736+
t.Errorf("error: channel closed too early.")
2737+
case <-ctx.Done():
2738+
}
2739+
}
2740+
2741+
func createClientServerConn(t *testing.T, connectOptions ConnectOptions) (context.Context, chan error, ClientTransport) {
28062742
// Create a server.
28072743
lis, err := net.Listen("tcp", "localhost:0")
28082744
if err != nil {
@@ -2868,7 +2804,7 @@ func (s) TestClientCloseTimeoutOnHang(t *testing.T) {
28682804
}
28692805
}()
28702806

2871-
ct, err := NewClientTransport(ctx, context.Background(), resolver.Address{Addr: lis.Addr().String()}, ConnectOptions{Dialer: hangingDialer}, func(GoAwayReason) {})
2807+
ct, err := NewClientTransport(ctx, context.Background(), resolver.Address{Addr: lis.Addr().String()}, connectOptions, func(GoAwayReason) {})
28722808
if err != nil {
28732809
t.Fatalf("Error while creating client transport: %v", err)
28742810
}
@@ -2879,11 +2815,13 @@ func (s) TestClientCloseTimeoutOnHang(t *testing.T) {
28792815
// Wait until server receives the headers and settings frame as part of greet.
28802816
<-greetDone
28812817
ct.Close(errors.New("manually closed by client"))
2882-
defer close(writeHangSignal)
28832818
select {
2884-
case <-writeHangSignal:
2885-
t.Errorf("error: channel closed too early.")
2819+
case err := <-errorCh:
2820+
if err != nil {
2821+
t.Errorf("Error receiving the GOAWAY frame: %v", err)
2822+
}
28862823
case <-ctx.Done():
2824+
t.Errorf("Context timed out")
28872825
}
2888-
2826+
return ctx, errorCh, ct
28892827
}

0 commit comments

Comments
 (0)