diff options
author | Matt Holt <[email protected]> | 2024-03-01 09:57:05 -0700 |
---|---|---|
committer | GitHub <[email protected]> | 2024-03-01 09:57:05 -0700 |
commit | 46c5db92da8ff971db26a3f11282fba5594a7f28 (patch) | |
tree | afe92806e42b9685d4326c4b214fdfc2988de004 | |
parent | de4959fe7b7a6a1ab7a7a12462222efe95fbb661 (diff) | |
download | caddy-46c5db92da8ff971db26a3f11282fba5594a7f28.tar.gz caddy-46c5db92da8ff971db26a3f11282fba5594a7f28.zip |
core: OnExit hooks (#6128)
* core: OnExit callbacks
* core: Process-global OnExit callbacks
-rw-r--r-- | caddy.go | 28 | ||||
-rw-r--r-- | context.go | 15 |
2 files changed, 41 insertions, 2 deletions
@@ -715,6 +715,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 { @@ -736,6 +737,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 @@ -774,6 +785,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 e90475b19..d73af7702 100644 --- a/context.go +++ b/context.go @@ -44,8 +44,9 @@ type Context struct { 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 @@ -86,7 +87,8 @@ func (ctx *Context) OnCancel(f func()) { ctx.cleanupFuncs = append(ctx.cleanupFuncs, f) } -// Filesystems returns a ref to the FilesystemMap +// Filesystems returns a ref to the FilesystemMap. +// EXPERIMENTAL: This API is subject to change. func (ctx *Context) Filesystems() FileSystems { // if no config is loaded, we use a default filesystemmap, which includes the osfs if ctx.cfg == nil { @@ -95,6 +97,15 @@ func (ctx *Context) Filesystems() FileSystems { return ctx.cfg.filesystems } +// 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 |