gitee.com/h79/goutils@v1.22.10/common/scheduler/scheduler_group.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"gitee.com/h79/goutils/common/logger"
     5  	"gitee.com/h79/goutils/common/queue"
     6  	"gitee.com/h79/goutils/common/system"
     7  	"sync"
     8  	"time"
     9  )
    10  
    11  var _ queue.IPriority = (*scheduledItem)(nil)
    12  
    13  type scheduledItem struct {
    14  	Trigger  Trigger
    15  	Job      Task
    16  	nextTime int64
    17  }
    18  
    19  // GValue for implement queue.IPriority
    20  func (it *scheduledItem) GValue() interface{} {
    21  	return it
    22  }
    23  
    24  // PValue for implement queue.IPriority
    25  func (it *scheduledItem) PValue() int64 {
    26  	return it.nextTime
    27  }
    28  
    29  type groupScheduled struct {
    30  	rm              sync.Mutex
    31  	items           *queue.Capacity
    32  	tm              *time.Ticker
    33  	uniques         map[string]*scheduledItem
    34  	feederTaskCheck system.RunningCheck
    35  	execTaskCheck   system.RunningCheck
    36  	duration        time.Duration
    37  	minDuration     time.Duration
    38  	execTime        time.Duration
    39  	location        *time.Location
    40  	feeder          chan *scheduledItem
    41  	interrupt       chan struct{}
    42  	index           int
    43  	logEnabled      bool
    44  }
    45  
    46  const feederCount = 3
    47  const defTaskDuration = time.Microsecond * 500
    48  
    49  func newGroupScheduled(idx int, capacity int, location *time.Location, minDuration, duration time.Duration, logEnabled bool) *groupScheduled {
    50  	if duration < defTaskDuration {
    51  		duration = defTaskDuration
    52  	}
    53  	if minDuration < defTaskDuration {
    54  		minDuration = defTaskDuration
    55  	}
    56  	tm := time.NewTicker(duration)
    57  	return &groupScheduled{
    58  		items:       queue.NewCapacity(capacity),
    59  		feeder:      make(chan *scheduledItem, feederCount*2),
    60  		interrupt:   make(chan struct{}, feederCount*10),
    61  		location:    location,
    62  		duration:    duration,
    63  		minDuration: minDuration,
    64  		tm:          tm,
    65  		index:       idx,
    66  		logEnabled:  logEnabled,
    67  		uniques:     map[string]*scheduledItem{},
    68  	}
    69  }
    70  
    71  func (gs *groupScheduled) AddTask(task Task, uniqueEnabled bool, generateFn GenerateOrInitFunc) error {
    72  	return gs.AddTriggerTask(task, NewSimpleTrigger(time.Second*5), uniqueEnabled, generateFn)
    73  }
    74  
    75  func (gs *groupScheduled) AddTriggerTask(task Task, trigger Trigger, uniqueEnabled bool, generateFn GenerateOrInitFunc) error {
    76  	if system.IsQuit() {
    77  		logger.W("Task", "application is quited,not can add for jobId= %s", task.GetId())
    78  		return system.ClosedError
    79  	}
    80  	err := gs.add(task, trigger, uniqueEnabled, generateFn)
    81  	if err == nil {
    82  		gs.Run()
    83  		gs.resetTaskTime()
    84  	}
    85  	return err
    86  }
    87  
    88  func defaultUpdateFn(_ int, _ Task) (Task, Trigger, bool) {
    89  	return nil, nil, true
    90  }
    91  
    92  func (gs *groupScheduled) UpdateTask(jobId string, uniqueEnabled bool, notExistNewFlag bool, generateFn GenerateOrInitFunc) bool {
    93  	ret := gs.update(jobId, uniqueEnabled, notExistNewFlag, generateFn)
    94  	if ret && notExistNewFlag {
    95  		gs.Run()
    96  		gs.resetTaskTime()
    97  	}
    98  	return ret
    99  }
   100  
   101  func (gs *groupScheduled) add(task Task, trigger Trigger, uniqueEnabled bool, generateFn GenerateOrInitFunc) error {
   102  	gs.rm.Lock()
   103  	defer gs.rm.Unlock()
   104  	var item *scheduledItem
   105  	if uniqueEnabled {
   106  		if it, ok := gs.uniques[task.GetId()]; ok {
   107  			item = it
   108  		}
   109  		if item != nil {
   110  			if generateFn != nil {
   111  				generateFn(gs.index, item.Job)
   112  			}
   113  			return ErrJobExist
   114  		}
   115  		//go to add
   116  	}
   117  	nt, err := trigger.NextFireTime(time.Now().In(gs.location).UnixNano())
   118  	if err != nil {
   119  		return err
   120  	}
   121  	item = &scheduledItem{Trigger: trigger, Job: task, nextTime: nt}
   122  	err = gs.items.Add(item)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	if uniqueEnabled {
   127  		gs.uniques[task.GetId()] = item
   128  	}
   129  	if generateFn != nil {
   130  		generateFn(gs.index, task)
   131  	}
   132  	return nil
   133  }
   134  
   135  func (gs *groupScheduled) update(jobId string, uniqueEnabled bool, notExistNewFlag bool, generateFn GenerateOrInitFunc) bool {
   136  	gs.rm.Lock()
   137  	defer gs.rm.Unlock()
   138  	var item *scheduledItem
   139  	if uniqueEnabled {
   140  		if it, ok := gs.uniques[jobId]; ok {
   141  			item = it
   142  		}
   143  	} else {
   144  		item = gs.find(jobId)
   145  	}
   146  	if item != nil {
   147  		if generateFn != nil {
   148  			_, _, ok := generateFn(gs.index, item.Job)
   149  			return ok
   150  		}
   151  		return true
   152  	}
   153  	if notExistNewFlag && generateFn != nil {
   154  		tk, trigger, _ := generateFn(gs.index, nil)
   155  		if tk == nil || trigger == nil {
   156  			return false
   157  		}
   158  		nt, err := trigger.NextFireTime(time.Now().In(gs.location).UnixNano())
   159  		if err != nil {
   160  			return false
   161  		}
   162  		item = &scheduledItem{Trigger: trigger, Job: tk, nextTime: nt}
   163  		err = gs.items.Add(item)
   164  		if err != nil {
   165  			return false
   166  		}
   167  		if uniqueEnabled {
   168  			gs.uniques[tk.GetId()] = item
   169  		}
   170  		_, _, ret := generateFn(gs.index, tk)
   171  		return ret
   172  	}
   173  	return false
   174  }
   175  
   176  func (gs *groupScheduled) ForeachTask(update func(group int, task Task, time time.Duration, now, trigger int64) bool) {
   177  	gs.rm.Lock()
   178  	defer gs.rm.Unlock()
   179  	now := time.Now().In(gs.location).UnixNano()
   180  	gs.items.Foreach(func(v any, index int) bool {
   181  		if item, ok := v.(*scheduledItem); ok {
   182  			return update(gs.index, item.Job, gs.execTime, now, item.nextTime)
   183  		}
   184  		return false
   185  	})
   186  }
   187  
   188  func (gs *groupScheduled) StopTask(jobId string) {
   189  	gs.rm.Lock()
   190  	defer gs.rm.Unlock()
   191  	if task := gs.find(jobId); task != nil {
   192  		task.Job.Cancel()
   193  	}
   194  }
   195  
   196  func (gs *groupScheduled) RemoveTask(jobId string) {
   197  	gs.rm.Lock()
   198  	defer gs.rm.Unlock()
   199  	if task := gs.find(jobId); task != nil {
   200  		task.Job.Cancel()
   201  	}
   202  }
   203  
   204  func (gs *groupScheduled) HasTask(jobId string) bool {
   205  	gs.rm.Lock()
   206  	defer gs.rm.Unlock()
   207  	return gs.find(jobId) != nil
   208  }
   209  
   210  func (gs *groupScheduled) Run() {
   211  	if system.IsQuit() {
   212  		return
   213  	}
   214  	gs.execTaskCheck.GoRunning(gs.execTask)
   215  	gs.feederTaskCheck.GoRunning(gs.runFeeder)
   216  }
   217  
   218  func (gs *groupScheduled) find(jobId string) *scheduledItem {
   219  	job, idx := gs.items.Find(func(v interface{}, index int) bool {
   220  		if item, ok := v.(*scheduledItem); ok {
   221  			return item.Job.GetId() == jobId
   222  		}
   223  		return false
   224  	})
   225  	if idx == -1 {
   226  		return nil
   227  	}
   228  	return job.(*scheduledItem)
   229  }
   230  
   231  func (gs *groupScheduled) peek() *scheduledItem {
   232  	gs.rm.Lock()
   233  	defer gs.rm.Unlock()
   234  	var v = gs.items.Peek()
   235  	item, ok := v.(*scheduledItem)
   236  	if !ok {
   237  		return nil
   238  	}
   239  	return item
   240  }
   241  
   242  func (gs *groupScheduled) calculateNextTime() time.Duration {
   243  	job := gs.peek()
   244  	if job != nil {
   245  		now := time.Now().In(gs.location).UnixNano()
   246  		if job.nextTime >= now {
   247  			return time.Duration(job.nextTime - now)
   248  		}
   249  		return 0
   250  	}
   251  	return gs.duration
   252  }
   253  
   254  func (gs *groupScheduled) resetTaskTime() {
   255  	duration := gs.calculateNextTime()
   256  	if duration < gs.minDuration {
   257  		duration = gs.minDuration
   258  	}
   259  	if gs.execTime == duration {
   260  		return
   261  	}
   262  	gs.execTime = duration
   263  	gs.tm.Reset(duration)
   264  	if gs.logEnabled {
   265  		logger.N("Schedule", "next run time= %d in group= %d", duration, gs.index)
   266  	}
   267  }
   268  
   269  func (gs *groupScheduled) execTask() {
   270  	defer gs.tm.Stop()
   271  	for {
   272  		gs.resetTaskTime()
   273  
   274  		select {
   275  		case <-system.Closed():
   276  			gs.quited()
   277  			return
   278  
   279  		case <-gs.tm.C:
   280  			for i := 0; i < feederCount-1; i++ {
   281  				gs.runTask()
   282  			}
   283  
   284  		case <-gs.interrupt:
   285  			break
   286  		}
   287  	}
   288  }
   289  
   290  func (gs *groupScheduled) quited() {
   291  	var opts = With(2)
   292  	for i := 0; i < len(gs.feeder); i++ {
   293  		item, ok := <-gs.feeder
   294  		if !ok {
   295  			return
   296  		}
   297  		_, _ = item.Job.Execute(opts...)
   298  	}
   299  	gs.rm.Lock()
   300  	defer gs.rm.Unlock()
   301  	for {
   302  		var v = gs.items.Pop()
   303  		var item, ok = v.(*scheduledItem)
   304  		if !ok {
   305  			break
   306  		}
   307  		_, _ = item.Job.Execute(opts...)
   308  	}
   309  }
   310  
   311  func (gs *groupScheduled) addTask(item *scheduledItem) error {
   312  	gs.rm.Lock()
   313  	defer gs.rm.Unlock()
   314  	now := time.Now().In(gs.location).UnixNano()
   315  	nextTime, err := item.Trigger.NextFireTime(now)
   316  	if err != nil {
   317  		return err
   318  	}
   319  	if now-item.nextTime > (time.Second * 5).Nanoseconds() {
   320  		if gs.logEnabled {
   321  			logger.W("Schedule", "the task is block too long, jobId= '%s' in group=%d", item.Job.GetId(), gs.index)
   322  		}
   323  	}
   324  	item.nextTime = nextTime
   325  	return gs.items.Add(item)
   326  }
   327  
   328  func (gs *groupScheduled) runFeeder() {
   329  	for {
   330  		select {
   331  		case item := <-gs.feeder:
   332  			err := gs.addTask(item)
   333  			if err != nil {
   334  				gs.removeUnique(item.Job.GetId())
   335  				logger.E("Schedule", "add task failure for feeder, the task lost, jobId= '%s' in group=%d, err= %s", item.Job.GetId(), gs.index, err)
   336  				break
   337  			}
   338  			gs.interrupt <- struct{}{}
   339  
   340  		case <-system.Closed():
   341  			return
   342  		}
   343  	}
   344  }
   345  
   346  func (gs *groupScheduled) pop(now int64) (*scheduledItem, bool) {
   347  	gs.rm.Lock()
   348  	defer gs.rm.Unlock()
   349  
   350  	var v = gs.items.Peek()
   351  	var item, ok = v.(*scheduledItem)
   352  	if !ok {
   353  		return nil, false
   354  	}
   355  	if gs.logEnabled {
   356  		logger.N("Schedule", "pop the task,jobId= '%s' in group= %d, time= (%d,%d), ex= %v", item.Job.GetId(), gs.index, item.nextTime, now, item.nextTime <= now)
   357  	}
   358  	if item.nextTime > now {
   359  		return nil, false
   360  	}
   361  	var it = gs.items.Pop()
   362  	if it != item {
   363  		panic("the value not equal")
   364  	}
   365  	return item, true
   366  }
   367  
   368  func (gs *groupScheduled) runTask() bool {
   369  	// 没到时间,不用执行
   370  	now := time.Now().In(gs.location).UnixNano()
   371  	item, ok := gs.pop(now)
   372  	if !ok {
   373  		return false
   374  	}
   375  	_, err := item.Job.Execute()
   376  	if err != nil {
   377  		gs.removeUnique(item.Job.GetId())
   378  		if gs.logEnabled {
   379  			logger.E("Schedule", "the task execute error, jobId= '%s' in group=%d, err= %s", item.Job.GetId(), gs.index, err)
   380  		}
   381  		return true
   382  	}
   383  	if item.Job.GetState().IsQuit() {
   384  		gs.removeUnique(item.Job.GetId())
   385  		if gs.logEnabled {
   386  			logger.E("Schedule", "the task quited, state= '%s', jobId= '%s' in group= %d", item.Job.GetState(), item.Job.GetId(), gs.index)
   387  		}
   388  		return true
   389  	}
   390  	if system.IsQuit() {
   391  		gs.removeUnique(item.Job.GetId())
   392  		if gs.logEnabled {
   393  			logger.E("Schedule", "system quited, state= '%s', jobId= '%s' in group= %d", item.Job.GetState(), item.Job.GetId(), gs.index)
   394  		}
   395  		return false
   396  	}
   397  	item.nextTime = now
   398  	gs.feeder <- item
   399  	return true
   400  }
   401  
   402  func (gs *groupScheduled) removeUnique(jobId string) {
   403  	gs.rm.Lock()
   404  	defer gs.rm.Unlock()
   405  	delete(gs.uniques, jobId)
   406  }