diff --git a/cmd/web.go b/cmd/web.go index 7dfd19d6405d..ecaf74daaa5e 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -31,6 +31,7 @@ import ( "code.gitea.io/gitea/routers/repo" "code.gitea.io/gitea/routers/user" + "github.com/facebookgo/grace/gracehttp" "github.com/go-macaron/binding" "github.com/go-macaron/cache" "github.com/go-macaron/captcha" @@ -615,10 +616,29 @@ func runWeb(ctx *cli.Context) error { var err error switch setting.Protocol { case setting.HTTP: - err = http.ListenAndServe(listenAddr, m) + err = gracehttp.Serve(&http.Server{ + Addr: listenAddr, + Handler: m, + }) case setting.HTTPS: - server := &http.Server{Addr: listenAddr, TLSConfig: &tls.Config{MinVersion: tls.VersionTLS10}, Handler: m} - err = server.ListenAndServeTLS(setting.CertFile, setting.KeyFile) + config := &tls.Config{ + MinVersion: tls.VersionTLS10, + } + if config.NextProtos == nil { + config.NextProtos = []string{"http/1.1"} + } + + config.Certificates = make([]tls.Certificate, 1) + config.Certificates[0], err = tls.LoadX509KeyPair(setting.CertFile, setting.KeyFile) + if err != nil { + log.Fatal(4, "Failed to load https cert file %s: %v", listenAddr, err) + } + + err = gracehttp.Serve(&http.Server{ + Addr: listenAddr, + Handler: m, + TLSConfig: config, + }) case setting.FCGI: err = fcgi.Serve(nil, m) case setting.UnixSocket: diff --git a/vendor/github.com/facebookgo/grace/gracehttp/http.go b/vendor/github.com/facebookgo/grace/gracehttp/http.go new file mode 100644 index 000000000000..93e26a684f05 --- /dev/null +++ b/vendor/github.com/facebookgo/grace/gracehttp/http.go @@ -0,0 +1,186 @@ +// Package gracehttp provides easy to use graceful restart +// functionality for HTTP server. +package gracehttp + +import ( + "bytes" + "crypto/tls" + "flag" + "fmt" + "log" + "net" + "net/http" + "os" + "os/signal" + "sync" + "syscall" + + "github.com/facebookgo/grace/gracenet" + "github.com/facebookgo/httpdown" +) + +var ( + verbose = flag.Bool("gracehttp.log", true, "Enable logging.") + didInherit = os.Getenv("LISTEN_FDS") != "" + ppid = os.Getppid() +) + +// An app contains one or more servers and associated configuration. +type app struct { + servers []*http.Server + http *httpdown.HTTP + net *gracenet.Net + listeners []net.Listener + sds []httpdown.Server + errors chan error +} + +func newApp(servers []*http.Server) *app { + return &app{ + servers: servers, + http: &httpdown.HTTP{}, + net: &gracenet.Net{}, + listeners: make([]net.Listener, 0, len(servers)), + sds: make([]httpdown.Server, 0, len(servers)), + + // 2x num servers for possible Close or Stop errors + 1 for possible + // StartProcess error. + errors: make(chan error, 1+(len(servers)*2)), + } +} + +func (a *app) listen() error { + for _, s := range a.servers { + // TODO: default addresses + l, err := a.net.Listen("tcp", s.Addr) + if err != nil { + return err + } + if s.TLSConfig != nil { + l = tls.NewListener(l, s.TLSConfig) + } + a.listeners = append(a.listeners, l) + } + return nil +} + +func (a *app) serve() { + for i, s := range a.servers { + a.sds = append(a.sds, a.http.Serve(s, a.listeners[i])) + } +} + +func (a *app) wait() { + var wg sync.WaitGroup + wg.Add(len(a.sds) * 2) // Wait & Stop + go a.signalHandler(&wg) + for _, s := range a.sds { + go func(s httpdown.Server) { + defer wg.Done() + if err := s.Wait(); err != nil { + a.errors <- err + } + }(s) + } + wg.Wait() +} + +func (a *app) term(wg *sync.WaitGroup) { + for _, s := range a.sds { + go func(s httpdown.Server) { + defer wg.Done() + if err := s.Stop(); err != nil { + a.errors <- err + } + }(s) + } +} + +func (a *app) signalHandler(wg *sync.WaitGroup) { + ch := make(chan os.Signal, 10) + signal.Notify(ch, syscall.SIGTERM, syscall.SIGUSR2) + for { + sig := <-ch + switch sig { + case syscall.SIGTERM: + // this ensures a subsequent TERM will trigger standard go behaviour of + // terminating. + signal.Stop(ch) + a.term(wg) + return + case syscall.SIGUSR2: + // we only return here if there's an error, otherwise the new process + // will send us a TERM when it's ready to trigger the actual shutdown. + if _, err := a.net.StartProcess(); err != nil { + a.errors <- err + } + } + } +} + +// Serve will serve the given http.Servers and will monitor for signals +// allowing for graceful termination (SIGTERM) or restart (SIGUSR2). +func Serve(servers ...*http.Server) error { + a := newApp(servers) + + // Acquire Listeners + if err := a.listen(); err != nil { + return err + } + + // Some useful logging. + if *verbose { + if didInherit { + if ppid == 1 { + log.Printf("Listening on init activated %s", pprintAddr(a.listeners)) + } else { + const msg = "Graceful handoff of %s with new pid %d and old pid %d" + log.Printf(msg, pprintAddr(a.listeners), os.Getpid(), ppid) + } + } else { + const msg = "Serving %s with pid %d" + log.Printf(msg, pprintAddr(a.listeners), os.Getpid()) + } + } + + // Start serving. + a.serve() + + // Close the parent if we inherited and it wasn't init that started us. + if didInherit && ppid != 1 { + if err := syscall.Kill(ppid, syscall.SIGTERM); err != nil { + return fmt.Errorf("failed to close parent: %s", err) + } + } + + waitdone := make(chan struct{}) + go func() { + defer close(waitdone) + a.wait() + }() + + select { + case err := <-a.errors: + if err == nil { + panic("unexpected nil error") + } + return err + case <-waitdone: + if *verbose { + log.Printf("Exiting pid %d.", os.Getpid()) + } + return nil + } +} + +// Used for pretty printing addresses. +func pprintAddr(listeners []net.Listener) []byte { + var out bytes.Buffer + for i, l := range listeners { + if i != 0 { + fmt.Fprint(&out, ", ") + } + fmt.Fprint(&out, l.Addr()) + } + return out.Bytes() +} diff --git a/vendor/github.com/facebookgo/grace/license b/vendor/github.com/facebookgo/grace/license new file mode 100644 index 000000000000..3aea8753287c --- /dev/null +++ b/vendor/github.com/facebookgo/grace/license @@ -0,0 +1,30 @@ +BSD License + +For grace software + +Copyright (c) 2015, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/facebookgo/grace/patents b/vendor/github.com/facebookgo/grace/patents new file mode 100644 index 000000000000..11940a803c06 --- /dev/null +++ b/vendor/github.com/facebookgo/grace/patents @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the grace software distributed by Facebook, Inc. + +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook’s rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/vendor/vendor.json b/vendor/vendor.json index 4382892eddfa..31685faa627e 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -92,6 +92,12 @@ "revision": "57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2", "revisionTime": "2015-12-24T04:54:52Z" }, + { + "checksumSHA1": "al8G84fPO2HjlLiCGyZZqBVDTI0=", + "path": "github.com/facebookgo/grace/gracehttp", + "revision": "053ab5d25436faedf3fe76fbf3da797c8c27c659", + "revisionTime": "2015-08-07T21:49:31Z" + }, { "checksumSHA1": "qTJizMr1DBhDTZiRNmC+khEClz8=", "path": "github.com/go-macaron/bindata",