diff --git a/caddy.go b/caddy.go index c52891af0..568d682f1 100644 --- a/caddy.go +++ b/caddy.go @@ -73,10 +73,12 @@ type Instance struct { // servers is the list of servers with their listeners. servers []serverListener - // these are callbacks to execute when certain events happen - onStartup []func() error - onRestart []func() error - onShutdown []func() error + // these callbacks execute when certain events occur + onFirstStartup []func() error // starting, not as part of a restart + onStartup []func() error // starting, even as part of a restart + onRestart []func() error // before restart commences + onShutdown []func() error // stopping, even as part of a restart + onFinalShutdown []func() error // stopping, not as part of a restart } // Stop stops all servers contained in i. It does NOT @@ -104,10 +106,11 @@ func (i *Instance) Stop() error { return nil } -// shutdownCallbacks executes all the shutdown callbacks of i. -// An error returned from one does not stop execution of the rest. -// All the errors will be returned. -func (i *Instance) shutdownCallbacks() []error { +// ShutdownCallbacks executes all the shutdown callbacks of i, +// including ones that are scheduled only for the final shutdown +// of i. An error returned from one does not stop execution of +// the rest. All the non-nil errors will be returned. +func (i *Instance) ShutdownCallbacks() []error { var errs []error for _, shutdownFunc := range i.onShutdown { err := shutdownFunc() @@ -115,6 +118,12 @@ func (i *Instance) shutdownCallbacks() []error { errs = append(errs, err) } } + for _, finalShutdownFunc := range i.onFinalShutdown { + err := finalShutdownFunc() + if err != nil { + errs = append(errs, err) + } + } return errs } @@ -159,6 +168,12 @@ func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) { } // success! stop the old instance + for _, shutdownFunc := range i.onShutdown { + err := shutdownFunc() + if err != nil { + return i, err + } + } i.Stop() log.Println("[INFO] Reloading complete") @@ -409,6 +424,15 @@ func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]r return err } + // run startup callbacks + if restartFds == nil { + for _, firstStartupFunc := range inst.onFirstStartup { + err := firstStartupFunc() + if err != nil { + return err + } + } + } for _, startupFunc := range inst.onStartup { err := startupFunc() if err != nil { diff --git a/caddyhttp/errors/errors.go b/caddyhttp/errors/errors.go index f933adcd1..8d4dd84a0 100644 --- a/caddyhttp/errors/errors.go +++ b/caddyhttp/errors/errors.go @@ -29,7 +29,8 @@ type ErrorHandler struct { LogFile string Log *log.Logger LogRoller *httpserver.LogRoller - Debug bool // if true, errors are written out to client rather than to a log + Debug bool // if true, errors are written out to client rather than to a log + file *os.File // a log file to close when done } func (h ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { diff --git a/caddyhttp/errors/setup.go b/caddyhttp/errors/setup.go index 36ef74d55..2b3b6dafd 100644 --- a/caddyhttp/errors/setup.go +++ b/caddyhttp/errors/setup.go @@ -49,11 +49,10 @@ func setup(c *caddy.Controller) error { } if handler.LogRoller != nil { file.Close() - handler.LogRoller.Filename = handler.LogFile - writer = handler.LogRoller.GetLogWriter() } else { + handler.file = file writer = file } } @@ -62,6 +61,14 @@ func setup(c *caddy.Controller) error { return nil }) + // When server stops, close any open log file + c.OnShutdown(func() error { + if handler.file != nil { + handler.file.Close() + } + return nil + }) + httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { handler.Next = next return handler diff --git a/caddyhttp/log/log.go b/caddyhttp/log/log.go index e266f84f5..4d2f85037 100644 --- a/caddyhttp/log/log.go +++ b/caddyhttp/log/log.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "net/http" + "os" "github.com/mholt/caddy" "github.com/mholt/caddy/caddyhttp/httpserver" @@ -67,6 +68,7 @@ type Rule struct { Format string Log *log.Logger Roller *httpserver.LogRoller + file *os.File // if logging to a file that needs to be closed } const ( diff --git a/caddyhttp/log/setup.go b/caddyhttp/log/setup.go index ddb39da6d..8cdad9f85 100644 --- a/caddyhttp/log/setup.go +++ b/caddyhttp/log/setup.go @@ -43,6 +43,7 @@ func setup(c *caddy.Controller) error { rules[i].Roller.Filename = rules[i].OutputFile writer = rules[i].Roller.GetLogWriter() } else { + rules[i].file = file writer = file } } @@ -53,6 +54,16 @@ func setup(c *caddy.Controller) error { return nil }) + // When server stops, close any open log files + c.OnShutdown(func() error { + for _, rule := range rules { + if rule.file != nil { + rule.file.Close() + } + } + return nil + }) + httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { return Logger{Next: next, Rules: rules, ErrorFunc: httpserver.DefaultErrorFunc} }) diff --git a/controller.go b/controller.go index 3c25e4879..890534f9b 100644 --- a/controller.go +++ b/controller.go @@ -54,8 +54,14 @@ func (c *Controller) ServerType() string { return c.instance.serverType } +// OnFirstStartup adds fn to the list of callback functions to execute +// when the server is about to be started NOT as part of a restart. +func (c *Controller) OnFirstStartup(fn func() error) { + c.instance.onFirstStartup = append(c.instance.onFirstStartup, fn) +} + // OnStartup adds fn to the list of callback functions to execute -// when the server is about to be started. +// when the server is about to be started (including restarts). func (c *Controller) OnStartup(fn func() error) { c.instance.onStartup = append(c.instance.onStartup, fn) } @@ -67,11 +73,17 @@ func (c *Controller) OnRestart(fn func() error) { } // OnShutdown adds fn to the list of callback functions to execute -// when the server is about to be shut down.. +// when the server is about to be shut down (including restarts). func (c *Controller) OnShutdown(fn func() error) { c.instance.onShutdown = append(c.instance.onShutdown, fn) } +// OnFinalShutdown adds fn to the list of callback functions to execute +// when the server is about to be shut down NOT as part of a restart. +func (c *Controller) OnFinalShutdown(fn func() error) { + c.instance.onFinalShutdown = append(c.instance.onFinalShutdown, fn) +} + // Context gets the context associated with the instance associated with c. func (c *Controller) Context() Context { return c.instance.context diff --git a/sigtrap.go b/sigtrap.go index 7acdbc491..ed7dc803a 100644 --- a/sigtrap.go +++ b/sigtrap.go @@ -72,7 +72,7 @@ func allShutdownCallbacks() []error { var errs []error instancesMu.Lock() for _, inst := range instances { - errs = append(errs, inst.shutdownCallbacks()...) + errs = append(errs, inst.ShutdownCallbacks()...) } instancesMu.Unlock() return errs diff --git a/startupshutdown/startupshutdown.go b/startupshutdown/startupshutdown.go index 0a14e0c82..908a4f9cc 100644 --- a/startupshutdown/startupshutdown.go +++ b/startupshutdown/startupshutdown.go @@ -15,12 +15,12 @@ func init() { // Startup registers a startup callback to execute during server start. func Startup(c *caddy.Controller) error { - return registerCallback(c, c.OnStartup) + return registerCallback(c, c.OnFirstStartup) } // Shutdown registers a shutdown callback to execute during server stop. func Shutdown(c *caddy.Controller) error { - return registerCallback(c, c.OnShutdown) + return registerCallback(c, c.OnFinalShutdown) } // registerCallback registers a callback function to execute by