Skip to content
This repository has been archived by the owner on Mar 22, 2019. It is now read-only.

handle closed listeners #14

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gracedemo/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"os"
"time"

"github.com/facebookgo/grace/gracehttp"
"github.com/absolute8511/grace/gracehttp"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was probably flipped for testing, but we should undo this before merging here.

)

var (
Expand Down
8 changes: 7 additions & 1 deletion gracehttp/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"sync"
"syscall"

"github.com/facebookgo/grace/gracenet"
"github.com/absolute8511/grace/gracenet"
"github.com/facebookgo/httpdown"
)

Expand Down Expand Up @@ -186,6 +186,12 @@ func ServeWithOptions(servers []*http.Server, options ...option) error {
return a.run()
}

func ServeWithNet(servers []*http.Server, grace *gracenet.Net) error {
a := newApp(servers)
a.net = grace
return a.run()
}

// 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 {
Expand Down
2 changes: 1 addition & 1 deletion gracehttp/testbin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"testing"
"time"

"github.com/facebookgo/grace/gracehttp"
"github.com/absolute8511/grace/gracehttp"
)

const preStartProcessEnv = "GRACEHTTP_PRE_START_PROCESS"
Expand Down
61 changes: 48 additions & 13 deletions gracenet/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ var originalWD, _ = os.Getwd()
// Net provides the family of Listen functions and maintains the associated
// state. Typically you will have only once instance of Net per application.
type Net struct {
inherited []net.Listener
active []net.Listener
mutex sync.Mutex
inheritOnce sync.Once
inherited []net.Listener
active []net.Listener
closedListeners []net.Listener
mutex sync.Mutex
inheritOnce sync.Once

// used in tests to override the default behavior of starting from fd 3.
fdStart int
Expand Down Expand Up @@ -168,6 +169,13 @@ func (n *Net) ListenUnix(nett string, laddr *net.UnixAddr) (*net.UnixListener, e
return l, nil
}

func (n *Net) Close(l net.Listener) {
n.mutex.Lock()
defer n.mutex.Unlock()
n.closedListeners = append(n.closedListeners, l)
l.Close()
}

// activeListeners returns a snapshot copy of the active listeners.
func (n *Net) activeListeners() ([]net.Listener, error) {
n.mutex.Lock()
Expand Down Expand Up @@ -198,6 +206,35 @@ func isSameAddr(a1, a2 net.Addr) bool {
return a1s == a2s
}

func (n *Net) prepareInheriteFiles(listeners []net.Listener) ([]*os.File, error) {
n.mutex.Lock()
closedList := make([]net.Addr, len(n.closedListeners))
for i, l := range n.closedListeners {
closedList[i] = l.Addr()
}
n.mutex.Unlock()
// Extract the fds from the listeners.
files := make([]*os.File, 0, len(listeners)-len(closedList))
for _, l := range listeners {
isClosed := false
for _, closedAddr := range closedList {
if isSameAddr(l.Addr(), closedAddr) {
isClosed = true
break
}
}
if isClosed {
continue
}
f, err := l.(filer).File()
if err != nil {
return nil, err
}
files = append(files, f)
}
return files, nil
}

// StartProcess starts a new process passing it the active listeners. It
// doesn't fork, but starts a new process using the same environment and
// arguments as when it was originally started. This allows for a newly
Expand All @@ -210,15 +247,13 @@ func (n *Net) StartProcess() (int, error) {
}

// Extract the fds from the listeners.
files := make([]*os.File, len(listeners))
for i, l := range listeners {
files[i], err = l.(filer).File()
if err != nil {
return 0, err
}
defer files[i].Close()
files, err := n.prepareInheriteFiles(listeners)
if err != nil {
return 0, err
}
for _, f := range files {
defer f.Close()
}

// Use the original binary location. This works with symlinks such that if
// the file it points to has been changed we will use the updated symlink.
argv0, err := exec.LookPath(os.Args[0])
Expand All @@ -233,7 +268,7 @@ func (n *Net) StartProcess() (int, error) {
env = append(env, v)
}
}
env = append(env, fmt.Sprintf("%s%d", envCountKeyPrefix, len(listeners)))
env = append(env, fmt.Sprintf("%s%d", envCountKeyPrefix, len(files)))

allFiles := append([]*os.File{os.Stdin, os.Stdout, os.Stderr}, files...)
process, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
Expand Down
72 changes: 72 additions & 0 deletions gracenet/net_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,78 @@ func TestPortZeroTwice(t *testing.T) {
ensure.Nil(t, l2.Close())
}

func TestPrepareInheriteFiles(t *testing.T) {
var n Net

port1, err := freeport.Get()
ensure.Nil(t, err)
addr1 := fmt.Sprintf(":%d", port1)
l1, err := net.Listen("tcp", addr1)
ensure.Nil(t, err)

port2, err := freeport.Get()
ensure.Nil(t, err)
addr2 := fmt.Sprintf(":%d", port2)
l2, err := net.Listen("tcp", addr2)
ensure.Nil(t, err)

files, err := n.prepareInheriteFiles([]net.Listener{l1, l2})
ensure.Nil(t, err)
ensure.DeepEqual(t, len(files), 2)

// assign both to prevent GC from kicking in the finalizer
fds := []int{dup(t, int(files[0].Fd())), dup(t, int(files[0].Fd()))}
n.fdStart = fds[0]
os.Setenv(envCountKey, "2")
// Close these after to ensure we get coalaced file descriptors.
ensure.Nil(t, l1.Close())
ensure.Nil(t, l2.Close())

ensure.Nil(t, n.inherit())
ensure.DeepEqual(t, len(n.inherited), 2)
}

func TestCloseListener(t *testing.T) {
var n Net

port1, err := freeport.Get()
ensure.Nil(t, err)
addr1 := fmt.Sprintf(":%d", port1)
l1, err := net.Listen("tcp", addr1)
ensure.Nil(t, err)

port2, err := freeport.Get()
ensure.Nil(t, err)
addr2 := fmt.Sprintf(":%d", port2)
l2, err := net.Listen("tcp", addr2)
ensure.Nil(t, err)

// close using the gracenet interface
n.Close(l2)

files, err := n.prepareInheriteFiles([]net.Listener{l1, l2})
ensure.Nil(t, err)
ensure.DeepEqual(t, len(files), 1)

// assign both to prevent GC from kicking in the finalizer
fds := []int{dup(t, int(files[0].Fd()))}
n.fdStart = fds[0]
os.Setenv(envCountKey, "1")
// Close these after to ensure we get coalaced file descriptors.
ensure.Nil(t, l1.Close())

ensure.Nil(t, n.inherit())
ensure.DeepEqual(t, len(n.inherited), 1)

l1, err = n.Listen("tcp", addr1)

ensure.Nil(t, err)
ensure.DeepEqual(t, len(n.active), 1)
ensure.DeepEqual(t, n.inherited[0], nil)
ensure.Nil(t, l1.Close())
ensure.Nil(t, files[0].Close())
}

// We dup file descriptors because the os.Files are closed by a finalizer when
// they are GCed, which interacts badly with the fact that the OS reuses fds,
// and that we emulating inheriting the fd by it's integer value in our tests.
Expand Down