diff options
Diffstat (limited to 'vendor/codeberg.org/gruf/go-runners/run.go')
-rw-r--r-- | vendor/codeberg.org/gruf/go-runners/run.go | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-runners/run.go b/vendor/codeberg.org/gruf/go-runners/run.go new file mode 100644 index 000000000..27f7fb9b8 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-runners/run.go @@ -0,0 +1,130 @@ +package runners + +import ( + "context" + "errors" + "fmt" + "sync" + "time" +) + +// FuncRunner provides a means of managing long-running functions e.g. main logic loops. +type FuncRunner struct { + // HandOff is the time after which a blocking function will be considered handed off + HandOff time.Duration + + // ErrorHandler is the function that errors are passed to when encountered by the + // provided function. This can be used both for logging, and for error filtering + ErrorHandler func(err error) error + + svc Service // underlying service to manage start/stop + err error // last-set error + mu sync.Mutex // protects err +} + +// Go will attempt to run 'fn' asynchronously. The provided context is used to propagate requested +// cancel if FuncRunner.Stop() is called. Any returned error will be passed to FuncRunner.ErrorHandler +// for filtering/logging/etc. Any blocking functions will be waited on for FuncRunner.HandOff amount of +// time before considering the function as handed off. Returned bool is success state, i.e. returns true +// if function is successfully handed off or returns within hand off time with nil error. +func (r *FuncRunner) Go(fn func(ctx context.Context) error) bool { + done := make(chan struct{}) + + go func() { + var cancelled bool + + has := r.svc.Run(func(ctx context.Context) { + // reset error + r.mu.Lock() + r.err = nil + r.mu.Unlock() + + // Run supplied func and set errror if returned + if err := Run(func() error { return fn(ctx) }); err != nil { + r.mu.Lock() + r.err = err + r.mu.Unlock() + } + + // signal done + close(done) + + // Check if cancelled + select { + case <-ctx.Done(): + cancelled = true + default: + cancelled = false + } + }) + + switch has { + // returned after starting + case true: + r.mu.Lock() + + // filter out errors due FuncRunner.Stop() being called + if cancelled && errors.Is(r.err, context.Canceled) { + // filter out errors from FuncRunner.Stop() being called + r.err = nil + } else if r.err != nil && r.ErrorHandler != nil { + // pass any non-nil error to set handler + r.err = r.ErrorHandler(r.err) + } + + r.mu.Unlock() + + // already running + case false: + close(done) + } + }() + + // get valid handoff to use + handoff := r.HandOff + if handoff < 1 { + handoff = time.Second * 5 + } + + select { + // handed off (long-run successful) + case <-time.After(handoff): + return true + + // 'fn' returned, check error + case <-done: + return (r.Err() == nil) + } +} + +// Stop will cancel the context supplied to the running function. +func (r *FuncRunner) Stop() bool { + return r.svc.Stop() +} + +// Err returns the last-set error value. +func (r *FuncRunner) Err() error { + r.mu.Lock() + err := r.err + r.mu.Unlock() + return err +} + +// Run will execute the supplied 'fn' catching any panics. Returns either function-returned error or formatted panic. +func Run(fn func() error) (err error) { + defer func() { + if r := recover(); r != nil { + if e, ok := r.(error); ok { + // wrap and preserve existing error + err = fmt.Errorf("caught panic: %w", e) + } else { + // simply create new error fromt iface + err = fmt.Errorf("caught panic: %v", r) + } + } + }() + + // run supplied func + err = fn() + return +} |