github.com/blend/go-sdk@v1.20240719.1/async/interval.go (about) 1 /* 2 3 Copyright (c) 2024 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package async 9 10 import ( 11 "context" 12 "time" 13 14 "github.com/blend/go-sdk/ex" 15 ) 16 17 /* 18 NewInterval returns a new worker that runs an action on an interval. 19 20 Example: 21 22 iw := NewInterval(func(ctx context.Context) error { return nil }, 500*time.Millisecond) 23 go iw.Start() 24 <-iw.Started() 25 */ 26 func NewInterval(action ContextAction, interval time.Duration, options ...IntervalOption) *Interval { 27 i := Interval{ 28 Latch: NewLatch(), 29 Action: action, 30 Context: context.Background(), 31 Interval: interval, 32 } 33 for _, option := range options { 34 option(&i) 35 } 36 return &i 37 } 38 39 // IntervalOption is an option for the interval worker. 40 type IntervalOption func(*Interval) 41 42 // OptIntervalDelay sets the interval worker start delay. 43 func OptIntervalDelay(d time.Duration) IntervalOption { 44 return func(i *Interval) { 45 i.Delay = d 46 } 47 } 48 49 // OptIntervalContext sets the interval worker context. 50 func OptIntervalContext(ctx context.Context) IntervalOption { 51 return func(i *Interval) { 52 i.Context = ctx 53 } 54 } 55 56 // OptIntervalStopOnError sets if the interval worker should stop on action error. 57 func OptIntervalStopOnError(stopOnError bool) IntervalOption { 58 return func(i *Interval) { 59 i.StopOnError = stopOnError 60 } 61 } 62 63 // OptIntervalErrors sets the interval worker start error channel. 64 func OptIntervalErrors(errors chan error) IntervalOption { 65 return func(i *Interval) { 66 i.Errors = errors 67 } 68 } 69 70 // Interval is a background worker that performs an action on an interval. 71 type Interval struct { 72 *Latch 73 Context context.Context 74 Interval time.Duration 75 Action ContextAction 76 Delay time.Duration 77 StopOnError bool 78 Errors chan error 79 } 80 81 /* 82 Start starts the worker. 83 84 This will start the internal ticker, with a default initial delay of the given interval, and will return an ErrCannotStart if the interval worker is already started. 85 86 This call will block. 87 */ 88 func (i *Interval) Start() error { 89 if !i.CanStart() { 90 return ex.New(ErrCannotStart) 91 } 92 i.Starting() 93 return i.Dispatch() 94 } 95 96 // Stop stops the worker. 97 func (i *Interval) Stop() error { 98 if !i.CanStop() { 99 return ex.New(ErrCannotStop) 100 } 101 i.Stopping() 102 <-i.NotifyStopped() 103 i.Latch.Reset() // reset the latch in case we have to start again 104 return nil 105 } 106 107 // Dispatch is the main dispatch loop. 108 func (i *Interval) Dispatch() (err error) { 109 i.Started() 110 111 if i.Delay > 0 { 112 time.Sleep(i.Delay) 113 } 114 115 tick := time.NewTicker(i.Interval) 116 defer func() { 117 tick.Stop() 118 i.Stopped() 119 }() 120 121 var stopping <-chan struct{} 122 for { 123 stopping = i.NotifyStopping() 124 // check stopping conditions first 125 select { 126 case <-i.Context.Done(): 127 return 128 case <-stopping: 129 return 130 default: 131 } 132 133 select { 134 case <-tick.C: 135 err = i.Action(context.Background()) 136 if err != nil { 137 if i.StopOnError { 138 return 139 } 140 if i.Errors != nil { 141 i.Errors <- err 142 } 143 } 144 case <-i.Context.Done(): 145 return 146 case <-stopping: 147 return 148 } 149 } 150 }