github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/repeater/repeater.go (about) 1 package repeater 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/jonboulle/clockwork" 8 9 "github.com/ydb-platform/ydb-go-sdk/v3/internal/backoff" 10 "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" 11 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" 12 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 13 ) 14 15 type Repeater interface { 16 Stop() 17 Force() 18 } 19 20 // repeater contains logic of repeating some task. 21 type repeater struct { 22 // Interval contains an interval between task execution. 23 // Interval must be greater than zero; if not, Repeater will panic. 24 interval time.Duration 25 26 name string 27 trace *trace.Driver 28 29 // Task is a function that must be executed periodically. 30 task func(context.Context) error 31 32 cancel context.CancelFunc 33 stopped chan struct{} 34 35 force chan struct{} 36 clock clockwork.Clock 37 } 38 39 type option func(r *repeater) 40 41 func WithName(name string) option { 42 return func(r *repeater) { 43 r.name = name 44 } 45 } 46 47 func WithTrace(trace *trace.Driver) option { 48 return func(r *repeater) { 49 r.trace = trace 50 } 51 } 52 53 func WithInterval(interval time.Duration) option { 54 return func(r *repeater) { 55 r.interval = interval 56 } 57 } 58 59 func WithClock(clock clockwork.Clock) option { 60 return func(r *repeater) { 61 r.clock = clock 62 } 63 } 64 65 type Event = string 66 67 const ( 68 EventUnknown = Event("") 69 EventInit = Event("init") 70 EventTick = Event("tick") 71 EventForce = Event("force") 72 EventCancel = Event("cancel") 73 ) 74 75 type ctxEventTypeKey struct{} 76 77 func EventType(ctx context.Context) Event { 78 if eventType, ok := ctx.Value(ctxEventTypeKey{}).(Event); ok { 79 return eventType 80 } 81 82 return EventUnknown 83 } 84 85 func WithEvent(ctx context.Context, event Event) context.Context { 86 return context.WithValue(ctx, 87 ctxEventTypeKey{}, 88 event, 89 ) 90 } 91 92 // New creates and begins to execute task periodically. 93 func New( 94 ctx context.Context, 95 interval time.Duration, 96 task func(ctx context.Context) (err error), 97 opts ...option, 98 ) *repeater { 99 ctx, cancel := xcontext.WithCancel(ctx) 100 101 r := &repeater{ 102 interval: interval, 103 task: task, 104 cancel: cancel, 105 stopped: make(chan struct{}), 106 force: make(chan struct{}, 1), 107 clock: clockwork.NewRealClock(), 108 trace: &trace.Driver{}, 109 } 110 111 for _, opt := range opts { 112 if opt != nil { 113 opt(r) 114 } 115 } 116 117 go r.worker(ctx, r.clock.NewTicker(interval)) 118 119 return r 120 } 121 122 func (r *repeater) stop(onCancel func()) { 123 r.cancel() 124 if onCancel != nil { 125 onCancel() 126 } 127 <-r.stopped 128 } 129 130 // Stop stops to execute its task. 131 func (r *repeater) Stop() { 132 r.stop(nil) 133 } 134 135 func (r *repeater) Force() { 136 select { 137 case r.force <- struct{}{}: 138 default: 139 } 140 } 141 142 func (r *repeater) wakeUp(e Event) (err error) { 143 ctx := WithEvent(context.Background(), e) 144 145 onDone := trace.DriverOnRepeaterWakeUp(r.trace, &ctx, 146 stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/repeater.(*repeater).wakeUp"), 147 r.name, e, 148 ) 149 defer func() { 150 onDone(err) 151 152 if err != nil { 153 r.Force() 154 } else { 155 select { 156 case <-r.force: 157 default: 158 } 159 } 160 }() 161 162 return r.task(ctx) 163 } 164 165 func (r *repeater) worker(ctx context.Context, tick clockwork.Ticker) { 166 defer close(r.stopped) 167 defer tick.Stop() 168 169 // force returns backoff with delays [500ms...32s] 170 force := backoff.New( 171 backoff.WithSlotDuration(500*time.Millisecond), //nolint:gomnd 172 backoff.WithCeiling(6), //nolint:gomnd 173 backoff.WithJitterLimit(1), 174 ) 175 176 // forceIndex defines delay index for force backoff 177 forceIndex := 0 178 179 waitForceEvent := func() Event { 180 if forceIndex == 0 { 181 return EventForce 182 } 183 184 force := r.clock.NewTimer(force.Delay(forceIndex)) 185 defer force.Stop() 186 187 select { 188 case <-ctx.Done(): 189 return EventCancel 190 case <-tick.Chan(): 191 return EventTick 192 case <-force.Chan(): 193 return EventForce 194 } 195 } 196 197 // processEvent func checks wakeup error and returns new force index 198 processEvent := func(event Event) { 199 if event == EventCancel { 200 return 201 } 202 if err := r.wakeUp(event); err != nil { 203 forceIndex++ 204 } else { 205 forceIndex = 0 206 } 207 } 208 209 for { 210 select { 211 case <-ctx.Done(): 212 return 213 214 case <-tick.Chan(): 215 processEvent(EventTick) 216 217 case <-r.force: 218 processEvent(waitForceEvent()) 219 } 220 } 221 }