1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #4168 from crosbymichael/add-listenbuffer

Hold connections until the daemon has fully loaded
This commit is contained in:
Victor Vieux 2014-02-17 16:04:49 -08:00
commit 4187f4e750
4 changed files with 118 additions and 21 deletions

View file

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/engine"
"github.com/dotcloud/docker/pkg/listenbuffer"
"github.com/dotcloud/docker/pkg/systemd" "github.com/dotcloud/docker/pkg/systemd"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -25,6 +26,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time"
) )
// FIXME: move code common to client and server to common.go // FIXME: move code common to client and server to common.go
@ -34,6 +36,10 @@ const (
DEFAULTUNIXSOCKET = "/var/run/docker.sock" DEFAULTUNIXSOCKET = "/var/run/docker.sock"
) )
var (
activationLock chan struct{}
)
func ValidateHost(val string) (string, error) { func ValidateHost(val string) (string, error) {
host, err := utils.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val) host, err := utils.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val)
if err != nil { if err != nil {
@ -1159,7 +1165,7 @@ func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors
} }
} }
l, err := net.Listen(proto, addr) l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock, 15*time.Minute)
if err != nil { if err != nil {
return err return err
} }
@ -1201,8 +1207,15 @@ func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors
// ServeApi loops through all of the protocols sent in to docker and spawns // ServeApi loops through all of the protocols sent in to docker and spawns
// off a go routine to setup a serving http.Server for each. // off a go routine to setup a serving http.Server for each.
func ServeApi(job *engine.Job) engine.Status { func ServeApi(job *engine.Job) engine.Status {
protoAddrs := job.Args var (
chErrors := make(chan error, len(protoAddrs)) protoAddrs = job.Args
chErrors = make(chan error, len(protoAddrs))
)
activationLock = make(chan struct{})
if err := job.Eng.Register("acceptconnections", AcceptConnections); err != nil {
return job.Error(err)
}
for _, protoAddr := range protoAddrs { for _, protoAddr := range protoAddrs {
protoAddrParts := strings.SplitN(protoAddr, "://", 2) protoAddrParts := strings.SplitN(protoAddr, "://", 2)
@ -1219,8 +1232,15 @@ func ServeApi(job *engine.Job) engine.Status {
} }
} }
return engine.StatusOK
}
func AcceptConnections(job *engine.Job) engine.Status {
// Tell the init daemon we are accepting requests // Tell the init daemon we are accepting requests
go systemd.SdNotify("READY=1") go systemd.SdNotify("READY=1")
// close the lock so the listeners start accepting connections
close(activationLock)
return engine.StatusOK return engine.StatusOK
} }

View file

@ -81,25 +81,36 @@ func main() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// Load plugin: httpapi // load the daemon in the background so we can immediately start
job := eng.Job("initserver") // the http api so that connections don't fail while the daemon
job.Setenv("Pidfile", *pidfile) // is booting
job.Setenv("Root", *flRoot) go func() {
job.SetenvBool("AutoRestart", *flAutoRestart) // Load plugin: httpapi
job.SetenvList("Dns", flDns.GetAll()) job := eng.Job("initserver")
job.SetenvBool("EnableIptables", *flEnableIptables) job.Setenv("Pidfile", *pidfile)
job.SetenvBool("EnableIpForward", *flEnableIpForward) job.Setenv("Root", *flRoot)
job.Setenv("BridgeIface", *bridgeName) job.SetenvBool("AutoRestart", *flAutoRestart)
job.Setenv("BridgeIP", *bridgeIp) job.SetenvList("Dns", flDns.GetAll())
job.Setenv("DefaultIp", *flDefaultIp) job.SetenvBool("EnableIptables", *flEnableIptables)
job.SetenvBool("InterContainerCommunication", *flInterContainerComm) job.SetenvBool("EnableIpForward", *flEnableIpForward)
job.Setenv("GraphDriver", *flGraphDriver) job.Setenv("BridgeIface", *bridgeName)
job.SetenvInt("Mtu", *flMtu) job.Setenv("BridgeIP", *bridgeIp)
if err := job.Run(); err != nil { job.Setenv("DefaultIp", *flDefaultIp)
log.Fatal(err) job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
} job.Setenv("GraphDriver", *flGraphDriver)
job.SetenvInt("Mtu", *flMtu)
if err := job.Run(); err != nil {
log.Fatal(err)
}
// after the daemon is done setting up we can tell the api to start
// accepting connections
if err := eng.Job("acceptconnections").Run(); err != nil {
log.Fatal(err)
}
}()
// Serve api // Serve api
job = eng.Job("serveapi", flHosts.GetAll()...) job := eng.Job("serveapi", flHosts.GetAll()...)
job.SetenvBool("Logging", true) job.SetenvBool("Logging", true)
job.SetenvBool("EnableCors", *flEnableCors) job.SetenvBool("EnableCors", *flEnableCors)
job.Setenv("Version", dockerversion.VERSION) job.Setenv("Version", dockerversion.VERSION)

View file

@ -171,9 +171,14 @@ func spawnGlobalDaemon() {
log.Fatalf("Unable to spawn the test daemon: %s", err) log.Fatalf("Unable to spawn the test daemon: %s", err)
} }
}() }()
// Give some time to ListenAndServer to actually start // Give some time to ListenAndServer to actually start
// FIXME: use inmem transports instead of tcp // FIXME: use inmem transports instead of tcp
time.Sleep(time.Second) time.Sleep(time.Second)
if err := eng.Job("acceptconnections").Run(); err != nil {
log.Fatalf("Unable to accept connections for test api: %s", err)
}
} }
// FIXME: test that ImagePull(json=true) send correct json output // FIXME: test that ImagePull(json=true) send correct json output

View file

@ -0,0 +1,61 @@
/*
Package to allow go applications to immediately start
listening on a socket, unix, tcp, udp but hold connections
until the application has booted and is ready to accept them
*/
package listenbuffer
import (
"fmt"
"net"
"time"
)
// NewListenBuffer returns a listener listening on addr with the protocol. It sets the
// timeout to wait on first connection before an error is returned
func NewListenBuffer(proto, addr string, activate chan struct{}, timeout time.Duration) (net.Listener, error) {
wrapped, err := net.Listen(proto, addr)
if err != nil {
return nil, err
}
return &defaultListener{
wrapped: wrapped,
activate: activate,
timeout: timeout,
}, nil
}
type defaultListener struct {
wrapped net.Listener // the real listener to wrap
ready bool // is the listner ready to start accpeting connections
activate chan struct{}
timeout time.Duration // how long to wait before we consider this an error
}
func (l *defaultListener) Close() error {
return l.wrapped.Close()
}
func (l *defaultListener) Addr() net.Addr {
return l.wrapped.Addr()
}
func (l *defaultListener) Accept() (net.Conn, error) {
// if the listen has been told it is ready then we can go ahead and
// start returning connections
if l.ready {
return l.wrapped.Accept()
}
select {
case <-time.After(l.timeout):
// close the connection so any clients are disconnected
l.Close()
return nil, fmt.Errorf("timeout (%s) reached waiting for listener to become ready", l.timeout.String())
case <-l.activate:
l.ready = true
return l.Accept()
}
panic("unreachable")
}