summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-runners/service.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/codeberg.org/gruf/go-runners/service.go')
-rw-r--r--vendor/codeberg.org/gruf/go-runners/service.go159
1 files changed, 159 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-runners/service.go b/vendor/codeberg.org/gruf/go-runners/service.go
new file mode 100644
index 000000000..c0f878c45
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-runners/service.go
@@ -0,0 +1,159 @@
+package runners
+
+import (
+ "context"
+ "sync"
+)
+
+// Service provides a means of tracking a single long-running service, provided protected state
+// changes and preventing multiple instances running. Also providing service state information.
+type Service struct {
+ state uint32 // 0=stopped, 1=running, 2=stopping
+ wait sync.Mutex // wait is the mutex used as a single-entity wait-group, i.e. just a "wait" :p
+ cncl context.CancelFunc // cncl is the cancel function set for the current context
+ ctx context.Context // ctx is the current context for running function (or nil if not running)
+ mu sync.Mutex // mu protects state changes
+}
+
+// Run will run the supplied function until completion, use given context to propagate cancel.
+// Immediately returns false if the Service is already running, and true after completed run.
+func (svc *Service) Run(fn func(context.Context)) bool {
+ // Attempt to start the svc
+ ctx, ok := svc.doStart()
+ if !ok {
+ return false
+ }
+
+ defer func() {
+ // unlock single wait
+ svc.wait.Unlock()
+
+ // ensure stopped
+ svc.Stop()
+ }()
+
+ // Run user func
+ if fn != nil {
+ fn(ctx)
+ }
+ return true
+}
+
+// Stop will attempt to stop the service, cancelling the running function's context. Immediately
+// returns false if not running, and true only after Service is fully stopped.
+func (svc *Service) Stop() bool {
+ // Attempt to stop the svc
+ cncl, ok := svc.doStop()
+ if !ok {
+ return false
+ }
+
+ defer func() {
+ // Get svc lock
+ svc.mu.Lock()
+
+ // Wait until stopped
+ svc.wait.Lock()
+ svc.wait.Unlock()
+
+ // Reset the svc
+ svc.ctx = nil
+ svc.cncl = nil
+ svc.state = 0
+ svc.mu.Unlock()
+ }()
+
+ cncl() // cancel ctx
+ return true
+}
+
+// doStart will safely set Service state to started, returning a ptr to this context insance.
+func (svc *Service) doStart() (context.Context, bool) {
+ // Protect startup
+ svc.mu.Lock()
+
+ if svc.state != 0 /* not stopped */ {
+ svc.mu.Unlock()
+ return nil, false
+ }
+
+ // state started
+ svc.state = 1
+
+ // Take our own ptr
+ var ctx context.Context
+
+ if svc.ctx == nil {
+ // Context required allocating
+ svc.ctx, svc.cncl = ContextWithCancel()
+ }
+
+ // Start the waiter
+ svc.wait.Lock()
+
+ // Set our ptr + unlock
+ ctx = svc.ctx
+ svc.mu.Unlock()
+
+ return ctx, true
+}
+
+// doStop will safely set Service state to stopping, returning a ptr to this cancelfunc instance.
+func (svc *Service) doStop() (context.CancelFunc, bool) {
+ // Protect stop
+ svc.mu.Lock()
+
+ if svc.state != 1 /* not started */ {
+ svc.mu.Unlock()
+ return nil, false
+ }
+
+ // state stopping
+ svc.state = 2
+
+ // Take our own ptr
+ // and unlock state
+ cncl := svc.cncl
+ svc.mu.Unlock()
+
+ return cncl, true
+}
+
+// Running returns if Service is running (i.e. state NOT stopped / stopping).
+func (svc *Service) Running() bool {
+ svc.mu.Lock()
+ state := svc.state
+ svc.mu.Unlock()
+ return (state == 1)
+}
+
+// Done returns a channel that's closed when Service.Stop() is called. It is
+// the same channel provided to the currently running service function.
+func (svc *Service) Done() <-chan struct{} {
+ var done <-chan struct{}
+
+ svc.mu.Lock()
+ switch svc.state {
+ // stopped
+ // (here we create a new context so that the
+ // returned 'done' channel here will still
+ // be valid for when Service is next started)
+ case 0:
+ if svc.ctx == nil {
+ // need to allocate new context
+ svc.ctx, svc.cncl = ContextWithCancel()
+ }
+ done = svc.ctx.Done()
+
+ // started
+ case 1:
+ done = svc.ctx.Done()
+
+ // stopping
+ case 2:
+ done = svc.ctx.Done()
+ }
+ svc.mu.Unlock()
+
+ return done
+}