github.com/MeteorsLiu/simpleMQ@v1.0.3/queue/task.go (about) 1 package queue 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "sync/atomic" 8 "time" 9 10 "github.com/google/uuid" 11 ) 12 13 const ( 14 DefaultRetryLimit = 5 15 ) 16 17 var ( 18 ErrTaskStopped = fmt.Errorf("task is stopped") 19 ErrRetryReachLimits = fmt.Errorf("retry reaches limits") 20 ErrFailReachLimits = fmt.Errorf("fails reaches limits") 21 ) 22 23 type Task interface { 24 Do() error 25 Error() error 26 TaskError() error 27 ID() string 28 Stop() 29 Interrupt() 30 IsDone() bool 31 // The action function when tasks is stop or interrputed. 32 OnDone(...Finalizer) 33 // The action function when tasks run fail. 34 OnFail(...Failure) 35 Wait() 36 String() string 37 IsRunUntilSuccess() bool 38 IsReachLimits() bool 39 SetTaskError(error) 40 TaskContext() *sync.Map 41 } 42 43 type TaskOptions func(*TaskEntry) 44 type TaskFunc func() error 45 type RetryFunc func(*TaskEntry) error 46 type Finalizer func(ok bool, task Task) 47 type Failure func(fail int, task Task) 48 type TaskEntry struct { 49 id string 50 task TaskFunc 51 taskErr error 52 taskCtx sync.Map 53 retryFunc RetryFunc 54 retryLimit int 55 fails atomic.Int32 56 failLimit int32 57 stop context.Context 58 doStop context.CancelFunc 59 stopOnce sync.Once 60 onTaskDone []Finalizer 61 onTaskFail []Failure 62 running sync.Mutex 63 runUntilSuccess bool 64 requireLock bool 65 } 66 67 func DefaultRetry() RetryFunc { 68 return func(tf *TaskEntry) error { 69 sleep := time.NewTicker(time.Second) 70 defer sleep.Stop() 71 var err error 72 for i := 0; i < tf.retryLimit; i++ { 73 // may be woken up by stop signal or the ticker. 74 select { 75 case <-tf.stop.Done(): 76 return ErrTaskStopped 77 case <-sleep.C: 78 } 79 80 if err := tf.task(); err == nil { 81 return nil 82 } 83 84 // max retry sleep: 85 // first time: 1 seconds 86 // and then 2 seconds, 4 seconds, 8 seconds, 16 seconds, 32 seconds. 87 // Totally, 1+2+4+8+16+32=63 seconds = 1 Minute 3 Second 88 sleep.Reset(1 << (i + 1) * time.Second) 89 } 90 tf.taskErr = err 91 return ErrRetryReachLimits 92 } 93 } 94 95 // fail fast. 96 // which promises the task will run once. 97 func WithNoRetryFunc() TaskOptions { 98 return func(te *TaskEntry) { 99 te.failLimit = 1 100 te.runUntilSuccess = false 101 te.retryFunc = func(te *TaskEntry) error { 102 if te.taskErr != nil { 103 te.Stop() 104 } 105 return te.taskErr 106 } 107 } 108 } 109 110 func WithRetryFunc(retry RetryFunc) TaskOptions { 111 return func(te *TaskEntry) { 112 te.retryFunc = retry 113 } 114 } 115 116 func LockRequired() TaskOptions { 117 return func(te *TaskEntry) { 118 te.requireLock = true 119 } 120 } 121 122 // when the custom RetryFunc is set, 123 // the retry limit will be ignored. 124 func WithRetryLimit(retry int) TaskOptions { 125 return func(te *TaskEntry) { 126 te.retryLimit = retry 127 } 128 } 129 130 func WithContext(ctx context.Context) TaskOptions { 131 return func(te *TaskEntry) { 132 te.stop, te.doStop = context.WithCancel(ctx) 133 } 134 } 135 136 func WithTaskID(id string) TaskOptions { 137 return func(te *TaskEntry) { 138 te.id = id 139 } 140 } 141 142 func WithRunUntilSuccess(RunUntilSuccess bool) TaskOptions { 143 return func(te *TaskEntry) { 144 te.runUntilSuccess = RunUntilSuccess 145 } 146 } 147 148 func WithOnTaskDone(f Finalizer) TaskOptions { 149 return func(te *TaskEntry) { 150 te.onTaskDone = append(te.onTaskDone, f) 151 } 152 } 153 154 func WithOnTaskFail(f Failure) TaskOptions { 155 return func(te *TaskEntry) { 156 te.onTaskFail = append(te.onTaskFail, f) 157 } 158 } 159 160 func WithFailLimits(limits int) TaskOptions { 161 return func(te *TaskEntry) { 162 if te.failLimit == DefaultRetryLimit { 163 te.failLimit = int32(limits) 164 } 165 } 166 } 167 168 func NewTask(task TaskFunc, opts ...TaskOptions) Task { 169 t := &TaskEntry{ 170 task: task, 171 runUntilSuccess: true, 172 retryLimit: DefaultRetryLimit, 173 retryFunc: DefaultRetry(), 174 failLimit: DefaultRetryLimit, 175 } 176 177 for _, o := range opts { 178 o(t) 179 } 180 if t.stop == nil || t.doStop == nil { 181 t.stop, t.doStop = context.WithCancel(context.Background()) 182 } 183 if t.id == "" { 184 t.id = uuid.NewString() 185 } 186 187 return t 188 } 189 190 func (t *TaskEntry) Do() error { 191 select { 192 case <-t.stop.Done(): 193 return ErrTaskStopped 194 default: 195 currentCnt := t.fails.Add(1) 196 if currentCnt > t.failLimit { 197 return ErrFailReachLimits 198 } 199 // most case it wounldn't need 200 if t.requireLock { 201 t.running.Lock() 202 defer t.running.Unlock() 203 } 204 if err := t.task(); err != nil { 205 // saved the error for the fail fast case. 206 t.taskErr = err 207 if err = t.retryFunc(t); err != nil { 208 for _, fail := range t.onTaskFail { 209 fail(int(currentCnt), t) 210 } 211 return err 212 } 213 } 214 } 215 t.Stop() 216 return nil 217 } 218 func (t *TaskEntry) Stop() { 219 // prevent the stop race. 220 t.stopOnce.Do(func() { 221 t.doStop() 222 for _, f := range t.onTaskDone { 223 f(true, t) 224 } 225 }) 226 227 } 228 229 func (t *TaskEntry) Interrupt() { 230 t.stopOnce.Do(func() { 231 t.doStop() 232 for _, f := range t.onTaskDone { 233 f(false, t) 234 } 235 }) 236 } 237 238 func (t *TaskEntry) SetTaskError(err error) { 239 t.taskCtx.Store("err", err) 240 } 241 242 func (t *TaskEntry) TaskError() error { 243 if err, ok := t.taskCtx.Load("err"); ok { 244 return err.(error) 245 } 246 return nil 247 } 248 249 func (t *TaskEntry) TaskContext() *sync.Map { 250 return &t.taskCtx 251 } 252 253 func (t *TaskEntry) ID() string { 254 return t.id 255 } 256 257 func (t *TaskEntry) IsRunUntilSuccess() bool { 258 return t.runUntilSuccess 259 } 260 261 func (t *TaskEntry) IsDone() bool { 262 select { 263 case <-t.stop.Done(): 264 return true 265 default: 266 return false 267 } 268 } 269 270 func (t *TaskEntry) Error() error { 271 return t.taskErr 272 } 273 274 func (t *TaskEntry) OnDone(f ...Finalizer) { 275 t.onTaskDone = append(t.onTaskDone, f...) 276 } 277 278 func (t *TaskEntry) OnFail(f ...Failure) { 279 t.onTaskFail = append(t.onTaskFail, f...) 280 } 281 282 func (t *TaskEntry) Wait() { 283 <-t.stop.Done() 284 } 285 286 func (t *TaskEntry) String() string { 287 return t.ID() 288 } 289 290 func (t *TaskEntry) IsReachLimits() bool { 291 return t.fails.Load() >= t.failLimit 292 }