Repository: grpc/grpc-go
File: internal/transport/http2_client.go (Go)

Code before:
	}
	t.mu.Unlock()

	// Per HTTP/2 spec, a GOAWAY frame must be sent before closing the
	// connection. See https://httpwg.org/specs/rfc7540.html#GOAWAY. It
	// also waits for loopyWriter to be closed with a timer to avoid the
	// long blocking in case the connection is blackholed, i.e. TCP is
	// just stuck.
	t.controlBuf.put(&goAway{code: http2.ErrCodeNo, debugData: []byte("client transport shutdown"), closeConn: err})
	timer := time.NewTimer(goAwayLoopyWriterTimeout)
	defer timer.Stop()
	select {
	case <-t.writerDone: // success
	case <-timer.C:
		t.logger.Infof("Failed to write a GOAWAY frame as part of connection close after %s. Giving up and closing the transport.", goAwayLoopyWriterTimeout)
	}
	t.cancel()
	t.conn.Close()
	// Wait for the reader goroutine to exit to ensure all resources are cleaned
	// up before Close can return.
	<-t.readerDone
	if t.keepaliveEnabled {
		// Wait for the keepAlive goroutine to exit to ensure all resources are cleaned
		// up before Close can return.
		<-t.keepAliveDone
	}
	channelz.RemoveEntry(t.channelz.ID)
	// Append info about previous goaways if there were any, since this may be important
	// for understanding the root cause for this connection to be closed.
	_, goAwayDebugMessage := t.GetGoAwayReason()

	var st *status.Status
	if len(goAwayDebugMessage) > 0 {
		st = status.Newf(codes.Unavailable, "closing transport due to: %v, received prior goaway: %v", err, goAwayDebugMessage)
		err = st.Err()
	} else {
		st = status.New(codes.Unavailable, err.Error())
	}

	// Notify all active streams.
	for _, s := range streams {
		t.closeStream(s, err, false, http2.ErrCodeNo, st, nil, false)
	}
	for _, sh := range t.statsHandlers {
		connEnd := &stats.ConnEnd{
			Client: true,
		}
		sh.HandleConn(t.ctx, connEnd)
	}
}


Review comment (suggestion):
A single comment might be sufficient.
```suggestion
	// Wait for the reader and keepalive goroutines to exit before returning from here.
	<-t.readerDone
	if t.keepaliveEnabled {
		<-t.keepaliveDone
	}
```

Code after:
	t.mu.Unlock()

	// Per HTTP/2 spec, a GOAWAY frame must be sent before closing the
	// connection. See https://httpwg.org/specs/rfc7540.html#GOAWAY. It
	// also waits for loopyWriter to be closed with a timer to avoid the
	// long blocking in case the connection is blackholed, i.e. TCP is
	// just stuck.
	t.controlBuf.put(&goAway{code: http2.ErrCodeNo, debugData: []byte("client transport shutdown"), closeConn: err})
	timer := time.NewTimer(goAwayLoopyWriterTimeout)
	defer timer.Stop()
	select {
	case <-t.writerDone: // success
	case <-timer.C:
		t.logger.Infof("Failed to write a GOAWAY frame as part of connection close after %s. Giving up and closing the transport.", goAwayLoopyWriterTimeout)
	}
	t.cancel()
	t.conn.Close()
	// Waits for the reader and keepalive goroutines to exit before returning to
	// ensure all resources are cleaned up before Close can return.
	<-t.readerDone
	if t.keepaliveEnabled {
		<-t.keepaliveDone
	}
	channelz.RemoveEntry(t.channelz.ID)
	// Append info about previous goaways if there were any, since this may be important
	// for understanding the root cause for this connection to be closed.
	_, goAwayDebugMessage := t.GetGoAwayReason()

	var st *status.Status
	if len(goAwayDebugMessage) > 0 {
		st = status.Newf(codes.Unavailable, "closing transport due to: %v, received prior goaway: %v", err, goAwayDebugMessage)
		err = st.Err()
	} else {
		st = status.New(codes.Unavailable, err.Error())
	}

	// Notify all active streams.
	for _, s := range streams {
		t.closeStream(s, err, false, http2.ErrCodeNo, st, nil, false)
	}
	for _, sh := range t.statsHandlers {
		connEnd := &stats.ConnEnd{
			Client: true,
		}
		sh.HandleConn(t.ctx, connEnd)
	}
}

// GracefulClose sets the state to draining, which prevents new streams from
// being created and causes the transport to be closed when the last active
// stream is closed.  If there are no active streams, the transport is closed