* support http service graceful restart * fix dependenciestags/v1.2.0-rc1
| @@ -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: | |||
| @@ -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. | |||
| @@ -0,0 +1,104 @@ | |||
| clock [](https://drone.io/github.com/benbjohnson/clock/latest) [](https://coveralls.io/r/benbjohnson/clock?branch=master) [](https://godoc.org/github.com/benbjohnson/clock)  | |||
| ===== | |||
| 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) | |||
| ``` | |||
| @@ -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{} | |||
| } | |||
| @@ -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() | |||
| } | |||
| @@ -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) | |||
| } | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -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 | |||
| } | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -0,0 +1,41 @@ | |||
| httpdown [](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. | |||
| @@ -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 | |||
| } | |||
| @@ -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") | |||
| } | |||
| @@ -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. | |||
| @@ -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. | |||
| @@ -0,0 +1,4 @@ | |||
| stats [](https://travis-ci.org/facebookgo/stats) | |||
| ===== | |||
| Documentation: https://godoc.org/github.com/facebookgo/stats | |||
| @@ -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 | |||
| } | |||
| @@ -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) | |||
| } | |||
| @@ -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", | |||