Commit 5cced604 authored by Matthew Holt's avatar Matthew Holt

startup: Only run commands at first startup

We had to hack some special support into the server and caddy packages for this. There are some middlewares which should only execute commands when the original parent process first starts up. For example, someone using the startup directive to start a backend service would not expect the command to be executed every time the config was reloaded or changed - only once when they first started the original caddy process.

This commit adds FirstStartup to the virtualhost config
parent 76ec785e
...@@ -71,6 +71,10 @@ var ( ...@@ -71,6 +71,10 @@ var (
// index in the list of inherited file descriptors. This // index in the list of inherited file descriptors. This
// variable is not safe for concurrent access. // variable is not safe for concurrent access.
loadedGob caddyfileGob loadedGob caddyfileGob
// startedBefore should be set to true if caddy has been
// started at least once.
startedBefore bool
) )
const ( const (
...@@ -128,6 +132,7 @@ func Start(cdyfile Input) (err error) { ...@@ -128,6 +132,7 @@ func Start(cdyfile Input) (err error) {
if err != nil { if err != nil {
return err return err
} }
startedBefore = true
// Close remaining file descriptors we may have inherited that we don't need // Close remaining file descriptors we may have inherited that we don't need
if IsRestart() { if IsRestart() {
...@@ -203,6 +208,18 @@ func startServers(groupings bindingGroup) error { ...@@ -203,6 +208,18 @@ func startServers(groupings bindingGroup) error {
wg.Add(1) wg.Add(1)
go func(s *server.Server, ln server.ListenerFile) { go func(s *server.Server, ln server.ListenerFile) {
defer wg.Done() 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 { if ln != nil {
errChan <- s.Serve(ln) errChan <- s.Serve(ln)
} else { } else {
......
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
// Startup registers a startup callback to execute during server start. // Startup registers a startup callback to execute during server start.
func Startup(c *Controller) (middleware.Middleware, error) { 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. // Shutdown registers a shutdown callback to execute during process exit.
......
...@@ -45,7 +45,7 @@ func TestStartup(t *testing.T) { ...@@ -45,7 +45,7 @@ func TestStartup(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Expected no errors, got: %v", err) t.Errorf("Expected no errors, got: %v", err)
} }
err = c.Startup[0]() err = c.FirstStartup[0]()
if err != nil && !test.shouldExecutionErr { if err != nil && !test.shouldExecutionErr {
t.Errorf("Test %d recieved an error of:\n%v", i, err) t.Errorf("Test %d recieved an error of:\n%v", i, err)
} }
......
...@@ -26,11 +26,21 @@ type Config struct { ...@@ -26,11 +26,21 @@ type Config struct {
// Middleware stack; map of path scope to middleware -- TODO: Support path scope? // Middleware stack; map of path scope to middleware -- TODO: Support path scope?
Middleware map[string][]middleware.Middleware Middleware map[string][]middleware.Middleware
// Functions (or methods) to execute at server start; these // Startup is a list of functions (or methods) to execute at
// are executed before any parts of the server are configured, // server startup and restart; these are executed before any
// and the functions are blocking // parts of the server are configured, and the functions are
// blocking. These are good for setting up middlewares and
// starting goroutines.
Startup []func() error 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; // Functions (or methods) to execute when the server quits;
// these are executed in response to SIGINT and are blocking // these are executed in response to SIGINT and are blocking
Shutdown []func() error Shutdown []func() error
......
...@@ -371,6 +371,23 @@ func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error { ...@@ -371,6 +371,23 @@ func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error {
return nil 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 // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so // connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually // dead TCP connections (e.g. closing laptop mid-download) eventually
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment