diff --git a/caddy/caddy.go b/caddy/caddy.go index 9988476d..da7fd14b 100644 --- a/caddy/caddy.go +++ b/caddy/caddy.go @@ -71,6 +71,10 @@ var ( // index in the list of inherited file descriptors. This // variable is not safe for concurrent access. loadedGob caddyfileGob + + // startedBefore should be set to true if caddy has been + // started at least once. + startedBefore bool ) const ( @@ -128,6 +132,7 @@ func Start(cdyfile Input) (err error) { if err != nil { return err } + startedBefore = true // Close remaining file descriptors we may have inherited that we don't need if IsRestart() { @@ -203,6 +208,18 @@ func startServers(groupings bindingGroup) error { wg.Add(1) go func(s *server.Server, ln server.ListenerFile) { defer wg.Done() + + // run startup functions that should only execute when + // the original parent process is starting. + if !IsRestart() && !startedBefore { + err := s.RunFirstStartupFuncs() + if err != nil { + errChan <- err + return + } + } + + // start the server if ln != nil { errChan <- s.Serve(ln) } else { diff --git a/caddy/setup/startupshutdown.go b/caddy/setup/startupshutdown.go index d09831cd..7a21ef47 100644 --- a/caddy/setup/startupshutdown.go +++ b/caddy/setup/startupshutdown.go @@ -10,7 +10,7 @@ import ( // Startup registers a startup callback to execute during server start. func Startup(c *Controller) (middleware.Middleware, error) { - return nil, registerCallback(c, &c.Startup) + return nil, registerCallback(c, &c.FirstStartup) } // Shutdown registers a shutdown callback to execute during process exit. diff --git a/caddy/setup/startupshutdown_test.go b/caddy/setup/startupshutdown_test.go index cf07a7e8..a6bdf1b7 100644 --- a/caddy/setup/startupshutdown_test.go +++ b/caddy/setup/startupshutdown_test.go @@ -45,7 +45,7 @@ func TestStartup(t *testing.T) { if err != nil { t.Errorf("Expected no errors, got: %v", err) } - err = c.Startup[0]() + err = c.FirstStartup[0]() if err != nil && !test.shouldExecutionErr { t.Errorf("Test %d recieved an error of:\n%v", i, err) } diff --git a/server/config.go b/server/config.go index a3bb5f50..f9ec05fc 100644 --- a/server/config.go +++ b/server/config.go @@ -26,11 +26,21 @@ type Config struct { // Middleware stack; map of path scope to middleware -- TODO: Support path scope? Middleware map[string][]middleware.Middleware - // Functions (or methods) to execute at server start; these - // are executed before any parts of the server are configured, - // and the functions are blocking + // Startup is a list of functions (or methods) to execute at + // server startup and restart; these are executed before any + // parts of the server are configured, and the functions are + // blocking. These are good for setting up middlewares and + // starting goroutines. Startup []func() error + // FirstStartup is like Startup but these functions only execute + // during the initial startup, not on subsequent restarts. + // + // (Note: The server does not ever run these on its own; it is up + // to the calling application to do so, and do so only once, as the + // server itself has no notion whether it's a restart or not.) + FirstStartup []func() error + // Functions (or methods) to execute when the server quits; // these are executed in response to SIGINT and are blocking Shutdown []func() error diff --git a/server/server.go b/server/server.go index 5c97914d..9971bc95 100644 --- a/server/server.go +++ b/server/server.go @@ -371,6 +371,23 @@ func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error { return nil } +// RunFirstStartupFuncs runs all of the server's FirstStartup +// callback functions unless one of them returns an error first. +// It is up the caller's responsibility to call this only once and +// at the correct time. The functions here should not be executed +// at restarts or where the user does not explicitly start a new +// instance of the server. +func (s *Server) RunFirstStartupFuncs() error { + for _, vh := range s.vhosts { + for _, f := range vh.config.FirstStartup { + if err := f(); err != nil { + return err + } + } + } + return nil +} + // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted // connections. It's used by ListenAndServe and ListenAndServeTLS so // dead TCP connections (e.g. closing laptop mid-download) eventually