github.com/webx-top/com@v1.2.12/goroutine.go (about) 1 package com 2 3 import ( 4 "context" 5 "log" 6 "os" 7 "os/signal" 8 "sync" 9 "sync/atomic" 10 "time" 11 ) 12 13 var ErrExitedByContext = context.Canceled 14 15 func Loop(ctx context.Context, exec func() error, duration time.Duration) error { 16 if ctx == nil { 17 ctx = context.Background() 18 } 19 check := func() <-chan struct{} { 20 return ctx.Done() 21 } 22 for { 23 select { 24 case <-check(): 25 log.Println(CalledAtFileLine(2), ErrExitedByContext) 26 return ErrExitedByContext 27 default: 28 if err := exec(); err != nil { 29 return err 30 } 31 time.Sleep(duration) 32 } 33 } 34 } 35 36 // Notify 等待系统信号 37 // <-Notify() 38 func Notify(sig ...os.Signal) chan os.Signal { 39 terminate := make(chan os.Signal, 1) 40 if len(sig) == 0 { 41 sig = []os.Signal{os.Interrupt} 42 } 43 signal.Notify(terminate, sig...) 44 return terminate 45 } 46 47 type DelayOncer interface { 48 Do(parentCtx context.Context, key string, f func() error, delayAndTimeout ...time.Duration) (isNew bool) 49 DoWithState(parentCtx context.Context, key string, f func(func() bool) error, delayAndTimeout ...time.Duration) (isNew bool) 50 Close() error 51 } 52 53 func NewDelayOnce(delay time.Duration, timeout time.Duration, debugMode ...bool) DelayOncer { 54 if timeout <= delay { 55 panic(`timeout must be greater than delay`) 56 } 57 var debug bool 58 if len(debugMode) > 0 { 59 debug = debugMode[0] 60 } 61 return &delayOnce{ 62 mp: sync.Map{}, 63 delay: delay, 64 timeout: timeout, 65 debug: debug, 66 } 67 } 68 69 // delayOnce 触发之后延迟一定的时间后再执行。如果在延迟处理的时间段内再次触发,则延迟时间基于此处触发时间顺延 70 // d := NewDelayOnce(time.Second*5, time.Hour) 71 // ctx := context.TODO() 72 // 73 // for i:=0; i<10; i++ { 74 // d.Do(ctx, `key`,func() error { return nil }) 75 // } 76 type delayOnce struct { 77 mp sync.Map 78 delay time.Duration 79 timeout time.Duration 80 debug bool 81 } 82 83 type eventSession struct { 84 cancel context.CancelFunc 85 time time.Time 86 mutex sync.RWMutex 87 stop chan struct{} 88 running atomic.Bool 89 } 90 91 func (e *eventSession) Renew(t time.Time) { 92 e.mutex.Lock() 93 e.time = t 94 e.mutex.Unlock() 95 } 96 97 func (e *eventSession) Time() time.Time { 98 e.mutex.RLock() 99 t := e.time 100 e.mutex.RUnlock() 101 return t 102 } 103 104 func (e *eventSession) Cancel() <-chan struct{} { 105 e.cancel() 106 return e.stop 107 } 108 109 func (d *delayOnce) checkAndStore(parentCtx context.Context, key string, timeout time.Duration) (*eventSession, context.Context) { 110 v, loaded := d.mp.Load(key) 111 if loaded { 112 session := v.(*eventSession) 113 if session.running.Load() { 114 return nil, nil 115 } 116 if time.Since(session.Time()) < timeout { // 超过 d.timeout 后重新处理 117 session.Renew(time.Now()) 118 d.mp.Store(key, session) 119 return nil, nil 120 } 121 122 if d.debug { 123 log.Println(`[delayOnce] cancel -------------> ` + key) 124 } 125 126 <-session.Cancel() 127 128 if d.debug { 129 log.Println(`[delayOnce] canceled -------------> ` + key) 130 } 131 132 if session.running.Load() { 133 return nil, nil 134 } 135 } 136 ctx, cancel := context.WithCancel(parentCtx) 137 session := &eventSession{ 138 cancel: cancel, 139 time: time.Now(), 140 stop: make(chan struct{}, 1), 141 } 142 d.mp.Store(key, session) 143 return session, ctx 144 } 145 146 func (d *delayOnce) getDelayAndTimeout(delayAndTimeout ...time.Duration) (time.Duration, time.Duration) { 147 delay := d.delay 148 timeout := d.timeout 149 if len(delayAndTimeout) > 0 { 150 if delayAndTimeout[0] > 0 { 151 delay = delayAndTimeout[0] 152 } 153 if len(delayAndTimeout) > 1 { 154 if delayAndTimeout[1] > 0 { 155 timeout = delayAndTimeout[1] 156 } 157 } 158 } 159 return delay, timeout 160 } 161 162 func (d *delayOnce) Do(parentCtx context.Context, key string, f func() error, delayAndTimeout ...time.Duration) (isNew bool) { 163 delay, timeout := d.getDelayAndTimeout(delayAndTimeout...) 164 session, ctx := d.checkAndStore(parentCtx, key, timeout) 165 if session == nil { 166 return false 167 } 168 go func(key string) { 169 t := time.NewTicker(time.Second) 170 defer t.Stop() 171 for { 172 select { 173 case <-ctx.Done(): // 如果先进入“<-t.C”分支,会等“<-t.C”分支内的代码执行完毕后才有机会执行本分支 174 d.mp.Delete(key) 175 session.stop <- struct{}{} 176 close(session.stop) 177 if d.debug { 178 log.Println(`[delayOnce] close -------------> ` + key) 179 } 180 return 181 case <-t.C: 182 if time.Since(session.Time()) >= delay { // 时间超过d.delay才触发 183 session.running.Store(true) 184 err := f() 185 session.running.Store(false) 186 session.Cancel() 187 if err != nil { 188 log.Println(key+`:`, err) 189 } 190 } 191 } 192 } 193 }(key) 194 return true 195 } 196 197 func (d *delayOnce) DoWithState(parentCtx context.Context, key string, f func(func() bool) error, delayAndTimeout ...time.Duration) (isNew bool) { 198 delay, timeout := d.getDelayAndTimeout(delayAndTimeout...) 199 session, ctx := d.checkAndStore(parentCtx, key, timeout) 200 if session == nil { 201 return false 202 } 203 go func(key string) { 204 var state int32 205 isAbort := func() bool { 206 return atomic.LoadInt32(&state) > 0 207 } 208 go func() { 209 <-ctx.Done() 210 atomic.AddInt32(&state, 1) 211 }() 212 t := time.NewTicker(time.Second) 213 defer t.Stop() 214 for { 215 select { 216 case <-ctx.Done(): // 如果先进入“<-t.C”分支,会等“<-t.C”分支内的代码执行完毕后才有机会执行本分支 217 d.mp.Delete(key) 218 session.stop <- struct{}{} 219 close(session.stop) 220 if d.debug { 221 log.Println(`[delayOnce] close -------------> ` + key) 222 } 223 return 224 case <-t.C: 225 if time.Since(session.Time()) >= delay { // 时间超过d.delay才触发 226 session.running.Store(true) 227 err := f(isAbort) 228 session.running.Store(false) 229 session.Cancel() 230 if err != nil { 231 log.Println(key+`:`, err) 232 } 233 } 234 } 235 } 236 }(key) 237 return true 238 } 239 240 func (d *delayOnce) Close() error { 241 d.mp.Range(func(key, value interface{}) bool { 242 session := value.(*eventSession) 243 session.Cancel() 244 return true 245 }) 246 return nil 247 }