diff --git a/grace.go b/grace.go index c470066..85d12df 100644 --- a/grace.go +++ b/grace.go @@ -18,10 +18,13 @@ import ( var ( // This error is returned by Inherits() when we're not inheriting any fds. - ErrNotInheriting = errors.New("no inherited listeners") + ErrNotInheriting = errors.New("grace: no inherited listeners") // This error is returned by Listener.Accept() when Close is in progress. - ErrAlreadyClosed = errors.New("already closed") + ErrAlreadyClosed = errors.New("grace: already closed") + + errRestartListeners = errors.New("grace: restart must be given listeners") + errTermTimeout = errors.New("grace: TERM timeout in closing listeners") // Time in the past to trigger immediate deadline. timeInPast = time.Now() @@ -142,35 +145,71 @@ func (l *listener) Accept() (net.Conn, error) { return &conn{Conn: c, wg: &l.wg}, nil } +// Process configures the restart process. type Process struct { + // TermTimeout if set will determine how long we'll wait for listeners when + // we're sent the TERM signal. + TermTimeout time.Duration +} + +func (p *Process) term(listeners []Listener) error { + // shutdown all listeners in parallel + errs := make(chan error, len(listeners)) + wg := sync.WaitGroup{} + wg.Add(len(listeners)) + for _, l := range listeners { + go func(l Listener) { + defer wg.Done() + if err := l.Close(); err != nil { + errs <- err + } + }(l) + } + + if p.TermTimeout.Nanoseconds() == 0 { + // no timeout, wait indefinitely + wg.Wait() + } else { + // wait in background to allow for implementing a timeout + done := make(chan struct{}) + go func() { + defer close(done) + wg.Wait() + }() + + // wait for graceful termination or timeout + select { + case <-done: + case <-time.After(p.TermTimeout): + return errTermTimeout + } + } + + // if any errors occurred, return the first one + if len(errs) > 0 { + return <-errs + } + + return nil } // Wait for signals to gracefully terminate or restart the process. -func (p *Process) Wait(listeners []Listener) (err error) { +func (p *Process) Wait(listeners []Listener) error { ch := make(chan os.Signal, 2) signal.Notify(ch, syscall.SIGTERM, syscall.SIGUSR2) for { sig := <-ch switch sig { case syscall.SIGTERM: + // this ensures a subsequent TERM will trigger standard go behaviour of + // terminating. signal.Stop(ch) - var wg sync.WaitGroup - wg.Add(len(listeners)) - for _, l := range listeners { - go func(l Listener) { - defer wg.Done() - cErr := l.Close() - if cErr != nil { - err = cErr - } - }(l) - } - wg.Wait() - return + return p.term(listeners) case syscall.SIGUSR2: - rErr := Restart(listeners) - if rErr != nil { - return rErr + // 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 := p.Restart(listeners); err != nil { + return err } } } @@ -213,7 +252,7 @@ func (p *Process) CloseParent() error { // Restart the process passing the given listeners to the new process. func (p *Process) Restart(listeners []Listener) (err error) { if len(listeners) == 0 { - return errors.New("restart must be given listeners.") + return errRestartListeners } // Extract the fds from the listeners. diff --git a/gracedemo/demo.go b/gracedemo/demo.go index 8be272c..3e39261 100644 --- a/gracedemo/demo.go +++ b/gracedemo/demo.go @@ -5,10 +5,11 @@ package main import ( "flag" "fmt" - "github.com/ParsePlatform/go.grace/gracehttp" "net/http" "os" "time" + + "github.com/facebookgo/grace/gracehttp" ) var ( diff --git a/gracehttp/http.go b/gracehttp/http.go index 30839c5..73e7595 100644 --- a/gracehttp/http.go +++ b/gracehttp/http.go @@ -13,7 +13,7 @@ import ( "net/http" "os" - "github.com/ParsePlatform/go.grace" + "github.com/facebookgo/grace" ) var ( diff --git a/gracehttp/http_test.go b/gracehttp/http_test.go index 8e0e30a..e703d69 100644 --- a/gracehttp/http_test.go +++ b/gracehttp/http_test.go @@ -16,15 +16,15 @@ import ( "testing" "time" - "github.com/ParsePlatform/go.freeport" - "github.com/ParsePlatform/go.tool" + "github.com/facebookgo/freeport" + "github.com/facebookgo/tool" ) var ( // Debug logging. debugLog = flag.Bool("debug", false, "enable debug logging") testserverCommand = &tool.CommandBuild{ - ImportPath: "github.com/ParsePlatform/go.grace/gracehttp/testserver", + ImportPath: "github.com/facebookgo/grace/gracehttp/testserver", } ) diff --git a/gracehttp/testserver/testserver.go b/gracehttp/testserver/testserver.go index 91929b4..9292658 100644 --- a/gracehttp/testserver/testserver.go +++ b/gracehttp/testserver/testserver.go @@ -13,7 +13,7 @@ import ( "sync" "time" - "github.com/ParsePlatform/go.grace/gracehttp" + "github.com/facebookgo/grace/gracehttp" ) type response struct { diff --git a/readme.md b/readme.md index 316decc..740c81b 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -go.grace [![Build Status](https://secure.travis-ci.org/ParsePlatform/go.grace.png)](http://travis-ci.org/ParsePlatform/go.grace) +go.grace [![Build Status](https://secure.travis-ci.org/facebookgo/grace.png)](http://travis-ci.org/facebookgo/grace) ======== Package grace provides a library that makes it easy to build socket @@ -17,11 +17,11 @@ Usage ----- Demo HTTP Server with graceful termination and restart: -https://github.com/ParsePlatform/go.grace/blob/master/gracedemo/demo.go +https://github.com/facebookgo/grace/blob/master/gracedemo/demo.go 1. Install the demo application - go get github.com/ParsePlatform/go.grace/gracedemo + go get github.com/facebookgo/grace/gracedemo 1. Start it in the first terminal @@ -55,7 +55,7 @@ Documentation ------------- `http.Server` graceful termination and restart: -http://godoc.org/github.com/ParsePlatform/go.grace/gracehttp +http://godoc.org/github.com/facebookgo/grace/gracehttp `net.Listener` graceful termination and restart: -http://godoc.org/github.com/ParsePlatform/go.grace +http://godoc.org/github.com/facebookgo/grace