http3: close QUIC listeners created by Server on graceful shutdown (#5101)

This commit is contained in:
Marten Seemann
2025-05-08 14:22:11 +08:00
committed by GitHub
parent 8296ba7d19
commit 1725cb0878
2 changed files with 101 additions and 35 deletions

View File

@@ -719,7 +719,20 @@ func (s *Server) Shutdown(ctx context.Context) error {
return nil
}
s.graceCancel()
// close all listeners
var closeErrs []error
for _, l := range s.listeners {
if l.createdLocally {
if err := (*l.ln).Close(); err != nil {
closeErrs = append(closeErrs, err)
}
}
}
s.mutex.Unlock()
if len(closeErrs) > 0 {
return errors.Join(closeErrs...)
}
if s.connCount.Load() == 0 {
return s.Close()

View File

@@ -201,14 +201,23 @@ func TestGracefulShutdownPendingStreams(t *testing.T) {
func TestHTTP3ListenerClosing(t *testing.T) {
t.Run("application listener", func(t *testing.T) {
testHTTP3ListenerClosing(t, true)
testHTTP3ListenerClosing(t, false, true)
})
t.Run("listener created by the http3.Server", func(t *testing.T) {
testHTTP3ListenerClosing(t, false)
testHTTP3ListenerClosing(t, false, false)
})
}
func testHTTP3ListenerClosing(t *testing.T, useApplicationListener bool) {
func TestHTTP3ListenerGracefulShutdown(t *testing.T) {
t.Run("application listener", func(t *testing.T) {
testHTTP3ListenerClosing(t, true, true)
})
t.Run("listener created by the http3.Server", func(t *testing.T) {
testHTTP3ListenerClosing(t, true, false)
})
}
func testHTTP3ListenerClosing(t *testing.T, graceful, useApplicationListener bool) {
dial := func(t *testing.T, ctx context.Context, u *url.URL) error {
t.Helper()
tlsConf := getTLSClientConfig()
@@ -231,6 +240,12 @@ func testHTTP3ListenerClosing(t *testing.T, useApplicationListener bool) {
mux.HandleFunc("/ok", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
handlerChan := make(chan struct{})
mux.HandleFunc("/long", func(w http.ResponseWriter, r *http.Request) {
<-handlerChan
w.WriteHeader(http.StatusOK)
})
tlsConf := http3.ConfigureTLSConfig(getTLSConfig())
server := &http3.Server{
Handler: mux,
@@ -260,8 +275,21 @@ func testHTTP3ListenerClosing(t *testing.T, useApplicationListener bool) {
defer cancel()
require.NoError(t, dial(t, ctx, u))
// close the server
longReqChan := make(chan error, 1)
shutdownChan := make(chan error, 1)
if graceful {
go func() {
u := &url.URL{Scheme: "https", Host: host, Path: "/long"}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
longReqChan <- dial(t, ctx, u)
}()
time.Sleep(scaleDuration(10 * time.Millisecond))
go func() { shutdownChan <- server.Shutdown(context.Background()) }()
} else {
require.NoError(t, server.Close())
}
select {
case err := <-serveChan:
@@ -275,9 +303,7 @@ func testHTTP3ListenerClosing(t *testing.T, useApplicationListener bool) {
ctx, cancel := context.WithTimeout(context.Background(), scaleDuration(10*time.Millisecond))
defer cancel()
require.ErrorIs(t, dial(t, ctx, u), context.DeadlineExceeded)
return
}
} else {
// If the listener was created by the application, it will not be closed,
// and it can be used to accept new connections.
errChan := make(chan error, 1)
@@ -298,7 +324,7 @@ func testHTTP3ListenerClosing(t *testing.T, useApplicationListener bool) {
}
}()
for range 3 {
for range 2 {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
err := dial(t, ctx, u)
@@ -312,4 +338,31 @@ func testHTTP3ListenerClosing(t *testing.T, useApplicationListener bool) {
t.Fatal("server did not accept connection")
}
}
}
// the long request should have been terminated
if graceful {
select {
case err := <-longReqChan:
t.Fatalf("request should not have terminated: %v", err)
case err := <-shutdownChan:
t.Fatalf("graceful shutdown should not have returned: %v", err)
case <-time.After(scaleDuration(10 * time.Millisecond)):
}
close(handlerChan)
select {
case err := <-longReqChan:
require.NoError(t, err)
case <-time.After(time.Second):
t.Fatal("long request did not terminate")
}
select {
case err := <-shutdownChan:
require.NoError(t, err)
case <-time.After(time.Second):
t.Fatal("shutdown did not complete")
}
}
}