periodicalexecutor.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. package executors
  2. import (
  3. "reflect"
  4. "sync"
  5. "sync/atomic"
  6. "time"
  7. "github.com/tal-tech/go-zero/core/lang"
  8. "github.com/tal-tech/go-zero/core/proc"
  9. "github.com/tal-tech/go-zero/core/syncx"
  10. "github.com/tal-tech/go-zero/core/threading"
  11. "github.com/tal-tech/go-zero/core/timex"
  12. )
  13. const idleRound = 10
  14. type (
  15. // A type that satisfies executors.TaskContainer can be used as the underlying
  16. // container that used to do periodical executions.
  17. TaskContainer interface {
  18. // AddTask adds the task into the container.
  19. // Returns true if the container needs to be flushed after the addition.
  20. AddTask(task interface{}) bool
  21. // Execute handles the collected tasks by the container when flushing.
  22. Execute(tasks interface{})
  23. // RemoveAll removes the contained tasks, and return them.
  24. RemoveAll() interface{}
  25. }
  26. PeriodicalExecutor struct {
  27. commander chan interface{}
  28. interval time.Duration
  29. container TaskContainer
  30. waitGroup sync.WaitGroup
  31. // avoid race condition on waitGroup when calling wg.Add/Done/Wait(...)
  32. wgBarrier syncx.Barrier
  33. confirmChan chan lang.PlaceholderType
  34. inflight int32
  35. guarded bool
  36. newTicker func(duration time.Duration) timex.Ticker
  37. lock sync.Mutex
  38. }
  39. )
  40. func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *PeriodicalExecutor {
  41. executor := &PeriodicalExecutor{
  42. // buffer 1 to let the caller go quickly
  43. commander: make(chan interface{}, 1),
  44. interval: interval,
  45. container: container,
  46. confirmChan: make(chan lang.PlaceholderType),
  47. newTicker: func(d time.Duration) timex.Ticker {
  48. return timex.NewTicker(d)
  49. },
  50. }
  51. proc.AddShutdownListener(func() {
  52. executor.Flush()
  53. })
  54. return executor
  55. }
  56. func (pe *PeriodicalExecutor) Add(task interface{}) {
  57. if vals, ok := pe.addAndCheck(task); ok {
  58. pe.commander <- vals
  59. <-pe.confirmChan
  60. }
  61. }
  62. func (pe *PeriodicalExecutor) Flush() bool {
  63. pe.enterExecution()
  64. return pe.executeTasks(func() interface{} {
  65. pe.lock.Lock()
  66. defer pe.lock.Unlock()
  67. return pe.container.RemoveAll()
  68. }())
  69. }
  70. func (pe *PeriodicalExecutor) Sync(fn func()) {
  71. pe.lock.Lock()
  72. defer pe.lock.Unlock()
  73. fn()
  74. }
  75. func (pe *PeriodicalExecutor) Wait() {
  76. pe.Flush()
  77. pe.wgBarrier.Guard(func() {
  78. pe.waitGroup.Wait()
  79. })
  80. }
  81. func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool) {
  82. pe.lock.Lock()
  83. defer func() {
  84. if !pe.guarded {
  85. pe.guarded = true
  86. // defer to unlock quickly
  87. defer pe.backgroundFlush()
  88. }
  89. pe.lock.Unlock()
  90. }()
  91. if pe.container.AddTask(task) {
  92. atomic.AddInt32(&pe.inflight, 1)
  93. return pe.container.RemoveAll(), true
  94. }
  95. return nil, false
  96. }
  97. func (pe *PeriodicalExecutor) backgroundFlush() {
  98. threading.GoSafe(func() {
  99. // flush before quit goroutine to avoid missing tasks
  100. defer pe.Flush()
  101. ticker := pe.newTicker(pe.interval)
  102. defer ticker.Stop()
  103. var commanded bool
  104. last := timex.Now()
  105. for {
  106. select {
  107. case vals := <-pe.commander:
  108. commanded = true
  109. atomic.AddInt32(&pe.inflight, -1)
  110. pe.enterExecution()
  111. pe.confirmChan <- lang.Placeholder
  112. pe.executeTasks(vals)
  113. last = timex.Now()
  114. case <-ticker.Chan():
  115. if commanded {
  116. commanded = false
  117. } else if pe.Flush() {
  118. last = timex.Now()
  119. } else if pe.shallQuit(last) {
  120. return
  121. }
  122. }
  123. }
  124. })
  125. }
  126. func (pe *PeriodicalExecutor) doneExecution() {
  127. pe.waitGroup.Done()
  128. }
  129. func (pe *PeriodicalExecutor) enterExecution() {
  130. pe.wgBarrier.Guard(func() {
  131. pe.waitGroup.Add(1)
  132. })
  133. }
  134. func (pe *PeriodicalExecutor) executeTasks(tasks interface{}) bool {
  135. defer pe.doneExecution()
  136. ok := pe.hasTasks(tasks)
  137. if ok {
  138. pe.container.Execute(tasks)
  139. }
  140. return ok
  141. }
  142. func (pe *PeriodicalExecutor) hasTasks(tasks interface{}) bool {
  143. if tasks == nil {
  144. return false
  145. }
  146. val := reflect.ValueOf(tasks)
  147. switch val.Kind() {
  148. case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
  149. return val.Len() > 0
  150. default:
  151. // unknown type, let caller execute it
  152. return true
  153. }
  154. }
  155. func (pe *PeriodicalExecutor) shallQuit(last time.Duration) (stop bool) {
  156. if timex.Since(last) <= pe.interval*idleRound {
  157. return
  158. }
  159. // checking pe.inflight and setting pe.guarded should be locked together
  160. pe.lock.Lock()
  161. if atomic.LoadInt32(&pe.inflight) == 0 {
  162. pe.guarded = false
  163. stop = true
  164. }
  165. pe.lock.Unlock()
  166. return
  167. }