feat(cmd/transpilerd): update transpilerd to use http server package

The http package now contains a server that handles signals and proper
shutdown procedure. It has now been updated to use it.

The http package has also added a `ListenAndServe` convenience function
that is similar to the `net/http` one, but also takes in a logger and
will automatically use the most common signals when running an http
server.
pull/10616/head
Jonathan A. Sternberg 2018-05-23 09:27:17 -05:00
parent 0c422a863f
commit d8e4f4f2e0
2 changed files with 54 additions and 14 deletions

View File

@ -3,7 +3,6 @@ package main
import (
"fmt"
"log"
nethttp "net/http"
"os"
"strings"
@ -61,7 +60,9 @@ func transpileF(cmd *cobra.Command, args []string) {
handler.Handler = transpileHandler
log.Printf("Starting transpilerd on %s\n", flags.bindAddr)
log.Fatal(nethttp.ListenAndServe(flags.bindAddr, handler))
if err := http.ListenAndServe(flags.bindAddr, handler, nil); err != nil {
log.Fatal(err)
}
}
func main() {

View File

@ -7,6 +7,7 @@ import (
"os"
"os/signal"
"sync"
"syscall"
"time"
"go.uber.org/zap"
@ -23,10 +24,10 @@ const DefaultShutdownTimeout = 20 * time.Second
type Server struct {
ShutdownTimeout time.Duration
srv *http.Server
signalCh chan os.Signal
logger *zap.Logger
wg sync.WaitGroup
srv *http.Server
signals map[os.Signal]struct{}
logger *zap.Logger
wg sync.WaitGroup
}
// NewServer returns a new server struct that can be used.
@ -50,14 +51,17 @@ func (s *Server) Serve(listener net.Listener) error {
// When we return, wait for all pending goroutines to finish.
defer s.wg.Wait()
signalCh, cancel := s.notifyOnSignals()
defer cancel()
errCh := s.serve(listener)
select {
case err := <-errCh:
// The server has failed and reported an error.
return err
case <-s.signalCh:
case <-signalCh:
// We have received an interrupt. Signal the shutdown process.
return s.shutdown()
return s.shutdown(signalCh)
}
}
@ -74,7 +78,7 @@ func (s *Server) serve(listener net.Listener) <-chan error {
return errCh
}
func (s *Server) shutdown() error {
func (s *Server) shutdown(signalCh <-chan os.Signal) error {
// The shutdown needs to succeed in 20 seconds or less.
ctx, cancel := context.WithTimeout(context.Background(), s.ShutdownTimeout)
defer cancel()
@ -87,7 +91,7 @@ func (s *Server) shutdown() error {
go func() {
defer s.wg.Done()
select {
case <-s.signalCh:
case <-signalCh:
cancel()
case <-done:
}
@ -96,10 +100,45 @@ func (s *Server) shutdown() error {
}
// ListenForSignals registers the the server to listen for the given signals
// to shutdown the server.
// to shutdown the server. The signals are not captured until Serve is called.
func (s *Server) ListenForSignals(signals ...os.Signal) {
if s.signalCh == nil {
s.signalCh = make(chan os.Signal, 4)
if s.signals == nil {
s.signals = make(map[os.Signal]struct{})
}
for _, sig := range signals {
s.signals[sig] = struct{}{}
}
signal.Notify(s.signalCh, signals...)
}
func (s *Server) notifyOnSignals() (_ <-chan os.Signal, cancel func()) {
if len(s.signals) == 0 {
return nil, func() {}
}
// Retrieve which signals we want to be notified on.
signals := make([]os.Signal, 0, len(s.signals))
for sig := range s.signals {
signals = append(signals, sig)
}
// Create the signal channel and mark ourselves to be notified
// of signals. Allow up to two signals for each signal type we catch.
signalCh := make(chan os.Signal, len(signals)*2)
signal.Notify(signalCh, signals...)
return signalCh, func() { signal.Stop(signalCh) }
}
// ListenAndServe is a convenience method for opening a listener using the address
// and then serving the handler on that address. This method sets up the typical
// signal handlers.
func ListenAndServe(addr string, handler http.Handler, logger *zap.Logger) error {
l, err := net.Listen("tcp", addr)
if err != nil {
return err
}
server := NewServer(handler, logger)
server.ListenForSignals(os.Interrupt, syscall.SIGTERM)
return server.Serve(l)
}