dont drop the last connection. fixes TLS issues
This commit is contained in:
parent
267d243f26
commit
57754541e7
84
grace.go
84
grace.go
@ -13,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -21,6 +22,9 @@ var (
|
|||||||
|
|
||||||
// This error is returned by Listener.Accept() when Close is in progress.
|
// This error is returned by Listener.Accept() when Close is in progress.
|
||||||
ErrAlreadyClosed = errors.New("already closed")
|
ErrAlreadyClosed = errors.New("already closed")
|
||||||
|
|
||||||
|
// Time in the past to trigger immediate deadline.
|
||||||
|
timeInPast = time.Date(1983, time.November, 6, 0, 0, 0, 0, time.UTC)
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -31,29 +35,17 @@ const (
|
|||||||
errClosed = "use of closed network connection"
|
errClosed = "use of closed network connection"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A FileListener is a file backed net.Listener.
|
// A Listener providing a graceful Close process and can be sent
|
||||||
type FileListener interface {
|
// across processes using the underlying File descriptor.
|
||||||
|
type Listener interface {
|
||||||
net.Listener
|
net.Listener
|
||||||
|
|
||||||
// Will return the underlying file representing this Listener.
|
// Will return the underlying file representing this Listener.
|
||||||
File() (f *os.File, err error)
|
File() (f *os.File, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Listener providing a graceful Close process and can be sent
|
|
||||||
// across processes using the underlying File descriptor.
|
|
||||||
type Listener interface {
|
|
||||||
FileListener
|
|
||||||
|
|
||||||
// Will indicate that a Close is requested preventing further Accept. It will
|
|
||||||
// also wait for the active connections to be terminated before returning.
|
|
||||||
// Note, this won't actually do the close, and is provided as part of the
|
|
||||||
// public API for cases where the socket must not be closed (such as systemd
|
|
||||||
// activation).
|
|
||||||
CloseRequest()
|
|
||||||
}
|
|
||||||
|
|
||||||
type listener struct {
|
type listener struct {
|
||||||
FileListener
|
Listener
|
||||||
closed bool
|
closed bool
|
||||||
closedMutex sync.RWMutex
|
closedMutex sync.RWMutex
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
@ -72,36 +64,60 @@ func (c conn) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wraps an existing File listener to provide a graceful Close() process.
|
// Wraps an existing File listener to provide a graceful Close() process.
|
||||||
func NewListener(l FileListener) Listener {
|
func NewListener(l Listener) Listener {
|
||||||
return &listener{FileListener: l}
|
return &listener{Listener: l}
|
||||||
}
|
|
||||||
|
|
||||||
func (l *listener) CloseRequest() {
|
|
||||||
l.closedMutex.Lock()
|
|
||||||
l.closed = true
|
|
||||||
l.closedMutex.Unlock()
|
|
||||||
l.wg.Wait()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *listener) Close() error {
|
func (l *listener) Close() error {
|
||||||
l.CloseRequest()
|
l.closedMutex.Lock()
|
||||||
return l.FileListener.Close()
|
l.closed = true
|
||||||
|
l.closedMutex.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// Init provided sockets dont actually close so we trigger Accept to return
|
||||||
|
// by setting the deadline.
|
||||||
|
if os.Getppid() == 1 {
|
||||||
|
if ld, ok := l.Listener.(interface {
|
||||||
|
SetDeadline(t time.Time) error
|
||||||
|
}); ok {
|
||||||
|
ld.SetDeadline(timeInPast)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(os.Stderr, "init activated server did not have SetDeadline")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = l.Listener.Close()
|
||||||
|
}
|
||||||
|
l.wg.Wait()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *listener) Accept() (net.Conn, error) {
|
func (l *listener) Accept() (net.Conn, error) {
|
||||||
c, err := l.FileListener.Accept()
|
l.closedMutex.RLock()
|
||||||
|
if l.closed {
|
||||||
|
l.closedMutex.RUnlock()
|
||||||
|
return nil, ErrAlreadyClosed
|
||||||
|
}
|
||||||
|
l.closedMutex.RUnlock()
|
||||||
|
|
||||||
|
c, err := l.Listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasSuffix(err.Error(), errClosed) {
|
if strings.HasSuffix(err.Error(), errClosed) {
|
||||||
return nil, ErrAlreadyClosed
|
return nil, ErrAlreadyClosed
|
||||||
}
|
}
|
||||||
return nil, err
|
|
||||||
}
|
// We use SetDeadline above to trigger Accept to return when we're trying
|
||||||
|
// to handoff to a child as part of our restart process. In this scenario
|
||||||
|
// we want to tread the timeout the same as a Close.
|
||||||
|
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||||
l.closedMutex.RLock()
|
l.closedMutex.RLock()
|
||||||
defer l.closedMutex.RUnlock()
|
|
||||||
if l.closed {
|
if l.closed {
|
||||||
c.Close()
|
l.closedMutex.RUnlock()
|
||||||
return nil, ErrAlreadyClosed
|
return nil, ErrAlreadyClosed
|
||||||
}
|
}
|
||||||
|
l.closedMutex.RUnlock()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
l.wg.Add(1)
|
l.wg.Add(1)
|
||||||
return conn{Conn: c, wg: &l.wg}, nil
|
return conn{Conn: c, wg: &l.wg}, nil
|
||||||
}
|
}
|
||||||
@ -118,14 +134,10 @@ func Wait(listeners []Listener) (err error) {
|
|||||||
wg.Add(len(listeners))
|
wg.Add(len(listeners))
|
||||||
for _, l := range listeners {
|
for _, l := range listeners {
|
||||||
go func(l Listener) {
|
go func(l Listener) {
|
||||||
if os.Getppid() == 1 { // init provided sockets dont actually close
|
|
||||||
l.CloseRequest()
|
|
||||||
} else {
|
|
||||||
cErr := l.Close()
|
cErr := l.Close()
|
||||||
if cErr != nil {
|
if cErr != nil {
|
||||||
err = cErr
|
err = cErr
|
||||||
}
|
}
|
||||||
}
|
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}(l)
|
}(l)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user