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