Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
186 lines
4.0 KiB
186 lines
4.0 KiB
// 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() |
|
}
|
|
|