From 9edd4874ac8d8dab36af5133341f9742724122a0 Mon Sep 17 00:00:00 2001 From: emile Date: Fri, 25 Sep 2015 22:02:55 +0200 Subject: [PATCH 1/2] refactoring server --- traefik.go | 90 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/traefik.go b/traefik.go index 875f1377e..44d66d005 100644 --- a/traefik.go +++ b/traefik.go @@ -47,6 +47,7 @@ func main() { defer close(configurationChan) var providers = []Provider{} var sigs = make(chan os.Signal, 1) + defer close(sigs) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) // load global configuration @@ -91,7 +92,9 @@ func main() { if err == nil { currentConfiguration = configuration configurationRouter = newConfigurationRouter - srv.Stop(time.Duration(globalConfiguration.GraceTimeOut) * time.Second) + oldServer := srv + srv = prepareServer(configurationRouter, globalConfiguration, loggerMiddleware, metrics) + stopServer(oldServer, globalConfiguration) time.Sleep(3 * time.Second) } else { log.Error("Error loading new configuration, aborted ", err) @@ -135,50 +138,20 @@ func main() { sig := <-sigs log.Infof("I have to go... %+v", sig) goAway = true - srv.Stop(time.Duration(globalConfiguration.GraceTimeOut) * time.Second) + stopServer(srv, globalConfiguration) }() + //negroni.Use(middlewares.NewCircuitBreaker(oxyLogger)) + //negroni.Use(middlewares.NewRoutes(configurationRouter)) + srv = prepareServer(configurationRouter, globalConfiguration, loggerMiddleware, metrics) + for { if goAway { break } - // middlewares - var negroni = negroni.New() - negroni.Use(metrics) - negroni.Use(loggerMiddleware) - //negroni.Use(middlewares.NewCircuitBreaker(oxyLogger)) - //negroni.Use(middlewares.NewRoutes(configurationRouter)) - negroni.UseHandler(configurationRouter) - - srv = &graceful.Server{ - Timeout: time.Duration(globalConfiguration.GraceTimeOut) * time.Second, - NoSignalHandling: true, - - Server: &http.Server{ - Addr: globalConfiguration.Port, - Handler: negroni, - }, - } - go func() { - if len(globalConfiguration.CertFile) > 0 && len(globalConfiguration.KeyFile) > 0 { - err := srv.ListenAndServeTLS(globalConfiguration.CertFile, globalConfiguration.KeyFile) - if err != nil { - netOpError, ok := err.(*net.OpError) - if ok && netOpError.Err.Error() != "use of closed network connection" { - log.Fatal("Error creating server: ", err) - } - } - } else { - err := srv.ListenAndServe() - if err != nil { - netOpError, ok := err.(*net.OpError) - if ok && netOpError.Err.Error() != "use of closed network connection" { - log.Fatal("Error creating server: ", err) - } - } - } + startServer(srv, globalConfiguration) }() log.Info("Started") <-srv.StopChan() @@ -186,6 +159,49 @@ func main() { } } +func startServer(srv *graceful.Server, globalConfiguration *GlobalConfiguration){ + if len(globalConfiguration.CertFile) > 0 && len(globalConfiguration.KeyFile) > 0 { + err := srv.ListenAndServeTLS(globalConfiguration.CertFile, globalConfiguration.KeyFile) + if err != nil { + netOpError, ok := err.(*net.OpError) + if ok && netOpError.Err.Error() != "use of closed network connection" { + log.Fatal("Error creating server: ", err) + } + } + } else { + err := srv.ListenAndServe() + if err != nil { + netOpError, ok := err.(*net.OpError) + if ok && netOpError.Err.Error() != "use of closed network connection" { + log.Fatal("Error creating server: ", err) + } + } + } +} + +func stopServer(srv *graceful.Server, globalConfiguration *GlobalConfiguration){ + srv.Stop(time.Duration(globalConfiguration.GraceTimeOut) * time.Second) +} + +func prepareServer(router *mux.Router, globalConfiguration *GlobalConfiguration, middlewares...negroni.Handler) (*graceful.Server){ + // middlewares + var negroni = negroni.New() + for _, middleware := range middlewares { + negroni.Use(middleware) + } + negroni.UseHandler(router) + + return &graceful.Server{ + Timeout: time.Duration(globalConfiguration.GraceTimeOut) * time.Second, + NoSignalHandling: true, + + Server: &http.Server{ + Addr: globalConfiguration.Port, + Handler: negroni, + }, + } +} + func LoadConfig(configuration *Configuration, globalConfiguration *GlobalConfiguration) (*mux.Router, error) { router := mux.NewRouter() router.NotFoundHandler = http.HandlerFunc(notFoundHandler) From 6d06f1a86238b592f687c35240982a1bd14d26c3 Mon Sep 17 00:00:00 2001 From: emile Date: Sat, 26 Sep 2015 00:20:45 +0200 Subject: [PATCH 2/2] graceful shutdown using manners --- Godeps/Godeps.json | 14 +++------ README.md | 2 +- traefik.go | 78 +++++++++++++++++++++++++--------------------- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index dc9563a3e..cadcb2eba 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -69,6 +69,11 @@ "ImportPath": "github.com/mailgun/log", "Rev": "44874009257d4d47ba9806f1b7f72a32a015e4d8" }, + { + "ImportPath": "github.com/mailgun/manners", + "Comment": "0.3.1-30-g37136f7", + "Rev": "37136f736785d7c6aa3b9a27b4b2dd1028ca6d79" + }, { "ImportPath": "github.com/mailgun/oxy/cbreaker", "Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b" @@ -101,19 +106,10 @@ "ImportPath": "github.com/thoas/stats", "Rev": "54ed61c2b47e263ae2f01b86837b0c4bd1da28e8" }, - { - "ImportPath": "github.com/tylerb/graceful", - "Comment": "v1.2.1", - "Rev": "ac9ebe4f1ee151ac1eeeaef32957085cba64d508" - }, { "ImportPath": "github.com/unrolled/render", "Rev": "26b4e3aac686940fe29521545afad9966ddfc80c" }, - { - "ImportPath": "golang.org/x/net/netutil", - "Rev": "d9558e5c97f85372afee28cf2b6059d7d3818919" - }, { "ImportPath": "gopkg.in/alecthomas/kingpin.v2", "Comment": "v2.0.12", diff --git a/README.md b/README.md index 53b06a486..a7b1a2359 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/ * [Oxy](https://github.com/mailgun/oxy/): an awsome proxy library made by Mailgun guys * [Gorilla mux](https://github.com/gorilla/mux): famous request router * [Negroni](https://github.com/codegangsta/negroni): web middlewares made simple -* [Graceful](https://github.com/tylerb/graceful): graceful shutdown of http.Handler servers +* [Manners](https://github.com/mailgun/manners): graceful shutdown of http.Handler servers # Quick start diff --git a/traefik.go b/traefik.go index 44d66d005..d4214773d 100644 --- a/traefik.go +++ b/traefik.go @@ -17,13 +17,14 @@ import ( "github.com/codegangsta/negroni" "github.com/emilevauge/traefik/middlewares" "github.com/gorilla/mux" + "github.com/mailgun/manners" "github.com/mailgun/oxy/cbreaker" "github.com/mailgun/oxy/forward" "github.com/mailgun/oxy/roundrobin" "github.com/thoas/stats" - "github.com/tylerb/graceful" "github.com/unrolled/render" "gopkg.in/alecthomas/kingpin.v2" + "runtime" ) var ( @@ -39,15 +40,18 @@ var ( ) func main() { + runtime.GOMAXPROCS(runtime.NumCPU()) kingpin.Parse() fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags) - var srv *graceful.Server + var srv *manners.GracefulServer var configurationRouter *mux.Router var configurationChan = make(chan *Configuration, 10) defer close(configurationChan) - var providers = []Provider{} var sigs = make(chan os.Signal, 1) defer close(sigs) + var stopChan = make(chan bool) + defer close(stopChan) + var providers = []Provider{} signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) // load global configuration @@ -93,9 +97,14 @@ func main() { currentConfiguration = configuration configurationRouter = newConfigurationRouter oldServer := srv - srv = prepareServer(configurationRouter, globalConfiguration, loggerMiddleware, metrics) - stopServer(oldServer, globalConfiguration) - time.Sleep(3 * time.Second) + newsrv := prepareServer(configurationRouter, globalConfiguration, oldServer, loggerMiddleware, metrics) + go startServer(newsrv, globalConfiguration) + srv = newsrv + time.Sleep(2 * time.Second) + if oldServer != nil { + log.Info("Stopping old server") + oldServer.Close() + } } else { log.Error("Error loading new configuration, aborted ", err) } @@ -133,33 +142,25 @@ func main() { }() } - goAway := false go func() { sig := <-sigs log.Infof("I have to go... %+v", sig) - goAway = true - stopServer(srv, globalConfiguration) + log.Info("Stopping server") + srv.Close() + stopChan <- true }() //negroni.Use(middlewares.NewCircuitBreaker(oxyLogger)) //negroni.Use(middlewares.NewRoutes(configurationRouter)) - srv = prepareServer(configurationRouter, globalConfiguration, loggerMiddleware, metrics) + srv = prepareServer(configurationRouter, globalConfiguration, nil, loggerMiddleware, metrics) + go startServer(srv, globalConfiguration) - for { - if goAway { - break - } - - go func() { - startServer(srv, globalConfiguration) - }() - log.Info("Started") - <-srv.StopChan() - log.Info("Stopped") - } + <-stopChan + log.Info("Shutting down") } -func startServer(srv *graceful.Server, globalConfiguration *GlobalConfiguration){ +func startServer(srv *manners.GracefulServer, globalConfiguration *GlobalConfiguration) { + log.Info("Starting server") if len(globalConfiguration.CertFile) > 0 && len(globalConfiguration.KeyFile) > 0 { err := srv.ListenAndServeTLS(globalConfiguration.CertFile, globalConfiguration.KeyFile) if err != nil { @@ -177,13 +178,11 @@ func startServer(srv *graceful.Server, globalConfiguration *GlobalConfiguration) } } } + log.Info("Server stopped") } -func stopServer(srv *graceful.Server, globalConfiguration *GlobalConfiguration){ - srv.Stop(time.Duration(globalConfiguration.GraceTimeOut) * time.Second) -} - -func prepareServer(router *mux.Router, globalConfiguration *GlobalConfiguration, middlewares...negroni.Handler) (*graceful.Server){ +func prepareServer(router *mux.Router, globalConfiguration *GlobalConfiguration, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) *manners.GracefulServer { + log.Info("Preparing server") // middlewares var negroni = negroni.New() for _, middleware := range middlewares { @@ -191,14 +190,23 @@ func prepareServer(router *mux.Router, globalConfiguration *GlobalConfiguration, } negroni.UseHandler(router) - return &graceful.Server{ - Timeout: time.Duration(globalConfiguration.GraceTimeOut) * time.Second, - NoSignalHandling: true, - - Server: &http.Server{ + if oldServer == nil { + return manners.NewWithServer( + &http.Server{ + Addr: globalConfiguration.Port, + Handler: negroni, + }) + } else { + server, err := oldServer.HijackListener(&http.Server{ Addr: globalConfiguration.Port, Handler: negroni, - }, + }, nil) + if err != nil { + log.Fatalf("Error hijacking server %s", err) + return nil + } else { + return server + } } } @@ -231,7 +239,7 @@ func LoadConfig(configuration *Configuration, globalConfiguration *GlobalConfigu } else { log.Debugf("Reusing backend %s", frontend.Backend) } - // stream.New(backends[frontend.Backend], stream.Retry("IsNetworkError() && Attempts() <= " + strconv.Itoa(globalConfiguration.Replay)), stream.Logger(oxyLogger)) + // stream.New(backends[frontend.Backend], stream.Retry("IsNetworkError() && Attempts() <= " + strconv.Itoa(globalConfiguration.Replay)), stream.Logger(oxyLogger)) var negroni = negroni.New() negroni.Use(middlewares.NewCircuitBreaker(backends[frontend.Backend], cbreaker.Logger(oxyLogger))) newRoute.Handler(negroni)