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  }