Skip to content

Commit d34deae

Browse files
committed
quic: ensure Endpoint.Close waits for connection loops to exit
In CL 822984, Endpoint.Close was modified to only return when its listen loop has actually exited. However, if Endpoint.packetConn is closed prior to Endpoint.Close being called (as in the case of net/http), Endpoint.Close can still return prematurely before all connection loops exit. Fix this by making sure that we also wait for all connection loops to exit. For golang/go#70914 Change-Id: I7dbc985475e845c89e2fa9a7755718326a6a6964 Reviewed-on: https://go-review.googlesource.com/c/net/+/823524 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Damien Neil <dneil@google.com>
1 parent 55577aa commit d34deae

2 files changed

Lines changed: 33 additions & 16 deletions

File tree

quic/endpoint.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,21 @@ func (e *Endpoint) Close(ctx context.Context) error {
153153
for _, c := range conns {
154154
c.Abort(localTransportError{code: errNo})
155155
}
156+
156157
select {
157-
case <-e.closec:
158158
case <-ctx.Done():
159-
for _, c := range conns {
160-
c.exit()
161-
}
162-
<-e.closec
159+
case <-e.closec:
160+
}
161+
for _, c := range conns {
162+
c.exit()
163+
}
164+
// We should only return once all conn loops and the listen loop exit.
165+
// That is, there should no longer be any lingering goroutines.
166+
for _, c := range conns {
167+
<-c.donec
163168
}
164-
return ctx.Err() // nil if context hasn't expired
169+
<-e.closec
170+
return ctx.Err()
165171
}
166172

167173
// Accept waits for and returns the next connection.

quic/endpoint_test.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,16 +154,27 @@ func TestEndpointClosePacketConn(t *testing.T) {
154154
}
155155

156156
func TestEndpointCloseCanceledContext(t *testing.T) {
157-
cli, _ := newLocalConnPair(t, &Config{}, &Config{})
158-
// Closing an endpoint with a canceled context terminates connections
159-
// immediately, without waiting for peer acknowledgement. However, it
160-
// should still wait until the endpoint is actually closed before
161-
// returning.
162-
if err := cli.endpoint.Close(canceledContext()); !errors.Is(err, context.Canceled) {
163-
t.Errorf("Endpoint.Close(canceledContext()) = %v, want %v", err, context.Canceled)
164-
}
165-
if err := cli.Wait(canceledContext()); !errors.Is(err, errConnClosed) {
166-
t.Errorf("Conn.Wait(canceledContext()) = %v, want %v", err, errConnClosed)
157+
for _, closePacketConn := range []bool{false, true} {
158+
name := "packet conn open"
159+
if closePacketConn {
160+
name = "packet conn closed"
161+
}
162+
t.Run(name, func(t *testing.T) {
163+
cli, _ := newLocalConnPair(t, &Config{}, &Config{})
164+
if closePacketConn {
165+
cli.endpoint.packetConn.Close()
166+
}
167+
// Closing an endpoint with a canceled context terminates connections
168+
// immediately, without waiting for peer acknowledgement. However, it
169+
// should still wait until the endpoint is actually closed before
170+
// returning.
171+
if err := cli.endpoint.Close(canceledContext()); !errors.Is(err, context.Canceled) {
172+
t.Errorf("Endpoint.Close(canceledContext()) = %v, want %v", err, context.Canceled)
173+
}
174+
if err := cli.Wait(canceledContext()); !errors.Is(err, errConnClosed) {
175+
t.Errorf("Conn.Wait(canceledContext()) = %v, want %v", err, errConnClosed)
176+
}
177+
})
167178
}
168179
}
169180

0 commit comments

Comments
 (0)