properly handle os.Interrupt Signal
This makes systemd not put unit into fail mode when stopping INFO: * make sure to use signals.RegisterCloser for everything that should be closed on interrupt * for any net.Listeners created make sure to wrap them with network.WrapListener and register with signals.RegisterCloser
Cette révision appartient à :
Parent
252f6a14b4
révision
73f77f1624
9 fichiers modifiés avec 162 ajouts et 3 suppressions
4
.gitignore
externe
4
.gitignore
externe
|
@ -14,3 +14,7 @@ templates/*.html.go
|
|||
*.backup
|
||||
tags
|
||||
*.retry
|
||||
|
||||
# emacs temp files
|
||||
*\#*
|
||||
*~
|
8
main.go
8
main.go
|
@ -40,10 +40,18 @@ func RunServer(conf *config.Config) {
|
|||
l, err := network.CreateHTTPListener(conf)
|
||||
log.CheckError(err)
|
||||
if err == nil {
|
||||
// add http server to be closed gracefully
|
||||
signals.RegisterCloser(&network.GracefulHttpCloser{
|
||||
Server: srv,
|
||||
Listener: l,
|
||||
})
|
||||
log.Infof("listening on %s", l.Addr())
|
||||
err := srv.Serve(l)
|
||||
if err != nil && err != network.ErrListenerStopped {
|
||||
log.CheckError(err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
17
network/closer.go
Fichier normal
17
network/closer.go
Fichier normal
|
@ -0,0 +1,17 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// implements io.Closer that gracefully closes an http server
|
||||
type GracefulHttpCloser struct {
|
||||
Server *http.Server
|
||||
Listener net.Listener
|
||||
}
|
||||
|
||||
func (c *GracefulHttpCloser) Close() error {
|
||||
c.Listener.Close()
|
||||
return c.Server.Shutdown(nil)
|
||||
}
|
58
network/graceful.go
Fichier normal
58
network/graceful.go
Fichier normal
|
@ -0,0 +1,58 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
var ErrListenerStopped = errors.New("listener was stopped")
|
||||
|
||||
// GracefulListener provides safe and graceful net.Listener wrapper that prevents error on graceful shutdown
|
||||
type GracefulListener struct {
|
||||
listener net.Listener
|
||||
stop chan int
|
||||
}
|
||||
|
||||
func (l *GracefulListener) Accept() (net.Conn, error) {
|
||||
for {
|
||||
c, err := l.listener.Accept()
|
||||
select {
|
||||
case <-l.stop:
|
||||
if c != nil {
|
||||
c.Close()
|
||||
}
|
||||
close(l.stop)
|
||||
l.stop = nil
|
||||
return nil, ErrListenerStopped
|
||||
default:
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
neterr, ok := err.(net.Error)
|
||||
if ok && neterr.Timeout() && neterr.Temporary() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GracefulListener) Close() (err error) {
|
||||
l.listener.Close()
|
||||
if l.stop != nil {
|
||||
l.stop <- 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *GracefulListener) Addr() net.Addr {
|
||||
return l.listener.Addr()
|
||||
}
|
||||
|
||||
// WrapListener wraps a net.Listener such that it can be closed gracefully
|
||||
func WrapListener(l net.Listener) net.Listener {
|
||||
return &GracefulListener{
|
||||
listener: l,
|
||||
stop: make(chan int),
|
||||
}
|
||||
}
|
|
@ -20,5 +20,8 @@ func CreateHTTPListener(conf *config.Config) (l net.Listener, err error) {
|
|||
l = s
|
||||
}
|
||||
}
|
||||
if l != nil {
|
||||
l = WrapListener(l)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
26
util/signals/closers.go
Fichier normal
26
util/signals/closers.go
Fichier normal
|
@ -0,0 +1,26 @@
|
|||
package signals
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
closeAccess sync.Mutex
|
||||
closers []io.Closer
|
||||
)
|
||||
|
||||
// RegisterCloser adds an io.Closer to close on interrupt
|
||||
func RegisterCloser(c io.Closer) {
|
||||
closeAccess.Lock()
|
||||
closers = append(closers, c)
|
||||
closeAccess.Unlock()
|
||||
}
|
||||
|
||||
func closeClosers() {
|
||||
closeAccess.Lock()
|
||||
for idx := range closers {
|
||||
closers[idx].Close()
|
||||
}
|
||||
closeAccess.Unlock()
|
||||
}
|
7
util/signals/interrupt.go
Fichier normal
7
util/signals/interrupt.go
Fichier normal
|
@ -0,0 +1,7 @@
|
|||
package signals
|
||||
|
||||
// handle interrupt signal, platform independent
|
||||
func interrupted() {
|
||||
closeClosers()
|
||||
handleInterrupt()
|
||||
}
|
|
@ -21,7 +21,7 @@ func handleReload() {
|
|||
// returns when done
|
||||
func Handle() {
|
||||
chnl := make(chan os.Signal)
|
||||
signal.Notify(chnl, syscall.SIGHUP)
|
||||
signal.Notify(chnl, syscall.SIGHUP, os.Interrupt)
|
||||
for {
|
||||
sig, ok := <-chnl
|
||||
if !ok {
|
||||
|
@ -31,8 +31,17 @@ func Handle() {
|
|||
case syscall.SIGHUP:
|
||||
handleReload()
|
||||
break
|
||||
case os.Interrupt:
|
||||
interrupted()
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unix implementation of interrupt
|
||||
// called in interrupted()
|
||||
func handleInterrupt() {
|
||||
// XXX: put unix specific cleanup here as needed
|
||||
}
|
||||
|
|
|
@ -2,7 +2,34 @@
|
|||
|
||||
package signals
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
func Handle() {
|
||||
// windows has no sighup LOOOOL, this does nothing
|
||||
// TODO: Something about SIGHUP for Windows
|
||||
|
||||
chnl := make(chan os.Signal)
|
||||
signal.Notify(chnl, os.Interrupt)
|
||||
for {
|
||||
sig, ok := <-chnl
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
switch sig {
|
||||
case os.Interrupt:
|
||||
// this also closes listeners
|
||||
interrupted()
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// win32 interrupt handler
|
||||
// called in interrupted()
|
||||
func handleInterrupt() {
|
||||
// XXX: put any win32 specific cleanup here as needed
|
||||
}
|
||||
|
|
Référencer dans un nouveau ticket