From 527c2dd6651bc7beed12acd48b22ac69c0bf337e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 31 Dec 2016 09:00:33 +0800 Subject: [PATCH] Support http service graceful restart (#416) * support http service graceful restart * fix dependencies --- cmd/web.go | 26 +- vendor/github.com/facebookgo/clock/LICENSE | 21 + vendor/github.com/facebookgo/clock/README.md | 104 +++++ vendor/github.com/facebookgo/clock/clock.go | 363 +++++++++++++++++ .../facebookgo/grace/gracehttp/http.go | 186 +++++++++ .../facebookgo/grace/gracenet/net.go | 252 ++++++++++++ vendor/github.com/facebookgo/grace/license | 30 ++ vendor/github.com/facebookgo/grace/patents | 33 ++ .../facebookgo/httpdown/httpdown.go | 376 ++++++++++++++++++ vendor/github.com/facebookgo/httpdown/license | 30 ++ vendor/github.com/facebookgo/httpdown/patents | 33 ++ .../github.com/facebookgo/httpdown/readme.md | 41 ++ .../facebookgo/stats/aggregation.go | 35 ++ vendor/github.com/facebookgo/stats/counter.go | 112 ++++++ vendor/github.com/facebookgo/stats/license | 30 ++ vendor/github.com/facebookgo/stats/patents | 33 ++ vendor/github.com/facebookgo/stats/readme.md | 4 + vendor/github.com/facebookgo/stats/stats.go | 166 ++++++++ vendor/github.com/facebookgo/stats/stopper.go | 17 + vendor/vendor.json | 30 ++ 20 files changed, 1919 insertions(+), 3 deletions(-) create mode 100644 vendor/github.com/facebookgo/clock/LICENSE create mode 100644 vendor/github.com/facebookgo/clock/README.md create mode 100644 vendor/github.com/facebookgo/clock/clock.go create mode 100644 vendor/github.com/facebookgo/grace/gracehttp/http.go create mode 100644 vendor/github.com/facebookgo/grace/gracenet/net.go create mode 100644 vendor/github.com/facebookgo/grace/license create mode 100644 vendor/github.com/facebookgo/grace/patents create mode 100644 vendor/github.com/facebookgo/httpdown/httpdown.go create mode 100644 vendor/github.com/facebookgo/httpdown/license create mode 100644 vendor/github.com/facebookgo/httpdown/patents create mode 100644 vendor/github.com/facebookgo/httpdown/readme.md create mode 100644 vendor/github.com/facebookgo/stats/aggregation.go create mode 100644 vendor/github.com/facebookgo/stats/counter.go create mode 100644 vendor/github.com/facebookgo/stats/license create mode 100644 vendor/github.com/facebookgo/stats/patents create mode 100644 vendor/github.com/facebookgo/stats/readme.md create mode 100644 vendor/github.com/facebookgo/stats/stats.go create mode 100644 vendor/github.com/facebookgo/stats/stopper.go diff --git a/cmd/web.go b/cmd/web.go index 7dfd19d64..ecaf74daa 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/clock/LICENSE b/vendor/github.com/facebookgo/clock/LICENSE new file mode 100644 index 000000000..ce212cb1c --- /dev/null +++ b/vendor/github.com/facebookgo/clock/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Ben Johnson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/facebookgo/clock/README.md b/vendor/github.com/facebookgo/clock/README.md new file mode 100644 index 000000000..5d4f4fe72 --- /dev/null +++ b/vendor/github.com/facebookgo/clock/README.md @@ -0,0 +1,104 @@ +clock [![Build Status](https://drone.io/github.com/benbjohnson/clock/status.png)](https://drone.io/github.com/benbjohnson/clock/latest) [![Coverage Status](https://coveralls.io/repos/benbjohnson/clock/badge.png?branch=master)](https://coveralls.io/r/benbjohnson/clock?branch=master) [![GoDoc](https://godoc.org/github.com/benbjohnson/clock?status.png)](https://godoc.org/github.com/benbjohnson/clock) ![Project status](http://img.shields.io/status/experimental.png?color=red) +===== + +Clock is a small library for mocking time in Go. It provides an interface +around the standard library's [`time`][time] package so that the application +can use the realtime clock while tests can use the mock clock. + +[time]: http://golang.org/pkg/time/ + + +## Usage + +### Realtime Clock + +Your application can maintain a `Clock` variable that will allow realtime and +mock clocks to be interchangable. For example, if you had an `Application` type: + +```go +import "github.com/benbjohnson/clock" + +type Application struct { + Clock clock.Clock +} +``` + +You could initialize it to use the realtime clock like this: + +```go +var app Application +app.Clock = clock.New() +... +``` + +Then all timers and time-related functionality should be performed from the +`Clock` variable. + + +### Mocking time + +In your tests, you will want to use a `Mock` clock: + +```go +import ( + "testing" + + "github.com/benbjohnson/clock" +) + +func TestApplication_DoSomething(t *testing.T) { + mock := clock.NewMock() + app := Application{Clock: mock} + ... +} +``` + +Now that you've initialized your application to use the mock clock, you can +adjust the time programmatically. The mock clock always starts from the Unix +epoch (midnight, Jan 1, 1970 UTC). + + +### Controlling time + +The mock clock provides the same functions that the standard library's `time` +package provides. For example, to find the current time, you use the `Now()` +function: + +```go +mock := clock.NewMock() + +// Find the current time. +mock.Now().UTC() // 1970-01-01 00:00:00 +0000 UTC + +// Move the clock forward. +mock.Add(2 * time.Hour) + +// Check the time again. It's 2 hours later! +mock.Now().UTC() // 1970-01-01 02:00:00 +0000 UTC +``` + +Timers and Tickers are also controlled by this same mock clock. They will only +execute when the clock is moved forward: + +``` +mock := clock.NewMock() +count := 0 + +// Kick off a timer to increment every 1 mock second. +go func() { + ticker := clock.Ticker(1 * time.Second) + for { + <-ticker.C + count++ + } +}() +runtime.Gosched() + +// Move the clock forward 10 second. +mock.Add(10 * time.Second) + +// This prints 10. +fmt.Println(count) +``` + + diff --git a/vendor/github.com/facebookgo/clock/clock.go b/vendor/github.com/facebookgo/clock/clock.go new file mode 100644 index 000000000..bca1a7ba8 --- /dev/null +++ b/vendor/github.com/facebookgo/clock/clock.go @@ -0,0 +1,363 @@ +package clock + +import ( + "runtime" + "sort" + "sync" + "time" +) + +// Clock represents an interface to the functions in the standard library time +// package. Two implementations are available in the clock package. The first +// is a real-time clock which simply wraps the time package's functions. The +// second is a mock clock which will only make forward progress when +// programmatically adjusted. +type Clock interface { + After(d time.Duration) <-chan time.Time + AfterFunc(d time.Duration, f func()) *Timer + Now() time.Time + Sleep(d time.Duration) + Tick(d time.Duration) <-chan time.Time + Ticker(d time.Duration) *Ticker + Timer(d time.Duration) *Timer +} + +// New returns an instance of a real-time clock. +func New() Clock { + return &clock{} +} + +// clock implements a real-time clock by simply wrapping the time package functions. +type clock struct{} + +func (c *clock) After(d time.Duration) <-chan time.Time { return time.After(d) } + +func (c *clock) AfterFunc(d time.Duration, f func()) *Timer { + return &Timer{timer: time.AfterFunc(d, f)} +} + +func (c *clock) Now() time.Time { return time.Now() } + +func (c *clock) Sleep(d time.Duration) { time.Sleep(d) } + +func (c *clock) Tick(d time.Duration) <-chan time.Time { return time.Tick(d) } + +func (c *clock) Ticker(d time.Duration) *Ticker { + t := time.NewTicker(d) + return &Ticker{C: t.C, ticker: t} +} + +func (c *clock) Timer(d time.Duration) *Timer { + t := time.NewTimer(d) + return &Timer{C: t.C, timer: t} +} + +// Mock represents a mock clock that only moves forward programmically. +// It can be preferable to a real-time clock when testing time-based functionality. +type Mock struct { + mu sync.Mutex + now time.Time // current time + timers clockTimers // tickers & timers + + calls Calls + waiting []waiting + callsMutex sync.Mutex +} + +// NewMock returns an instance of a mock clock. +// The current time of the mock clock on initialization is the Unix epoch. +func NewMock() *Mock { + return &Mock{now: time.Unix(0, 0)} +} + +// Add moves the current time of the mock clock forward by the duration. +// This should only be called from a single goroutine at a time. +func (m *Mock) Add(d time.Duration) { + // Calculate the final current time. + t := m.now.Add(d) + + // Continue to execute timers until there are no more before the new time. + for { + if !m.runNextTimer(t) { + break + } + } + + // Ensure that we end with the new time. + m.mu.Lock() + m.now = t + m.mu.Unlock() + + // Give a small buffer to make sure the other goroutines get handled. + gosched() +} + +// runNextTimer executes the next timer in chronological order and moves the +// current time to the timer's next tick time. The next time is not executed if +// it's next time if after the max time. Returns true if a timer is executed. +func (m *Mock) runNextTimer(max time.Time) bool { + m.mu.Lock() + + // Sort timers by time. + sort.Sort(m.timers) + + // If we have no more timers then exit. + if len(m.timers) == 0 { + m.mu.Unlock() + return false + } + + // Retrieve next timer. Exit if next tick is after new time. + t := m.timers[0] + if t.Next().After(max) { + m.mu.Unlock() + return false + } + + // Move "now" forward and unlock clock. + m.now = t.Next() + m.mu.Unlock() + + // Execute timer. + t.Tick(m.now) + return true +} + +// After waits for the duration to elapse and then sends the current time on the returned channel. +func (m *Mock) After(d time.Duration) <-chan time.Time { + defer m.inc(&m.calls.After) + return m.Timer(d).C +} + +// AfterFunc waits for the duration to elapse and then executes a function. +// A Timer is returned that can be stopped. +func (m *Mock) AfterFunc(d time.Duration, f func()) *Timer { + defer m.inc(&m.calls.AfterFunc) + t := m.Timer(d) + t.C = nil + t.fn = f + return t +} + +// Now returns the current wall time on the mock clock. +func (m *Mock) Now() time.Time { + defer m.inc(&m.calls.Now) + m.mu.Lock() + defer m.mu.Unlock() + return m.now +} + +// Sleep pauses the goroutine for the given duration on the mock clock. +// The clock must be moved forward in a separate goroutine. +func (m *Mock) Sleep(d time.Duration) { + defer m.inc(&m.calls.Sleep) + <-m.After(d) +} + +// Tick is a convenience function for Ticker(). +// It will return a ticker channel that cannot be stopped. +func (m *Mock) Tick(d time.Duration) <-chan time.Time { + defer m.inc(&m.calls.Tick) + return m.Ticker(d).C +} + +// Ticker creates a new instance of Ticker. +func (m *Mock) Ticker(d time.Duration) *Ticker { + defer m.inc(&m.calls.Ticker) + m.mu.Lock() + defer m.mu.Unlock() + ch := make(chan time.Time) + t := &Ticker{ + C: ch, + c: ch, + mock: m, + d: d, + next: m.now.Add(d), + } + m.timers = append(m.timers, (*internalTicker)(t)) + return t +} + +// Timer creates a new instance of Timer. +func (m *Mock) Timer(d time.Duration) *Timer { + defer m.inc(&m.calls.Timer) + m.mu.Lock() + defer m.mu.Unlock() + ch := make(chan time.Time) + t := &Timer{ + C: ch, + c: ch, + mock: m, + next: m.now.Add(d), + } + m.timers = append(m.timers, (*internalTimer)(t)) + return t +} + +func (m *Mock) removeClockTimer(t clockTimer) { + m.mu.Lock() + defer m.mu.Unlock() + for i, timer := range m.timers { + if timer == t { + copy(m.timers[i:], m.timers[i+1:]) + m.timers[len(m.timers)-1] = nil + m.timers = m.timers[:len(m.timers)-1] + break + } + } + sort.Sort(m.timers) +} + +func (m *Mock) inc(addr *uint32) { + m.callsMutex.Lock() + defer m.callsMutex.Unlock() + *addr++ + var newWaiting []waiting + for _, w := range m.waiting { + if m.calls.atLeast(w.expected) { + close(w.done) + continue + } + newWaiting = append(newWaiting, w) + } + m.waiting = newWaiting +} + +// Wait waits for at least the relevant calls before returning. The expected +// Calls are always over the lifetime of the Mock. Values in the Calls struct +// are used as the minimum number of calls, this allows you to wait for only +// the calls you care about. +func (m *Mock) Wait(s Calls) { + m.callsMutex.Lock() + if m.calls.atLeast(s) { + m.callsMutex.Unlock() + return + } + done := make(chan struct{}) + m.waiting = append(m.waiting, waiting{expected: s, done: done}) + m.callsMutex.Unlock() + <-done +} + +// clockTimer represents an object with an associated start time. +type clockTimer interface { + Next() time.Time + Tick(time.Time) +} + +// clockTimers represents a list of sortable timers. +type clockTimers []clockTimer + +func (a clockTimers) Len() int { return len(a) } +func (a clockTimers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a clockTimers) Less(i, j int) bool { return a[i].Next().Before(a[j].Next()) } + +// Timer represents a single event. +// The current time will be sent on C, unless the timer was created by AfterFunc. +type Timer struct { + C <-chan time.Time + c chan time.Time + timer *time.Timer // realtime impl, if set + next time.Time // next tick time + mock *Mock // mock clock, if set + fn func() // AfterFunc function, if set +} + +// Stop turns off the ticker. +func (t *Timer) Stop() { + if t.timer != nil { + t.timer.Stop() + } else { + t.mock.removeClockTimer((*internalTimer)(t)) + } +} + +type internalTimer Timer + +func (t *internalTimer) Next() time.Time { return t.next } +func (t *internalTimer) Tick(now time.Time) { + if t.fn != nil { + t.fn() + } else { + t.c <- now + } + t.mock.removeClockTimer((*internalTimer)(t)) + gosched() +} + +// Ticker holds a channel that receives "ticks" at regular intervals. +type Ticker struct { + C <-chan time.Time + c chan time.Time + ticker *time.Ticker // realtime impl, if set + next time.Time // next tick time + mock *Mock // mock clock, if set + d time.Duration // time between ticks +} + +// Stop turns off the ticker. +func (t *Ticker) Stop() { + if t.ticker != nil { + t.ticker.Stop() + } else { + t.mock.removeClockTimer((*internalTicker)(t)) + } +} + +type internalTicker Ticker + +func (t *internalTicker) Next() time.Time { return t.next } +func (t *internalTicker) Tick(now time.Time) { + select { + case t.c <- now: + case <-time.After(1 * time.Millisecond): + } + t.next = now.Add(t.d) + gosched() +} + +// Sleep momentarily so that other goroutines can process. +func gosched() { runtime.Gosched() } + +// Calls keeps track of the count of calls for each of the methods on the Clock +// interface. +type Calls struct { + After uint32 + AfterFunc uint32 + Now uint32 + Sleep uint32 + Tick uint32 + Ticker uint32 + Timer uint32 +} + +// atLeast returns true if at least the number of calls in o have been made. +func (c Calls) atLeast(o Calls) bool { + if c.After < o.After { + return false + } + if c.AfterFunc < o.AfterFunc { + return false + } + if c.Now < o.Now { + return false + } + if c.Sleep < o.Sleep { + return false + } + if c.Tick < o.Tick { + return false + } + if c.Ticker < o.Ticker { + return false + } + if c.Timer < o.Timer { + return false + } + return true +} + +type waiting struct { + expected Calls + done chan struct{} +} 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 000000000..4ba8d284c --- /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.SIGINT, syscall.SIGTERM, syscall.SIGUSR2) + for { + sig := <-ch + switch sig { + case syscall.SIGINT, syscall.SIGTERM: + // this ensures a subsequent INT/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/gracenet/net.go b/vendor/github.com/facebookgo/grace/gracenet/net.go new file mode 100644 index 000000000..a980954a9 --- /dev/null +++ b/vendor/github.com/facebookgo/grace/gracenet/net.go @@ -0,0 +1,252 @@ +// Package gracenet provides a family of Listen functions that either open a +// fresh connection or provide an inherited connection from when the process +// was started. The behave like their counterparts in the net package, but +// transparently provide support for graceful restarts without dropping +// connections. This is provided in a systemd socket activation compatible form +// to allow using socket activation. +// +// BUG: Doesn't handle closing of listeners. +package gracenet + +import ( + "fmt" + "net" + "os" + "os/exec" + "strconv" + "strings" + "sync" +) + +const ( + // Used to indicate a graceful restart in the new process. + envCountKey = "LISTEN_FDS" + envCountKeyPrefix = envCountKey + "=" +) + +// In order to keep the working directory the same as when we started we record +// it at startup. +var originalWD, _ = os.Getwd() + +// Net provides the family of Listen functions and maintains the associated +// state. Typically you will have only once instance of Net per application. +type Net struct { + inherited []net.Listener + active []net.Listener + mutex sync.Mutex + inheritOnce sync.Once + + // used in tests to override the default behavior of starting from fd 3. + fdStart int +} + +func (n *Net) inherit() error { + var retErr error + n.inheritOnce.Do(func() { + n.mutex.Lock() + defer n.mutex.Unlock() + countStr := os.Getenv(envCountKey) + if countStr == "" { + return + } + count, err := strconv.Atoi(countStr) + if err != nil { + retErr = fmt.Errorf("found invalid count value: %s=%s", envCountKey, countStr) + return + } + + // In tests this may be overridden. + fdStart := n.fdStart + if fdStart == 0 { + // In normal operations if we are inheriting, the listeners will begin at + // fd 3. + fdStart = 3 + } + + for i := fdStart; i < fdStart+count; i++ { + file := os.NewFile(uintptr(i), "listener") + l, err := net.FileListener(file) + if err != nil { + file.Close() + retErr = fmt.Errorf("error inheriting socket fd %d: %s", i, err) + return + } + if err := file.Close(); err != nil { + retErr = fmt.Errorf("error closing inherited socket fd %d: %s", i, err) + return + } + n.inherited = append(n.inherited, l) + } + }) + return retErr +} + +// Listen announces on the local network address laddr. The network net must be +// a stream-oriented network: "tcp", "tcp4", "tcp6", "unix" or "unixpacket". It +// returns an inherited net.Listener for the matching network and address, or +// creates a new one using net.Listen. +func (n *Net) Listen(nett, laddr string) (net.Listener, error) { + switch nett { + default: + return nil, net.UnknownNetworkError(nett) + case "tcp", "tcp4", "tcp6": + addr, err := net.ResolveTCPAddr(nett, laddr) + if err != nil { + return nil, err + } + return n.ListenTCP(nett, addr) + case "unix", "unixpacket", "invalid_unix_net_for_test": + addr, err := net.ResolveUnixAddr(nett, laddr) + if err != nil { + return nil, err + } + return n.ListenUnix(nett, addr) + } +} + +// ListenTCP announces on the local network address laddr. The network net must +// be: "tcp", "tcp4" or "tcp6". It returns an inherited net.Listener for the +// matching network and address, or creates a new one using net.ListenTCP. +func (n *Net) ListenTCP(nett string, laddr *net.TCPAddr) (*net.TCPListener, error) { + if err := n.inherit(); err != nil { + return nil, err + } + + n.mutex.Lock() + defer n.mutex.Unlock() + + // look for an inherited listener + for i, l := range n.inherited { + if l == nil { // we nil used inherited listeners + continue + } + if isSameAddr(l.Addr(), laddr) { + n.inherited[i] = nil + n.active = append(n.active, l) + return l.(*net.TCPListener), nil + } + } + + // make a fresh listener + l, err := net.ListenTCP(nett, laddr) + if err != nil { + return nil, err + } + n.active = append(n.active, l) + return l, nil +} + +// ListenUnix announces on the local network address laddr. The network net +// must be a: "unix" or "unixpacket". It returns an inherited net.Listener for +// the matching network and address, or creates a new one using net.ListenUnix. +func (n *Net) ListenUnix(nett string, laddr *net.UnixAddr) (*net.UnixListener, error) { + if err := n.inherit(); err != nil { + return nil, err + } + + n.mutex.Lock() + defer n.mutex.Unlock() + + // look for an inherited listener + for i, l := range n.inherited { + if l == nil { // we nil used inherited listeners + continue + } + if isSameAddr(l.Addr(), laddr) { + n.inherited[i] = nil + n.active = append(n.active, l) + return l.(*net.UnixListener), nil + } + } + + // make a fresh listener + l, err := net.ListenUnix(nett, laddr) + if err != nil { + return nil, err + } + n.active = append(n.active, l) + return l, nil +} + +// activeListeners returns a snapshot copy of the active listeners. +func (n *Net) activeListeners() ([]net.Listener, error) { + n.mutex.Lock() + defer n.mutex.Unlock() + ls := make([]net.Listener, len(n.active)) + copy(ls, n.active) + return ls, nil +} + +func isSameAddr(a1, a2 net.Addr) bool { + if a1.Network() != a2.Network() { + return false + } + a1s := a1.String() + a2s := a2.String() + if a1s == a2s { + return true + } + + // This allows for ipv6 vs ipv4 local addresses to compare as equal. This + // scenario is common when listening on localhost. + const ipv6prefix = "[::]" + a1s = strings.TrimPrefix(a1s, ipv6prefix) + a2s = strings.TrimPrefix(a2s, ipv6prefix) + const ipv4prefix = "0.0.0.0" + a1s = strings.TrimPrefix(a1s, ipv4prefix) + a2s = strings.TrimPrefix(a2s, ipv4prefix) + return a1s == a2s +} + +// StartProcess starts a new process passing it the active listeners. It +// doesn't fork, but starts a new process using the same environment and +// arguments as when it was originally started. This allows for a newly +// deployed binary to be started. It returns the pid of the newly started +// process when successful. +func (n *Net) StartProcess() (int, error) { + listeners, err := n.activeListeners() + if err != nil { + return 0, err + } + + // Extract the fds from the listeners. + files := make([]*os.File, len(listeners)) + for i, l := range listeners { + files[i], err = l.(filer).File() + if err != nil { + return 0, err + } + defer files[i].Close() + } + + // Use the original binary location. This works with symlinks such that if + // the file it points to has been changed we will use the updated symlink. + argv0, err := exec.LookPath(os.Args[0]) + if err != nil { + return 0, err + } + + // Pass on the environment and replace the old count key with the new one. + var env []string + for _, v := range os.Environ() { + if !strings.HasPrefix(v, envCountKeyPrefix) { + env = append(env, v) + } + } + env = append(env, fmt.Sprintf("%s%d", envCountKeyPrefix, len(listeners))) + + allFiles := append([]*os.File{os.Stdin, os.Stdout, os.Stderr}, files...) + process, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{ + Dir: originalWD, + Env: env, + Files: allFiles, + }) + if err != nil { + return 0, err + } + return process.Pid, nil +} + +type filer interface { + File() (*os.File, error) +} diff --git a/vendor/github.com/facebookgo/grace/license b/vendor/github.com/facebookgo/grace/license new file mode 100644 index 000000000..3aea87532 --- /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 000000000..11940a803 --- /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/github.com/facebookgo/httpdown/httpdown.go b/vendor/github.com/facebookgo/httpdown/httpdown.go new file mode 100644 index 000000000..34c5dea9f --- /dev/null +++ b/vendor/github.com/facebookgo/httpdown/httpdown.go @@ -0,0 +1,376 @@ +// Package httpdown provides http.ConnState enabled graceful termination of +// http.Server. +package httpdown + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/facebookgo/clock" + "github.com/facebookgo/stats" +) + +const ( + defaultStopTimeout = time.Minute + defaultKillTimeout = time.Minute +) + +// A Server allows encapsulates the process of accepting new connections and +// serving them, and gracefully shutting down the listener without dropping +// active connections. +type Server interface { + // Wait waits for the serving loop to finish. This will happen when Stop is + // called, at which point it returns no error, or if there is an error in the + // serving loop. You must call Wait after calling Serve or ListenAndServe. + Wait() error + + // Stop stops the listener. It will block until all connections have been + // closed. + Stop() error +} + +// HTTP defines the configuration for serving a http.Server. Multiple calls to +// Serve or ListenAndServe can be made on the same HTTP instance. The default +// timeouts of 1 minute each result in a maximum of 2 minutes before a Stop() +// returns. +type HTTP struct { + // StopTimeout is the duration before we begin force closing connections. + // Defaults to 1 minute. + StopTimeout time.Duration + + // KillTimeout is the duration before which we completely give up and abort + // even though we still have connected clients. This is useful when a large + // number of client connections exist and closing them can take a long time. + // Note, this is in addition to the StopTimeout. Defaults to 1 minute. + KillTimeout time.Duration + + // Stats is optional. If provided, it will be used to record various metrics. + Stats stats.Client + + // Clock allows for testing timing related functionality. Do not specify this + // in production code. + Clock clock.Clock +} + +// Serve provides the low-level API which is useful if you're creating your own +// net.Listener. +func (h HTTP) Serve(s *http.Server, l net.Listener) Server { + stopTimeout := h.StopTimeout + if stopTimeout == 0 { + stopTimeout = defaultStopTimeout + } + killTimeout := h.KillTimeout + if killTimeout == 0 { + killTimeout = defaultKillTimeout + } + klock := h.Clock + if klock == nil { + klock = clock.New() + } + + ss := &server{ + stopTimeout: stopTimeout, + killTimeout: killTimeout, + stats: h.Stats, + clock: klock, + oldConnState: s.ConnState, + listener: l, + server: s, + serveDone: make(chan struct{}), + serveErr: make(chan error, 1), + new: make(chan net.Conn), + active: make(chan net.Conn), + idle: make(chan net.Conn), + closed: make(chan net.Conn), + stop: make(chan chan struct{}), + kill: make(chan chan struct{}), + } + s.ConnState = ss.connState + go ss.manage() + go ss.serve() + return ss +} + +// ListenAndServe returns a Server for the given http.Server. It is equivalent +// to ListenAndServe from the standard library, but returns immediately. +// Requests will be accepted in a background goroutine. If the http.Server has +// a non-nil TLSConfig, a TLS enabled listener will be setup. +func (h HTTP) ListenAndServe(s *http.Server) (Server, error) { + addr := s.Addr + if addr == "" { + if s.TLSConfig == nil { + addr = ":http" + } else { + addr = ":https" + } + } + l, err := net.Listen("tcp", addr) + if err != nil { + stats.BumpSum(h.Stats, "listen.error", 1) + return nil, err + } + if s.TLSConfig != nil { + l = tls.NewListener(l, s.TLSConfig) + } + return h.Serve(s, l), nil +} + +// server manages the serving process and allows for gracefully stopping it. +type server struct { + stopTimeout time.Duration + killTimeout time.Duration + stats stats.Client + clock clock.Clock + + oldConnState func(net.Conn, http.ConnState) + server *http.Server + serveDone chan struct{} + serveErr chan error + listener net.Listener + + new chan net.Conn + active chan net.Conn + idle chan net.Conn + closed chan net.Conn + stop chan chan struct{} + kill chan chan struct{} + + stopOnce sync.Once + stopErr error +} + +func (s *server) connState(c net.Conn, cs http.ConnState) { + if s.oldConnState != nil { + s.oldConnState(c, cs) + } + + switch cs { + case http.StateNew: + s.new <- c + case http.StateActive: + s.active <- c + case http.StateIdle: + s.idle <- c + case http.StateHijacked, http.StateClosed: + s.closed <- c + } +} + +func (s *server) manage() { + defer func() { + close(s.new) + close(s.active) + close(s.idle) + close(s.closed) + close(s.stop) + close(s.kill) + }() + + var stopDone chan struct{} + + conns := map[net.Conn]http.ConnState{} + var countNew, countActive, countIdle float64 + + // decConn decrements the count associated with the current state of the + // given connection. + decConn := func(c net.Conn) { + switch conns[c] { + default: + panic(fmt.Errorf("unknown existing connection: %s", c)) + case http.StateNew: + countNew-- + case http.StateActive: + countActive-- + case http.StateIdle: + countIdle-- + } + } + + // setup a ticker to report various values every minute. if we don't have a + // Stats implementation provided, we Stop it so it never ticks. + statsTicker := s.clock.Ticker(time.Minute) + if s.stats == nil { + statsTicker.Stop() + } + + for { + select { + case <-statsTicker.C: + // we'll only get here when s.stats is not nil + s.stats.BumpAvg("http-state.new", countNew) + s.stats.BumpAvg("http-state.active", countActive) + s.stats.BumpAvg("http-state.idle", countIdle) + s.stats.BumpAvg("http-state.total", countNew+countActive+countIdle) + case c := <-s.new: + conns[c] = http.StateNew + countNew++ + case c := <-s.active: + decConn(c) + countActive++ + + conns[c] = http.StateActive + case c := <-s.idle: + decConn(c) + countIdle++ + + conns[c] = http.StateIdle + + // if we're already stopping, close it + if stopDone != nil { + c.Close() + } + case c := <-s.closed: + stats.BumpSum(s.stats, "conn.closed", 1) + decConn(c) + delete(conns, c) + + // if we're waiting to stop and are all empty, we just closed the last + // connection and we're done. + if stopDone != nil && len(conns) == 0 { + close(stopDone) + return + } + case stopDone = <-s.stop: + // if we're already all empty, we're already done + if len(conns) == 0 { + close(stopDone) + return + } + + // close current idle connections right away + for c, cs := range conns { + if cs == http.StateIdle { + c.Close() + } + } + + // continue the loop and wait for all the ConnState updates which will + // eventually close(stopDone) and return from this goroutine. + + case killDone := <-s.kill: + // force close all connections + stats.BumpSum(s.stats, "kill.conn.count", float64(len(conns))) + for c := range conns { + c.Close() + } + + // don't block the kill. + close(killDone) + + // continue the loop and we wait for all the ConnState updates and will + // return from this goroutine when we're all done. otherwise we'll try to + // send those ConnState updates on closed channels. + + } + } +} + +func (s *server) serve() { + stats.BumpSum(s.stats, "serve", 1) + s.serveErr <- s.server.Serve(s.listener) + close(s.serveDone) + close(s.serveErr) +} + +func (s *server) Wait() error { + if err := <-s.serveErr; !isUseOfClosedError(err) { + return err + } + return nil +} + +func (s *server) Stop() error { + s.stopOnce.Do(func() { + defer stats.BumpTime(s.stats, "stop.time").End() + stats.BumpSum(s.stats, "stop", 1) + + // first disable keep-alive for new connections + s.server.SetKeepAlivesEnabled(false) + + // then close the listener so new connections can't connect come thru + closeErr := s.listener.Close() + <-s.serveDone + + // then trigger the background goroutine to stop and wait for it + stopDone := make(chan struct{}) + s.stop <- stopDone + + // wait for stop + select { + case <-stopDone: + case <-s.clock.After(s.stopTimeout): + defer stats.BumpTime(s.stats, "kill.time").End() + stats.BumpSum(s.stats, "kill", 1) + + // stop timed out, wait for kill + killDone := make(chan struct{}) + s.kill <- killDone + select { + case <-killDone: + case <-s.clock.After(s.killTimeout): + // kill timed out, give up + stats.BumpSum(s.stats, "kill.timeout", 1) + } + } + + if closeErr != nil && !isUseOfClosedError(closeErr) { + stats.BumpSum(s.stats, "listener.close.error", 1) + s.stopErr = closeErr + } + }) + return s.stopErr +} + +func isUseOfClosedError(err error) bool { + if err == nil { + return false + } + if opErr, ok := err.(*net.OpError); ok { + err = opErr.Err + } + return err.Error() == "use of closed network connection" +} + +// ListenAndServe is a convenience function to serve and wait for a SIGTERM +// or SIGINT before shutting down. +func ListenAndServe(s *http.Server, hd *HTTP) error { + if hd == nil { + hd = &HTTP{} + } + hs, err := hd.ListenAndServe(s) + if err != nil { + return err + } + + waiterr := make(chan error, 1) + go func() { + defer close(waiterr) + waiterr <- hs.Wait() + }() + + signals := make(chan os.Signal, 10) + signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT) + + select { + case err := <-waiterr: + if err != nil { + return err + } + case <-signals: + signal.Stop(signals) + if err := hs.Stop(); err != nil { + return err + } + if err := <-waiterr; err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/facebookgo/httpdown/license b/vendor/github.com/facebookgo/httpdown/license new file mode 100644 index 000000000..d849082ff --- /dev/null +++ b/vendor/github.com/facebookgo/httpdown/license @@ -0,0 +1,30 @@ +BSD License + +For httpdown 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/httpdown/patents b/vendor/github.com/facebookgo/httpdown/patents new file mode 100644 index 000000000..f7133456a --- /dev/null +++ b/vendor/github.com/facebookgo/httpdown/patents @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the httpdown 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/github.com/facebookgo/httpdown/readme.md b/vendor/github.com/facebookgo/httpdown/readme.md new file mode 100644 index 000000000..d5fa245db --- /dev/null +++ b/vendor/github.com/facebookgo/httpdown/readme.md @@ -0,0 +1,41 @@ +httpdown [![Build Status](https://secure.travis-ci.org/facebookgo/httpdown.png)](https://travis-ci.org/facebookgo/httpdown) +======== + +Documentation: https://godoc.org/github.com/facebookgo/httpdown + +Package httpdown provides a library that makes it easy to build a HTTP server +that can be shutdown gracefully (that is, without dropping any connections). + +If you want graceful restart and not just graceful shutdown, look at the +[grace](https://github.com/facebookgo/grace) package which uses this package +underneath but also provides graceful restart. + +Usage +----- + +Demo HTTP Server with graceful termination: +https://github.com/facebookgo/httpdown/blob/master/httpdown_example/main.go + +1. Install the demo application + + go get github.com/facebookgo/httpdown/httpdown_example + +1. Start it in the first terminal + + httpdown_example + + This will output something like: + + 2014/11/18 21:57:50 serving on http://127.0.0.1:8080/ with pid 17 + +1. In a second terminal start a slow HTTP request + + curl 'http://localhost:8080/?duration=20s' + +1. In a third terminal trigger a graceful shutdown (using the pid from your output): + + kill -TERM 17 + +This will demonstrate that the slow request was served before the server was +shutdown. You could also have used `Ctrl-C` instead of `kill` as the example +application triggers graceful shutdown on TERM or INT signals. diff --git a/vendor/github.com/facebookgo/stats/aggregation.go b/vendor/github.com/facebookgo/stats/aggregation.go new file mode 100644 index 000000000..8f57fb7f1 --- /dev/null +++ b/vendor/github.com/facebookgo/stats/aggregation.go @@ -0,0 +1,35 @@ +package stats + +import "sort" + +// Average returns the average value +func Average(values []float64) float64 { + if len(values) == 0 { + return 0 + } + + var val float64 + for _, point := range values { + val += point + } + return val / float64(len(values)) +} + +// Sum returns the sum of all the given values +func Sum(values []float64) float64 { + var val float64 + for _, point := range values { + val += point + } + return val +} + +// Percentiles returns a map containing the asked for percentiles +func Percentiles(values []float64, percentiles map[string]float64) map[string]float64 { + sort.Float64s(values) + results := map[string]float64{} + for label, p := range percentiles { + results[label] = values[int(float64(len(values))*p)] + } + return results +} diff --git a/vendor/github.com/facebookgo/stats/counter.go b/vendor/github.com/facebookgo/stats/counter.go new file mode 100644 index 000000000..59a0ed1e7 --- /dev/null +++ b/vendor/github.com/facebookgo/stats/counter.go @@ -0,0 +1,112 @@ +package stats + +import "fmt" + +// Type is the type of aggregation of apply +type Type int + +const ( + AggregateAvg Type = iota + AggregateSum + AggregateHistogram +) + +var ( + // HistogramPercentiles is used to determine which percentiles to return for + // SimpleCounter.Aggregate + HistogramPercentiles = map[string]float64{ + "p50": 0.5, + "p95": 0.95, + "p99": 0.99, + } + + // MinSamplesForPercentiles is used by SimpleCounter.Aggregate to determine + // what the minimum number of samples is required for percentile analysis + MinSamplesForPercentiles = 10 +) + +// Aggregates can be used to merge counters together. This is not goroutine safe +type Aggregates map[string]Counter + +// Add adds the counter for aggregation. This is not goroutine safe +func (a Aggregates) Add(c Counter) error { + key := c.FullKey() + if counter, ok := a[key]; ok { + if counter.GetType() != c.GetType() { + return fmt.Errorf("stats: mismatched aggregation type for: %s", key) + } + counter.AddValues(c.GetValues()...) + } else { + a[key] = c + } + return nil +} + +// Counter is the interface used by Aggregates to merge counters together +type Counter interface { + // FullKey is used to uniquely identify the counter + FullKey() string + + // AddValues adds values for aggregation + AddValues(...float64) + + // GetValues returns the values for aggregation + GetValues() []float64 + + // GetType returns the type of aggregation to apply + GetType() Type +} + +// SimpleCounter is a basic implementation of the Counter interface +type SimpleCounter struct { + Key string + Values []float64 + Type Type +} + +// FullKey is part of the Counter interace +func (s *SimpleCounter) FullKey() string { + return s.Key +} + +// GetValues is part of the Counter interface +func (s *SimpleCounter) GetValues() []float64 { + return s.Values +} + +// AddValues is part of the Counter interface +func (s *SimpleCounter) AddValues(vs ...float64) { + s.Values = append(s.Values, vs...) +} + +// GetType is part of the Counter interface +func (s *SimpleCounter) GetType() Type { + return s.Type +} + +// Aggregate aggregates the provided values appropriately, returning a map +// from key to value. If AggregateHistogram is specified, the map will contain +// the relevant percentiles as specified by HistogramPercentiles +func (s *SimpleCounter) Aggregate() map[string]float64 { + switch s.Type { + case AggregateAvg: + return map[string]float64{ + s.Key: Average(s.Values), + } + case AggregateSum: + return map[string]float64{ + s.Key: Sum(s.Values), + } + case AggregateHistogram: + histogram := map[string]float64{ + s.Key: Average(s.Values), + } + if len(s.Values) > MinSamplesForPercentiles { + for k, v := range Percentiles(s.Values, HistogramPercentiles) { + histogram[fmt.Sprintf("%s.%s", s.Key, k)] = v + } + } + return histogram + } + panic("stats: unsupported aggregation type") +} diff --git a/vendor/github.com/facebookgo/stats/license b/vendor/github.com/facebookgo/stats/license new file mode 100644 index 000000000..feae87075 --- /dev/null +++ b/vendor/github.com/facebookgo/stats/license @@ -0,0 +1,30 @@ +BSD License + +For stats 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/stats/patents b/vendor/github.com/facebookgo/stats/patents new file mode 100644 index 000000000..5d7617212 --- /dev/null +++ b/vendor/github.com/facebookgo/stats/patents @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the stats 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/github.com/facebookgo/stats/readme.md b/vendor/github.com/facebookgo/stats/readme.md new file mode 100644 index 000000000..f268ed307 --- /dev/null +++ b/vendor/github.com/facebookgo/stats/readme.md @@ -0,0 +1,4 @@ +stats [![Build Status](https://secure.travis-ci.org/facebookgo/stats.png)](https://travis-ci.org/facebookgo/stats) +===== + +Documentation: https://godoc.org/github.com/facebookgo/stats diff --git a/vendor/github.com/facebookgo/stats/stats.go b/vendor/github.com/facebookgo/stats/stats.go new file mode 100644 index 000000000..b833506aa --- /dev/null +++ b/vendor/github.com/facebookgo/stats/stats.go @@ -0,0 +1,166 @@ +// Package stats defines a lightweight interface for collecting statistics. It +// doesn't provide an implementation, just the shared interface. +package stats + +// Client provides methods to collection statistics. +type Client interface { + // BumpAvg bumps the average for the given key. + BumpAvg(key string, val float64) + + // BumpSum bumps the sum for the given key. + BumpSum(key string, val float64) + + // BumpHistogram bumps the histogram for the given key. + BumpHistogram(key string, val float64) + + // BumpTime is a special version of BumpHistogram which is specialized for + // timers. Calling it starts the timer, and it returns a value on which End() + // can be called to indicate finishing the timer. A convenient way of + // recording the duration of a function is calling it like such at the top of + // the function: + // + // defer s.BumpTime("my.function").End() + BumpTime(key string) interface { + End() + } +} + +// PrefixClient adds multiple keys for the same value, with each prefix +// added to the key and calls the underlying client. +func PrefixClient(prefixes []string, client Client) Client { + return &prefixClient{ + Prefixes: prefixes, + Client: client, + } +} + +type prefixClient struct { + Prefixes []string + Client Client +} + +func (p *prefixClient) BumpAvg(key string, val float64) { + for _, prefix := range p.Prefixes { + p.Client.BumpAvg(prefix+key, val) + } +} + +func (p *prefixClient) BumpSum(key string, val float64) { + for _, prefix := range p.Prefixes { + p.Client.BumpSum(prefix+key, val) + } +} + +func (p *prefixClient) BumpHistogram(key string, val float64) { + for _, prefix := range p.Prefixes { + p.Client.BumpHistogram(prefix+key, val) + } +} + +func (p *prefixClient) BumpTime(key string) interface { + End() +} { + var m multiEnder + for _, prefix := range p.Prefixes { + m = append(m, p.Client.BumpTime(prefix+key)) + } + return m +} + +// multiEnder combines many enders together. +type multiEnder []interface { + End() +} + +func (m multiEnder) End() { + for _, e := range m { + e.End() + } +} + +// HookClient is useful for testing. It provides optional hooks for each +// expected method in the interface, which if provided will be called. If a +// hook is not provided, it will be ignored. +type HookClient struct { + BumpAvgHook func(key string, val float64) + BumpSumHook func(key string, val float64) + BumpHistogramHook func(key string, val float64) + BumpTimeHook func(key string) interface { + End() + } +} + +// BumpAvg will call BumpAvgHook if defined. +func (c *HookClient) BumpAvg(key string, val float64) { + if c.BumpAvgHook != nil { + c.BumpAvgHook(key, val) + } +} + +// BumpSum will call BumpSumHook if defined. +func (c *HookClient) BumpSum(key string, val float64) { + if c.BumpSumHook != nil { + c.BumpSumHook(key, val) + } +} + +// BumpHistogram will call BumpHistogramHook if defined. +func (c *HookClient) BumpHistogram(key string, val float64) { + if c.BumpHistogramHook != nil { + c.BumpHistogramHook(key, val) + } +} + +// BumpTime will call BumpTimeHook if defined. +func (c *HookClient) BumpTime(key string) interface { + End() +} { + if c.BumpTimeHook != nil { + return c.BumpTimeHook(key) + } + return NoOpEnd +} + +type noOpEnd struct{} + +func (n noOpEnd) End() {} + +// NoOpEnd provides a dummy value for use in tests as valid return value for +// BumpTime(). +var NoOpEnd = noOpEnd{} + +// BumpAvg calls BumpAvg on the Client if it isn't nil. This is useful when a +// component has an optional stats.Client. +func BumpAvg(c Client, key string, val float64) { + if c != nil { + c.BumpAvg(key, val) + } +} + +// BumpSum calls BumpSum on the Client if it isn't nil. This is useful when a +// component has an optional stats.Client. +func BumpSum(c Client, key string, val float64) { + if c != nil { + c.BumpSum(key, val) + } +} + +// BumpHistogram calls BumpHistogram on the Client if it isn't nil. This is +// useful when a component has an optional stats.Client. +func BumpHistogram(c Client, key string, val float64) { + if c != nil { + c.BumpHistogram(key, val) + } +} + +// BumpTime calls BumpTime on the Client if it isn't nil. If the Client is nil +// it still returns a valid return value which will be a no-op. This is useful +// when a component has an optional stats.Client. +func BumpTime(c Client, key string) interface { + End() +} { + if c != nil { + return c.BumpTime(key) + } + return NoOpEnd +} diff --git a/vendor/github.com/facebookgo/stats/stopper.go b/vendor/github.com/facebookgo/stats/stopper.go new file mode 100644 index 000000000..38e8eab80 --- /dev/null +++ b/vendor/github.com/facebookgo/stats/stopper.go @@ -0,0 +1,17 @@ +package stats + +import "time" + +// Stopper calls Client.BumpSum and Client.BumpHistogram when End'ed +type Stopper struct { + Key string + Start time.Time + Client Client +} + +// End the Stopper +func (s *Stopper) End() { + since := time.Since(s.Start).Seconds() * 1000.0 + s.Client.BumpSum(s.Key+".total", since) + s.Client.BumpHistogram(s.Key, since) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 4382892ed..b153317f3 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -92,6 +92,36 @@ "revision": "57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2", "revisionTime": "2015-12-24T04:54:52Z" }, + { + "checksumSHA1": "imR2wF388/0fBU6RRWx8RvTi8Q8=", + "path": "github.com/facebookgo/clock", + "revision": "600d898af40aa09a7a93ecb9265d87b0504b6f03", + "revisionTime": "2015-04-10T01:09:13Z" + }, + { + "checksumSHA1": "j2O1k8Kvw6btFkJKt45nQQuAHM0=", + "path": "github.com/facebookgo/grace/gracehttp", + "revision": "5729e484473f52048578af1b80d0008c7024089b", + "revisionTime": "2016-09-26T23:17:15Z" + }, + { + "checksumSHA1": "nj3ykV8Em7WOfvHNIo/XNTT9LOM=", + "path": "github.com/facebookgo/grace/gracenet", + "revision": "5729e484473f52048578af1b80d0008c7024089b", + "revisionTime": "2016-09-26T23:17:15Z" + }, + { + "checksumSHA1": "8SgB/r3Cf+OvXmg8bIPnlEq03Bs=", + "path": "github.com/facebookgo/httpdown", + "revision": "a3b1354551a26449fbe05f5d855937f6e7acbd71", + "revisionTime": "2016-03-23T22:10:27Z" + }, + { + "checksumSHA1": "SzXvrWxavFo8HD/2hnbdu17C3VE=", + "path": "github.com/facebookgo/stats", + "revision": "1b76add642e42c6ffba7211ad7b3939ce654526e", + "revisionTime": "2015-10-06T22:16:25Z" + }, { "checksumSHA1": "qTJizMr1DBhDTZiRNmC+khEClz8=", "path": "github.com/go-macaron/bindata",