diff --git a/GUIDE.md b/GUIDE.md index cede970..f5860ba 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -25,3 +25,41 @@ func run(ctx context.Context, args []string) error { return nil } ``` + +## Manually trigger garbage collection + +Might be useful when something bad happened and you don't want to restart the binary/instance/container/etc. + +```go +func triggerGC(ctx context.Context) { + appx.DoOnSignal(ctx, os.SIGUSR1, func(ctx context.Context) { + runtime.GC() + }) +} +``` + +## Reload config on SIGHUP + +Very popular pattern to add zero-restart configs to your application. + +```go +type Config struct{ + // some fields +} + +func rereadConfig(ctx context.Context) (*Config, error) { + var cfg Config + var err error + + appx.DoOnSignal(ctx, os.SIGHUP, func(ctx context.Context) { + err = json.Unmarshal(&cfg) + if err != nil{ + println("oh, error :(") + } + }) + + return &cfg, err +} +``` + +Took from archived [cristalhq/sigreload](https://github.com/cristalhq/sigreload) diff --git a/appx.go b/appx.go index 9aa5072..4e301a3 100644 --- a/appx.go +++ b/appx.go @@ -4,7 +4,6 @@ import ( "context" "os" "os/signal" - "runtime" "syscall" "time" ) @@ -22,21 +21,26 @@ func Uptime() time.Duration { return time.Since(startTime) } -// DoGCWhen one of the given signal happens (GC means runtime.GC()). +// DoOnSignal runs fn on every signal. // Function is async, context is used to close underlying goroutine. -// In most cases appx.DoGCWhen(os.SIGUSR1) should be enough. -func DoGCWhen(ctx context.Context, signals ...os.Signal) { - ch := make(chan os.Signal, 1) - signal.Notify(ch, signals...) +func DoOnSignal(ctx context.Context, signal os.Signal, fn func(ctx context.Context)) { + ch := newChan(signal) go func() { for { select { + case <-ch: + fn(ctx) case <-ctx.Done(): return - case <-ch: - runtime.GC() } } }() } + +// newChan returns a channel triggered on every sig. +func newChan(sig os.Signal) <-chan os.Signal { + ch := make(chan os.Signal, 1) + signal.Notify(ch, sig) + return ch +}