aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMatt Holt <[email protected]>2024-03-01 09:57:05 -0700
committerMatthew Holt <[email protected]>2024-04-11 16:20:53 -0600
commit4071dd4e20ca05587c0b4d349f1f39f2cde7e95b (patch)
treee0522d4edc7fdfea72d3328abd13ee537fa3c9e8
parent0e204b730aa2b1fa0835336b1117eff8c420f713 (diff)
downloadcaddy-4071dd4e20ca05587c0b4d349f1f39f2cde7e95b.tar.gz
caddy-4071dd4e20ca05587c0b4d349f1f39f2cde7e95b.zip
core: OnExit hooks (#6128)
* core: OnExit callbacks * core: Process-global OnExit callbacks
-rw-r--r--caddy.go28
-rw-r--r--context.go12
2 files changed, 39 insertions, 1 deletions
diff --git a/caddy.go b/caddy.go
index 8e7dce507..4086b4a11 100644
--- a/caddy.go
+++ b/caddy.go
@@ -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