Skip to content

Commit 6dc6880

Browse files
neildgopherbot
authored andcommitted
http2: simplify ClientConn Close and Shutdown tests
Split testClientConnClose into four separate tests, rather than having one big function which performs different tests depending on its parameter. Use the more modern testClientConn in these tests, for simplicity. Drop the activeStreams function, which pokes a bit too far into implementation internals and isn't necessary for the new tests. (It had one use outside of testClientConnClose, which provides little useful signal and can also be dropped.) Change-Id: Id8d1c7feab59c1f041bc2d1cf0398e8b1e230c69 Reviewed-on: https://go-review.googlesource.com/c/net/+/701005 Reviewed-by: Nicholas Husin <[email protected]> Auto-Submit: Damien Neil <[email protected]> Reviewed-by: Nicholas Husin <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 4e2915b commit 6dc6880

File tree

2 files changed

+108
-184
lines changed

2 files changed

+108
-184
lines changed

http2/transport.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ var (
653653
errClientConnUnusable = errors.New("http2: client conn not usable")
654654
errClientConnNotEstablished = errors.New("http2: client conn could not be established")
655655
errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
656+
errClientConnForceClosed = errors.New("http2: client connection force closed via ClientConn.Close")
656657
)
657658

658659
// shouldRetryRequest is called by RoundTrip when a request fails to get
@@ -1206,8 +1207,7 @@ func (cc *ClientConn) closeForError(err error) {
12061207
//
12071208
// In-flight requests are interrupted. For a graceful shutdown, use Shutdown instead.
12081209
func (cc *ClientConn) Close() error {
1209-
err := errors.New("http2: client connection force closed via ClientConn.Close")
1210-
cc.closeForError(err)
1210+
cc.closeForError(errClientConnForceClosed)
12111211
return nil
12121212
}
12131213

http2/transport_test.go

Lines changed: 106 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -3852,200 +3852,128 @@ func benchLargeDownloadRoundTrip(b *testing.B, frameSize uint32) {
38523852
}
38533853
}
38543854

