diff options
author | Matt Holt <[email protected]> | 2024-03-01 09:57:05 -0700 |
---|---|---|
committer | Matthew Holt <[email protected]> | 2024-04-11 16:20:53 -0600 |
commit | 4071dd4e20ca05587c0b4d349f1f39f2cde7e95b (patch) | |
tree | e0522d4edc7fdfea72d3328abd13ee537fa3c9e8 | |
parent | 0e204b730aa2b1fa0835336b1117eff8c420f713 (diff) | |
download | caddy-4071dd4e20ca05587c0b4d349f1f39f2cde7e95b.tar.gz caddy-4071dd4e20ca05587c0b4d349f1f39f2cde7e95b.zip |
core: OnExit hooks (#6128)
* core: OnExit callbacks
* core: Process-global OnExit callbacks
-rw-r--r-- | caddy.go | 28 | ||||
-rw-r--r-- | context.go | 12 |
2 files changed, 39 insertions, 1 deletions
@@ -707,6 +707,7 @@ func exitProcess(ctx context.Context, logger *zap.Logger) { logger.Warn("exiting; byeee!! 👋") exitCode := ExitCodeSuccess + lastContext := ActiveContext() // stop all apps if err := Stop(); err != nil { @@ -728,6 +729,16 @@ func exitProcess(ctx context.Context, logger *zap.Logger) { } } + // execute any process-exit callbacks + for _, exitFunc := range lastContext.exitFuncs { + exitFunc(ctx) + } + exitFuncsMu.Lock() + for _, exitFunc := range exitFuncs { + exitFunc(ctx) + } + exitFuncsMu.Unlock() + // shut down admin endpoint(s) in goroutines so that // if this function was called from an admin handler, // it has a chance to return gracefully @@ -766,6 +777,23 @@ var exiting = new(int32) // accessed atomically // EXPERIMENTAL API: subject to change or removal. func Exiting() bool { return atomic.LoadInt32(exiting) == 1 } +// OnExit registers a callback to invoke during process exit. +// This registration is PROCESS-GLOBAL, meaning that each +// function should only be registered once forever, NOT once +// per config load (etc). +// +// EXPERIMENTAL API: subject to change or removal. +func OnExit(f func(context.Context)) { + exitFuncsMu.Lock() + exitFuncs = append(exitFuncs, f) + exitFuncsMu.Unlock() +} + +var ( + exitFuncs []func(context.Context) + exitFuncsMu sync.Mutex +) + // Duration can be an integer or a string. An integer is // interpreted as nanoseconds. If a string, it is a Go // time.Duration value such as `300ms`, `1.5h`, or `2h45m`; diff --git a/context.go b/context.go index 85978d49c..efc2c5608 100644 --- a/context.go +++ b/context.go @@ -39,8 +39,9 @@ type Context struct { context.Context moduleInstances map[string][]Module cfg *Config - cleanupFuncs []func() ancestry []Module + cleanupFuncs []func() // invoked at every config unload + exitFuncs []func(context.Context) // invoked at config unload ONLY IF the process is exiting (EXPERIMENTAL) } // NewContext provides a new context derived from the given @@ -81,6 +82,15 @@ func (ctx *Context) OnCancel(f func()) { ctx.cleanupFuncs = append(ctx.cleanupFuncs, f) } +// OnExit executes f when the process exits gracefully. +// The function is only executed if the process is gracefully +// shut down while this context is active. +// +// EXPERIMENTAL API: subject to change or removal. +func (ctx *Context) OnExit(f func(context.Context)) { + ctx.exitFuncs = append(ctx.exitFuncs, f) +} + // LoadModule loads the Caddy module(s) from the specified field of the parent struct // pointer and returns the loaded module(s). The struct pointer and its field name as // a string are necessary so that reflection can be used to read the struct tag on the |