github.com/blend/go-sdk@v1.20220411.3/async/interval.go (about) 1 /* 2 3 Copyright (c) 2022 - 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 27 */ 28 func NewInterval(action ContextAction, interval time.Duration, options ...IntervalOption) *Interval { 29 i := Interval{ 30 Latch: NewLatch(), 31 Action: action, 32 Context: context.Background(), 33 Interval: interval, 34 } 35 for _, option := range options { 36 option(&i) 37 } 38 return &i 39 } 40 41 // IntervalOption is an option for the interval worker. 42 type IntervalOption func(*Interval) 43 44 // OptIntervalDelay sets the interval worker start delay. 45 func OptIntervalDelay(d time.Duration) IntervalOption { 46 return func(i *Interval) { 47 i.Delay = d 48 } 49 } 50 51 // OptIntervalContext sets the interval worker context. 52 func OptIntervalContext(ctx context.Context) IntervalOption { 53 return func(i *Interval) { 54 i.Context = ctx 55 } 56 } 57 58 // OptIntervalStopOnError sets if the interval worker should stop on action error. 59 func OptIntervalStopOnError(stopOnError bool) IntervalOption { 60 return func(i *Interval) { 61 i.StopOnError = stopOnError 62 } 63 } 64 65 // OptIntervalErrors sets the interval worker start error channel. 66 func OptIntervalErrors(errors chan error) IntervalOption { 67 return func(i *Interval) { 68 i.Errors = errors 69 } 70 } 71 72 // Interval is a background worker that performs an action on an interval. 73 type Interval struct { 74 *Latch 75 Context context.Context 76 Interval time.Duration 77 Action ContextAction 78 Delay time.Duration 79 StopOnError bool 80 Errors chan error 81 } 82 83 /* 84 Start starts the worker. 85 86 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. 87 88 This call will block. 89 */ 90 func (i *Interval) Start() error { 91 if !i.CanStart() { 92 return ex.New(ErrCannotStart) 93 } 94 i.Starting() 95 return i.Dispatch() 96 } 97 98 // Stop stops the worker. 99 func (i *Interval) Stop() error { 100 if !i.CanStop() { 101 return ex.New(ErrCannotStop) 102 } 103 i.Stopping() 104 <-i.NotifyStopped() 105 i.Latch.Reset() // reset the latch in case we have to start again 106 return nil 107 } 108 109 // Dispatch is the main dispatch loop. 110 func (i *Interval) Dispatch() (err error) { 111 i.Started() 112 113 if i.Delay > 0 { 114 time.Sleep(i.Delay) 115 } 116 117 tick := time.NewTicker(i.Interval) 118 defer func() { 119 tick.Stop() 120 i.Stopped() 121 }() 122 123 var stopping <-chan struct{} 124 for { 125 stopping = i.NotifyStopping() 126 // check stopping conditions first 127 select { 128 case <-i.Context.Done(): 129 return 130 case <-stopping: 131 return 132 default: 133 } 134 135 select { 136 case <-tick.C: 137 err = i.Action(context.Background()) 138 if err != nil { 139 if i.StopOnError { 140 return 141 } 142 if i.Errors != nil { 143 i.Errors <- err 144 } 145 } 146 case <-i.Context.Done(): 147 return 148 case <-stopping: 149 return 150 } 151 } 152 }