3855-
func activeStreams(cc *ClientConn) int {
3856-
count := 0
3857-
cc.mu.Lock()
3858-
defer cc.mu.Unlock()
3859-
for _, cs := range cc.streams {
3860-
select {
3861-
case <-cs.abort:
3862-
default:
3863-
count++
3864-
}
3855+
// The client closes the connection just after the server got the client's HEADERS
3856+
// frame, but before the server sends its HEADERS response back. The expected
3857+
// result is an error on RoundTrip explaining the client closed the connection.
3858+
func TestClientConnCloseAtHeaders(t *testing.T) { synctestTest(t, testClientConnCloseAtHeaders) }
3859+
func testClientConnCloseAtHeaders(t testing.TB) {
3860+
tc := newTestClientConn(t)
3861+
tc.greet()
3862+
3863+
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
3864+
rt := tc.roundTrip(req)
3865+
tc.wantFrameType(FrameHeaders)
3866+
3867+
tc.cc.Close()
3868+
synctest.Wait()
3869+
if err := rt.err(); err != errClientConnForceClosed {
3870+
t.Fatalf("RoundTrip error = %v, want errClientConnForceClosed", err)
38653871
}
3866-
return count
38673872
}
38683873

3869-
type closeMode int
3874+
// The client closes the connection while reading the response.
3875+
// The expected behavior is a response body io read error on the client.
3876+
func TestClientConnCloseAtBody(t *testing.T) { synctestTest(t, testClientConnCloseAtBody) }
3877+
func testClientConnCloseAtBody(t testing.TB) {
3878+
tc := newTestClientConn(t)
3879+
tc.greet()
38703880

3871-
const (
3872-
closeAtHeaders closeMode = iota
3873-
closeAtBody
3874-
shutdown
3875-
shutdownCancel
3876-
)
3881+
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
3882+
rt := tc.roundTrip(req)
3883+
tc.wantFrameType(FrameHeaders)
38773884

3878-
// See golang.org/issue/17292
3879-
func testClientConnClose(t *testing.T, closeMode closeMode) {
3880-
clientDone := make(chan struct{})
3881-
defer close(clientDone)
3882-
handlerDone := make(chan struct{})
3883-
closeDone := make(chan struct{})
3884-
beforeHeader := func() {}
3885-
bodyWrite := func(w http.ResponseWriter) {}
3886-
ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
3887-
defer close(handlerDone)
3888-
beforeHeader()
3889-
w.WriteHeader(http.StatusOK)
3890-
w.(http.Flusher).Flush()
3891-
bodyWrite(w)
3892-
select {
3893-
case <-w.(http.CloseNotifier).CloseNotify():
3894-
// client closed connection before completion
3895-
if closeMode == shutdown || closeMode == shutdownCancel {
3896-
t.Error("expected request to complete")
3897-
}
3898-
case <-clientDone:
3899-
if closeMode == closeAtHeaders || closeMode == closeAtBody {
3900-
t.Error("expected connection closed by client")
3901-
}
3902-
}
3885+
tc.writeHeaders(HeadersFrameParam{
3886+
StreamID: rt.streamID(),
3887+
EndHeaders: true,
3888+
EndStream: false,
3889+
BlockFragment: tc.makeHeaderBlockFragment(
3890+
":status", "200",
3891+
),
39033892
})
3904-
tr := &Transport{TLSClientConfig: tlsConfigInsecure}
3905-
defer tr.CloseIdleConnections()
3906-
ctx := context.Background()
3907-
cc, err := tr.dialClientConn(ctx, ts.Listener.Addr().String(), false)
3908-
req, err := http.NewRequest("GET", ts.URL, nil)
3909-
if err != nil {
3910-
t.Fatal(err)
3911-
}
3912-
if closeMode == closeAtHeaders {
3913-
beforeHeader = func() {
3914-
if err := cc.Close(); err != nil {
3915-
t.Error(err)
3916-
}
3917-
close(closeDone)
3918-
}
3919-
}
3920-
var sendBody chan struct{}
3921-
if closeMode == closeAtBody {
3922-
sendBody = make(chan struct{})
3923-
bodyWrite = func(w http.ResponseWriter) {
3924-
<-sendBody
3925-
b := make([]byte, 32)
3926-
w.Write(b)
3927-
w.(http.Flusher).Flush()
3928-
if err := cc.Close(); err != nil {
3929-
t.Errorf("unexpected ClientConn close error: %v", err)
3930-
}
3931-
close(closeDone)
3932-
w.Write(b)
3933-
w.(http.Flusher).Flush()
3934-
}
3935-
}
3936-
res, err := cc.RoundTrip(req)
3937-
if res != nil {
3938-
defer res.Body.Close()
3939-
}
3940-
if closeMode == closeAtHeaders {
3941-
got := fmt.Sprint(err)
3942-
want := "http2: client connection force closed via ClientConn.Close"
3943-
if got != want {
3944-
t.Fatalf("RoundTrip error = %v, want %v", got, want)
3945-
}
3946-
} else {
3947-
if err != nil {
3948-
t.Fatalf("RoundTrip: %v", err)
3949-
}
3950-
if got, want := activeStreams(cc), 1; got != want {
3951-
t.Errorf("got %d active streams, want %d", got, want)
3952-
}
3953-
}
3954-
switch closeMode {
3955-
case shutdownCancel:
3956-
if err = cc.Shutdown(canceledCtx); err != context.Canceled {
3957-
t.Errorf("got %v, want %v", err, context.Canceled)
3958-
}
3959-
if cc.closing == false {
3960-
t.Error("expected closing to be true")
3961-
}
3962-
if cc.CanTakeNewRequest() == true {
3963-
t.Error("CanTakeNewRequest to return false")
3964-
}
3965-
if v, want := len(cc.streams), 1; v != want {
3966-
t.Errorf("expected %d active streams, got %d", want, v)
3967-
}
3968-
clientDone <- struct{}{}
3969-
<-handlerDone
3970-
case shutdown:
3971-
wait := make(chan struct{})
3972-
shutdownEnterWaitStateHook = func() {
3973-
close(wait)
3974-
shutdownEnterWaitStateHook = func() {}
3975-
}
3976-
defer func() { shutdownEnterWaitStateHook = func() {} }()
3977-
shutdown := make(chan struct{}, 1)
3978-
go func() {
3979-
if err = cc.Shutdown(context.Background()); err != nil {
3980-
t.Error(err)
3981-
}
3982-
close(shutdown)
3983-
}()
3984-
// Let the shutdown to enter wait state
3985-
<-wait
3986-
cc.mu.Lock()
3987-
if cc.closing == false {
3988-
t.Error("expected closing to be true")
3989-
}
3990-
cc.mu.Unlock()
3991-
if cc.CanTakeNewRequest() == true {
3992-
t.Error("CanTakeNewRequest to return false")
3993-
}
3994-
if got, want := activeStreams(cc), 1; got != want {
3995-
t.Errorf("got %d active streams, want %d", got, want)
3996-
}
3997-
// Let the active request finish
3998-
clientDone <- struct{}{}
3999-
// Wait for the shutdown to end
4000-
select {
4001-
case <-shutdown:
4002-
case <-time.After(2 * time.Second):
4003-
t.Fatal("expected server connection to close")
4004-
}
4005-
case closeAtHeaders, closeAtBody:
4006-
if closeMode == closeAtBody {
4007-
go close(sendBody)
4008-
if _, err := io.Copy(io.Discard, res.Body); err == nil {
4009-
t.Error("expected a Copy error, got nil")
4010-
}
4011-
}
4012-
<-closeDone
4013-
if got, want := activeStreams(cc), 0; got != want {
4014-
t.Errorf("got %d active streams, want %d", got, want)
4015-
}
4016-
// wait for server to get the connection close notice
4017-
select {
4018-
case <-handlerDone:
4019-
case <-time.After(2 * time.Second):
4020-
t.Fatal("expected server connection to close")
4021-
}
4022-
}
4023-
}
4024-
4025-
// The client closes the connection just after the server got the client's HEADERS
4026-
// frame, but before the server sends its HEADERS response back. The expected
4027-
// result is an error on RoundTrip explaining the client closed the connection.
4028-
func TestClientConnCloseAtHeaders(t *testing.T) {
4029-
testClientConnClose(t, closeAtHeaders)
4030-
}
3893+
tc.writeData(rt.streamID(), false, make([]byte, 64))
3894+
tc.cc.Close()
3895+
synctest.Wait()
40313896

4032-
// The client closes the connection between two server's response DATA frames.
4033-
// The expected behavior is a response body io read error on the client.
4034-
func TestClientConnCloseAtBody(t *testing.T) {
4035-
testClientConnClose(t, closeAtBody)
3897+
if _, err := io.Copy(io.Discard, rt.response().Body); err == nil {
3898+
t.Error("expected a Copy error, got nil")
3899+
}
40363900
}
40373901

40383902
// The client sends a GOAWAY frame before the server finished processing a request.
40393903
// We expect the connection not to close until the request is completed.
4040-
func TestClientConnShutdown(t *testing.T) {
4041-
testClientConnClose(t, shutdown)
3904+
func TestClientConnShutdown(t *testing.T) { synctestTest(t, testClientConnShutdown) }
3905+
func testClientConnShutdown(t testing.TB) {
3906+
tc := newTestClientConn(t)
3907+
tc.greet()
3908+
3909+
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
3910+
rt := tc.roundTrip(req)
3911+
tc.wantFrameType(FrameHeaders)
3912+
3913+
go tc.cc.Shutdown(context.Background())
3914+
synctest.Wait()
3915+
3916+
tc.wantFrameType(FrameGoAway)
3917+
tc.wantIdle() // connection is not closed
3918+
body := []byte("body")
3919+
tc.writeHeaders(HeadersFrameParam{
3920+
StreamID: rt.streamID(),
3921+
EndHeaders: true,
3922+
EndStream: false,
3923+
BlockFragment: tc.makeHeaderBlockFragment(
3924+
":status", "200",
3925+
),
3926+
})
3927+
tc.writeData(rt.streamID(), true, body)
3928+
3929+
rt.wantStatus(200)
3930+
rt.wantBody(body)
3931+
3932+
// Now that the client has received the response, it closes the connection.
3933+
tc.wantClosed()
40423934
}
40433935

40443936
// The client sends a GOAWAY frame before the server finishes processing a request,
40453937
// but cancels the passed context before the request is completed. The expected
40463938
// behavior is the client closing the connection after the context is canceled.
4047-
func TestClientConnShutdownCancel(t *testing.T) {
4048-
testClientConnClose(t, shutdownCancel)
3939+
func TestClientConnShutdownCancel(t *testing.T) { synctestTest(t, testClientConnShutdownCancel) }
3940+
func testClientConnShutdownCancel(t testing.TB) {
3941+
tc := newTestClientConn(t)
3942+
tc.greet()
3943+
3944+
req, _ := http.NewRequest("GET", "https://dummy.tld/", nil)
3945+
rt := tc.roundTrip(req)
3946+
tc.wantFrameType(FrameHeaders)
3947+
3948+
ctx, cancel := context.WithCancel(t.Context())
3949+
var shutdownErr error
3950+
go func() {
3951+
shutdownErr = tc.cc.Shutdown(ctx)
3952+
}()
3953+
synctest.Wait()
3954+
3955+
tc.wantFrameType(FrameGoAway)
3956+
tc.wantIdle() // connection is not closed
3957+
3958+
cancel()
3959+
synctest.Wait()
3960+
3961+
if shutdownErr != context.Canceled {
3962+
t.Fatalf("ClientConn.Shutdown(ctx) did not return context.Canceled after cancelling context")
3963+
}
3964+
3965+
// The documentation for this test states:
3966+
// The expected behavior is the client closing the connection
3967+
// after the context is canceled.
3968+
//
3969+
// This seems reasonable, but it isn't what we do.
3970+
// When ClientConn.Shutdown's context is canceled, Shutdown returns but
3971+
// the connection is not closed.
3972+
//
3973+
// TODO: Figure out the correct behavior.
3974+
if rt.done() {
3975+
t.Fatal("RoundTrip unexpectedly returned during shutdown")
3976+
}
40493977
}
40503978

40513979
// Issue 25009: use Request.GetBody if present, even if it seems like
@@ -4164,10 +4092,6 @@ readFrames:
41644092
if err := rt.err(); err != bodyReadError {
41654093
t.Fatalf("err = %v; want %v", err, bodyReadError)
41664094
}
4167-
4168-
if got := activeStreams(tc.cc); got != 0 {
4169-
t.Fatalf("active streams count: %v; want 0", got)
4170-
}
41714095
}
41724096

41734097
func TestTransportBodyReadError_Immediately(t *testing.T) { testTransportBodyReadError(t, nil) }

0 commit comments

Comments
 (0)