package via import ( "sync" "sync/atomic" "time" ) // Routine allows for defining concurrent goroutines safely. Goroutines started by *Routine // are tied to the *Context lifecycle. type Routine struct { mu sync.RWMutex ctxDisposed chan struct{} localInterrupt chan struct{} isRunning atomic.Bool routineFn func() tckDuration time.Duration updateTkrChan chan time.Duration } // OnInterval starts a go routine that sets a time.Ticker with the given duration and executes // the given func() on every tick. Use *Routine.UpdateInterval to update the interval. // If the routine is running, it is stopped. func (r *Routine) OnInterval(d time.Duration, fn func()) { if r.isRunning.Load() == true { r.Stop() } r.tckDuration = d r.routineFn = func() { r.mu.RLock() tkr := time.NewTicker(r.tckDuration) r.mu.RUnlock() defer tkr.Stop() // clean up the ticker when routine stops for { select { case <-r.ctxDisposed: // dispose of the routine when ctx is disposed return case <-r.localInterrupt: // dispose of the routine on interrupt signal return case d := <-r.updateTkrChan: tkr.Reset(d) case <-tkr.C: fn() } } } } // UpdateInterval sets a new interval duration for the internal *time.Ticker. If the provided // duration is equal of less than 0, UpdateInterval does nothing. func (r *Routine) UpdateInterval(d time.Duration) { r.mu.Lock() defer r.mu.Unlock() r.tckDuration = d r.updateTkrChan <- d } // Start executes the predifined goroutine. If no predifined goroutine exists, or it already // started, Start does nothing. func (r *Routine) Start() { if !r.isRunning.CompareAndSwap(false, true) || r.routineFn == nil { return } go r.routineFn() } // Stop interrupts the predifined goroutine. If no predifined goroutine exists, or it already // ustopped, Stop does nothing. func (r *Routine) Stop() { if !r.isRunning.CompareAndSwap(true, false) || r.routineFn == nil { return } r.localInterrupt <- struct{}{} } func newRoutine(ctxDisposedChan chan struct{}) *Routine { return &Routine{ ctxDisposed: ctxDisposedChan, localInterrupt: make(chan struct{}), updateTkrChan: make(chan time.Duration), } }