From 73f77f162412073aaeba775fd82a9e5d090e35c9 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 10 May 2017 08:23:29 -0400 Subject: [PATCH] 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 --- .gitignore | 4 +++ main.go | 10 ++++++- network/closer.go | 17 ++++++++++++ network/graceful.go | 58 +++++++++++++++++++++++++++++++++++++++ network/network.go | 3 ++ util/signals/closers.go | 26 ++++++++++++++++++ util/signals/interrupt.go | 7 +++++ util/signals/unix.go | 11 +++++++- util/signals/win32.go | 29 +++++++++++++++++++- 9 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 network/closer.go create mode 100644 network/graceful.go create mode 100644 util/signals/closers.go create mode 100644 util/signals/interrupt.go diff --git a/.gitignore b/.gitignore index fd8169c1..f42f961e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,7 @@ templates/*.html.go *.backup tags *.retry + +# emacs temp files +*\#* +*~ \ No newline at end of file diff --git a/main.go b/main.go index a0c8a3a4..3c89c7a8 100644 --- a/main.go +++ b/main.go @@ -40,9 +40,17 @@ 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) - log.CheckError(err) + if err != nil && err != network.ErrListenerStopped { + log.CheckError(err) + } + } } diff --git a/network/closer.go b/network/closer.go new file mode 100644 index 00000000..267db4b7 --- /dev/null +++ b/network/closer.go @@ -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) +} diff --git a/network/graceful.go b/network/graceful.go new file mode 100644 index 00000000..bcf4555a --- /dev/null +++ b/network/graceful.go @@ -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), + } +} diff --git a/network/network.go b/network/network.go index 89b249b6..d851da0a 100644 --- a/network/network.go +++ b/network/network.go @@ -20,5 +20,8 @@ func CreateHTTPListener(conf *config.Config) (l net.Listener, err error) { l = s } } + if l != nil { + l = WrapListener(l) + } return } diff --git a/util/signals/closers.go b/util/signals/closers.go new file mode 100644 index 00000000..ea1862de --- /dev/null +++ b/util/signals/closers.go @@ -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() +} diff --git a/util/signals/interrupt.go b/util/signals/interrupt.go new file mode 100644 index 00000000..f571ab35 --- /dev/null +++ b/util/signals/interrupt.go @@ -0,0 +1,7 @@ +package signals + +// handle interrupt signal, platform independent +func interrupted() { + closeClosers() + handleInterrupt() +} diff --git a/util/signals/unix.go b/util/signals/unix.go index 091bb25e..fe395001 100644 --- a/util/signals/unix.go +++ b/util/signals/unix.go @@ -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 +} diff --git a/util/signals/win32.go b/util/signals/win32.go index 9d9912e1..96f67013 100644 --- a/util/signals/win32.go +++ b/util/signals/win32.go @@ -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 }