Implement graceful shutdown and propagate request context (#468)

* feature: Implement graceful shutdown

Propagate the request context to the Redis client.
It is possible to propagate a context cancel to Redis client if the connection is closed by the HTTP client.
The redis.Cmdable cannot use WithContext, so added the Client interface to handle redis.Client and redis.ClusterClient transparently.

Added handling of Unix signals to http server.

Upgrade go-redis/redis to v7.

* Update dependencies

- Upgrade golang/x/* and google-api-go
- Migrate fsnotify import from gopkg.in to github.com
- Replace bmizerany/assert with stretchr/testify/assert

* add doc for  wrapper interface

* Update CHANGELOG.md

* fix: upgrade fsnotify to v1.4.9

* fix: remove unnessary logging

* fix: wait until  all connections have been closed

* refactor: move chan to main for testing

* add assert to check if stop chan is empty

* add an idiomatic for sync.WaitGroup with timeout
This commit is contained in:
Mitsuo Heijo
2020-04-04 16:12:38 +01:00
committed by GitHub
parent bdc686103e
commit c7bfbdecef
11 changed files with 177 additions and 55 deletions
+28 -14
View File
@@ -1,7 +1,9 @@
package main
import (
"context"
"crypto/tls"
"errors"
"net"
"net/http"
"strings"
@@ -14,6 +16,7 @@ import (
type Server struct {
Handler http.Handler
Opts *Options
stop chan struct{} // channel for waiting shutdown
}
// ListenAndServe will serve traffic on HTTP or HTTPS depending on TLS options
@@ -90,13 +93,7 @@ func (s *Server) ServeHTTP() {
logger.Fatalf("FATAL: listen (%s, %s) failed - %s", networkType, listenAddr, err)
}
logger.Printf("HTTP: listening on %s", listenAddr)
server := &http.Server{Handler: s.Handler}
err = server.Serve(listener)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
logger.Printf("ERROR: http.Serve() - %s", err)
}
s.serve(listener)
logger.Printf("HTTP: closing %s", listener.Addr())
}
@@ -125,16 +122,33 @@ func (s *Server) ServeHTTPS() {
logger.Printf("HTTPS: listening on %s", ln.Addr())
tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
srv := &http.Server{Handler: s.Handler}
err = srv.Serve(tlsListener)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
logger.Printf("ERROR: https.Serve() - %s", err)
}
s.serve(tlsListener)
logger.Printf("HTTPS: closing %s", tlsListener.Addr())
}
func (s *Server) serve(listener net.Listener) {
srv := &http.Server{Handler: s.Handler}
// See https://golang.org/pkg/net/http/#Server.Shutdown
idleConnsClosed := make(chan struct{})
go func() {
<-s.stop // wait notification for stopping server
// We received an interrupt signal, shut down.
if err := srv.Shutdown(context.Background()); err != nil {
// Error from closing listeners, or context timeout:
logger.Printf("HTTP server Shutdown: %v", err)
}
close(idleConnsClosed)
}()
err := srv.Serve(listener)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.Printf("ERROR: http.Serve() - %s", err)
}
<-idleConnsClosed
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually