Skip to content

Commit 46c5db9

Browse files
authored
core: OnExit hooks (#6128)
* core: OnExit callbacks * core: Process-global OnExit callbacks
1 parent de4959f commit 46c5db9

File tree

2 files changed

+41
-2
lines changed

2 files changed

+41
-2
lines changed

caddy.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,7 @@ func exitProcess(ctx context.Context, logger *zap.Logger) {
715715
logger.Warn("exiting; byeee!! 👋")
716716

717717
exitCode := ExitCodeSuccess
718+
lastContext := ActiveContext()
718719

719720
// stop all apps
720721
if err := Stop(); err != nil {
@@ -736,6 +737,16 @@ func exitProcess(ctx context.Context, logger *zap.Logger) {
736737
}
737738
}
738739

740+
// execute any process-exit callbacks
741+
for _, exitFunc := range lastContext.exitFuncs {
742+
exitFunc(ctx)
743+
}
744+
exitFuncsMu.Lock()
745+
for _, exitFunc := range exitFuncs {
746+
exitFunc(ctx)
747+
}
748+
exitFuncsMu.Unlock()
749+
739750
// shut down admin endpoint(s) in goroutines so that
740751
// if this function was called from an admin handler,
741752
// it has a chance to return gracefully
@@ -774,6 +785,23 @@ var exiting = new(int32) // accessed atomically
774785
// EXPERIMENTAL API: subject to change or removal.
775786
func Exiting() bool { return atomic.LoadInt32(exiting) == 1 }
776787

788+
// OnExit registers a callback to invoke during process exit.
789+
// This registration is PROCESS-GLOBAL, meaning that each
790+
// function should only be registered once forever, NOT once
791+
// per config load (etc).
792+
//
793+
// EXPERIMENTAL API: subject to change or removal.
794+
func OnExit(f func(context.Context)) {
795+
exitFuncsMu.Lock()
796+
exitFuncs = append(exitFuncs, f)
797+
exitFuncsMu.Unlock()
798+
}
799+
800+
var (
801+
exitFuncs []func(context.Context)
802+
exitFuncsMu sync.Mutex
803+
)
804+
777805
// Duration can be an integer or a string. An integer is
778806
// interpreted as nanoseconds. If a string, it is a Go
779807
// time.Duration value such as `300ms`, `1.5h`, or `2h45m`;

context.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ type Context struct {
4444

4545
moduleInstances map[string][]Module
4646
cfg *Config
47-
cleanupFuncs []func()
4847
ancestry []Module
48+
cleanupFuncs []func() // invoked at every config unload
49+
exitFuncs []func(context.Context) // invoked at config unload ONLY IF the process is exiting (EXPERIMENTAL)
4950
}
5051

5152
// NewContext provides a new context derived from the given
@@ -86,7 +87,8 @@ func (ctx *Context) OnCancel(f func()) {
8687
ctx.cleanupFuncs = append(ctx.cleanupFuncs, f)
8788
}
8889

89-
// Filesystems returns a ref to the FilesystemMap
90+
// Filesystems returns a ref to the FilesystemMap.
91+
// EXPERIMENTAL: This API is subject to change.
9092
func (ctx *Context) Filesystems() FileSystems {
9193
// if no config is loaded, we use a default filesystemmap, which includes the osfs
9294
if ctx.cfg == nil {
@@ -95,6 +97,15 @@ func (ctx *Context) Filesystems() FileSystems {
9597
return ctx.cfg.filesystems
9698
}
9799

100+
// OnExit executes f when the process exits gracefully.
101+
// The function is only executed if the process is gracefully
102+
// shut down while this context is active.
103+
//
104+
// EXPERIMENTAL API: subject to change or removal.
105+
func (ctx *Context) OnExit(f func(context.Context)) {
106+
ctx.exitFuncs = append(ctx.exitFuncs, f)
107+
}
108+
98109
// LoadModule loads the Caddy module(s) from the specified field of the parent struct
99110
// pointer and returns the loaded module(s). The struct pointer and its field name as
100111
// a string are necessary so that reflection can be used to read the struct tag on the

0 commit comments

Comments
 (0)