github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/f/timer.go (about) 1 package f 2 3 import ( 4 "container/heap" 5 "container/list" 6 "errors" 7 "sync" 8 "sync/atomic" 9 "time" 10 "unsafe" 11 ) 12 13 // Timer is an implementation of Hierarchical Timing Wheels. 14 type Timer struct { 15 tick int64 // in milliseconds 16 wheelSize int64 17 18 interval int64 // in milliseconds 19 currentTime int64 // in milliseconds 20 buckets []*TimerBucket 21 queue *timerDelayQueue 22 23 // The higher-level overflow wheel. 24 // 25 // NOTE: This field may be updated and read concurrently, through Add(). 26 overflowWheel unsafe.Pointer // type: *Timer 27 28 exitC chan struct{} 29 waitGroup sync.WaitGroup 30 } 31 32 // NewTimer creates an instance of Timer with the given tick and wheelSize. 33 func NewTimer(tick time.Duration, wheelSize int64) *Timer { 34 tickMs := int64(tick / time.Millisecond) 35 if tickMs <= 0 { 36 panic(errors.New("tick must be greater than or equal to 1ms")) 37 } 38 39 startMs := time.Now().UTC().UnixNano() / int64(time.Millisecond) 40 41 return newTimer( 42 tickMs, 43 wheelSize, 44 startMs, 45 newTimerDelayQueue(int(wheelSize)), 46 ) 47 } 48 49 // newTimer is an internal helper function that really creates an instance of Timer. 50 func newTimer(tickMs int64, wheelSize int64, startMs int64, queue *timerDelayQueue) *Timer { 51 currentTime := startMs 52 if tickMs > 0 { 53 currentTime = startMs - startMs%tickMs 54 } 55 buckets := make([]*TimerBucket, wheelSize) 56 for i := range buckets { 57 buckets[i] = NewTimerBucket() 58 } 59 return &Timer{ 60 tick: tickMs, 61 wheelSize: wheelSize, 62 currentTime: currentTime, 63 interval: tickMs * wheelSize, 64 buckets: buckets, 65 queue: queue, 66 exitC: make(chan struct{}), 67 } 68 } 69 70 // add inserts the timer t into the current timing wheel. 71 func (tw *Timer) add(t *TimerElement) bool { 72 currentTime := atomic.LoadInt64(&tw.currentTime) 73 if t.expiration < currentTime+tw.tick { 74 // Already expired 75 return false 76 } else if t.expiration < currentTime+tw.interval { 77 // Put it into its own bucket 78 virtualID := t.expiration / tw.tick 79 b := tw.buckets[virtualID%tw.wheelSize] 80 b.Add(t) 81 82 // Set the bucket expiration time 83 if b.SetExpiration(virtualID * tw.tick) { 84 // The bucket needs to be enqueued since it was an expired bucket. 85 // We only need to enqueue the bucket when its expiration time has changed, 86 // i.e. the wheel has advanced and this bucket get reused with a new expiration. 87 // Any further calls to set the expiration within the same wheel cycle will 88 // pass in the same value and hence return false, thus the bucket with the 89 // same expiration will not be enqueued multiple times. 90 tw.queue.Offer(b, b.Expiration()) 91 } 92 return true 93 } else { 94 // Out of the interval. Put it into the overflow wheel 95 overflowWheel := atomic.LoadPointer(&tw.overflowWheel) 96 if overflowWheel == nil { 97 atomic.CompareAndSwapPointer( 98 &tw.overflowWheel, 99 nil, 100 unsafe.Pointer(newTimer( 101 tw.interval, 102 tw.wheelSize, 103 currentTime, 104 tw.queue, 105 )), 106 ) 107 overflowWheel = atomic.LoadPointer(&tw.overflowWheel) 108 } 109 return (*Timer)(overflowWheel).add(t) 110 } 111 } 112 113 // addOrRun inserts the timer t into the current timing wheel, or run the 114 // timer's task if it has already expired. 115 func (tw *Timer) addOrRun(t *TimerElement) { 116 if !tw.add(t) { 117 // Already expired 118 119 // Like the standard time.AfterFunc (https://golang.org/pkg/time/#AfterFunc), 120 // always execute the timer's task in its own goroutine. 121 go t.task() 122 } 123 } 124 125 func (tw *Timer) advanceClock(expiration int64) { 126 currentTime := atomic.LoadInt64(&tw.currentTime) 127 if expiration >= currentTime+tw.tick { 128 currentTime := expiration 129 if tw.tick > 0 { 130 currentTime = expiration - expiration%tw.tick 131 } 132 atomic.StoreInt64(&tw.currentTime, currentTime) 133 134 // Try to advance the clock of the overflow wheel if present 135 overflowWheel := atomic.LoadPointer(&tw.overflowWheel) 136 if overflowWheel != nil { 137 (*Timer)(overflowWheel).advanceClock(currentTime) 138 } 139 } 140 } 141 142 // Start starts the current timing wheel. 143 func (tw *Timer) Start() { 144 tw.waitGroup.Add(1) 145 go func() { 146 defer tw.waitGroup.Done() 147 tw.queue.Poll(tw.exitC, func() int64 { 148 return time.Now().UTC().UnixNano() / int64(time.Millisecond) 149 }) 150 }() 151 152 tw.waitGroup.Add(1) 153 go func() { 154 defer tw.waitGroup.Done() 155 for { 156 select { 157 case elem := <-tw.queue.C: 158 b := elem.(*TimerBucket) 159 tw.advanceClock(b.Expiration()) 160 b.Flush(tw.addOrRun) 161 case <-tw.exitC: 162 return 163 } 164 } 165 }() 166 } 167 168 // Stop stops the current timing wheel. 169 // 170 // If there is any timer's task being running in its own goroutine, Stop does 171 // not wait for the task to complete before returning. If the caller needs to 172 // know whether the task is completed, it must coordinate with the task explicitly. 173 func (tw *Timer) Stop() { 174 close(tw.exitC) 175 tw.waitGroup.Wait() 176 } 177 178 // AfterFunc waits for the duration to elapse and then calls f in its own goroutine. 179 // It returns a Timer that can be used to cancel the call using its Stop method. 180 func (tw *Timer) AfterFunc(d time.Duration, f func()) *TimerElement { 181 t := &TimerElement{ 182 expiration: time.Now().UTC().Add(d).UnixNano() / int64(time.Millisecond), 183 task: f, 184 } 185 tw.addOrRun(t) 186 return t 187 } 188 189 // TimerScheduler determines the execution plan of a task. 190 type TimerScheduler interface { 191 // Next returns the next execution time after the given (previous) time. 192 // It will return a zero time if no next time is scheduled. 193 // 194 // All times must be UTC. 195 Next(time.Time) time.Time 196 } 197 198 // ScheduleFunc calls f (in its own goroutine) according to the execution 199 // plan scheduled by s. It returns a Timer that can be used to cancel the 200 // call using its Stop method. 201 // 202 // If the caller want to terminate the execution plan halfway, it must 203 // stop the timer and ensure that the timer is stopped actually, since in 204 // the current implementation, there is a gap between the expiring and the 205 // restarting of the timer. The wait time for ensuring is short since the 206 // gap is very small. 207 // 208 // Internally, ScheduleFunc will ask the first execution time (by calling 209 // s.Next()) initially, and create a timer if the execution time is non-zero. 210 // Afterwards, it will ask the next execution time each time f is about to 211 // be executed, and f will be called at the next execution time if the time 212 // is non-zero. 213 func (tw *Timer) ScheduleFunc(s TimerScheduler, f func()) (t *TimerElement) { 214 expiration := s.Next(time.Now().UTC()) 215 if expiration.IsZero() { 216 // No time is scheduled, return nil. 217 return 218 } 219 220 t = &TimerElement{ 221 expiration: expiration.UnixNano() / int64(time.Millisecond), 222 task: func() { 223 // Schedule the task to execute at the next time if possible. 224 expiration := s.Next(time.Unix(0, t.expiration*int64(time.Millisecond)).UTC()) 225 if !expiration.IsZero() { 226 t.expiration = expiration.UnixNano() / int64(time.Millisecond) 227 tw.addOrRun(t) 228 } 229 230 // Actually execute the task. 231 f() 232 }, 233 } 234 tw.addOrRun(t) 235 236 return 237 } 238 239 // The start of TimerElement implementation. 240 241 // TimerElement represents a single event. When the Timer expires, the given 242 // task will be executed. 243 type TimerElement struct { 244 expiration int64 // in milliseconds 245 task func() 246 247 // The bucket that holds the list to which this timer's element belongs. 248 // 249 // NOTE: This field may be updated and read concurrently, 250 // through Timer.Stop() and Bucket.Flush(). 251 b unsafe.Pointer // type: *bucket 252 253 // The timer's element. 254 element *list.Element 255 } 256 257 func (t *TimerElement) getBucket() *TimerBucket { 258 return (*TimerBucket)(atomic.LoadPointer(&t.b)) 259 } 260 261 func (t *TimerElement) setBucket(b *TimerBucket) { 262 atomic.StorePointer(&t.b, unsafe.Pointer(b)) 263 } 264 265 // Stop prevents the Timer from firing. It returns true if the call 266 // stops the timer, false if the timer has already expired or been stopped. 267 // 268 // If the timer t has already expired and the t.task has been started in its own 269 // goroutine; Stop does not wait for t.task to complete before returning. If the caller 270 // needs to know whether t.task is completed, it must coordinate with t.task explicitly. 271 func (t *TimerElement) Stop() bool { 272 stopped := false 273 for b := t.getBucket(); b != nil; b = t.getBucket() { 274 // If b.Remove is called just after the timing wheel's goroutine has: 275 // 1. removed t from b (through b.Flush -> b.remove) 276 // 2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add) 277 // this may fail to remove t due to the change of t's bucket. 278 stopped = b.Remove(t) 279 280 // Thus, here we re-get t's possibly new bucket (nil for case 1, or ab (non-nil) for case 2), 281 // and retry until the bucket becomes nil, which indicates that t has finally been removed. 282 } 283 return stopped 284 } 285 286 // TimerBucket manage timers list 287 type TimerBucket struct { 288 // 64-bit atomic operations require 64-bit alignment, but 32-bit 289 // compilers do not ensure it. So we must keep the 64-bit field 290 // as the first field of the struct. 291 // 292 // For more explanations, see https://golang.org/pkg/sync/atomic/#pkg-note-BUG 293 expiration int64 294 295 mu sync.Mutex 296 Timers *list.List 297 } 298 299 // NewTimerBucket creates timers list 300 func NewTimerBucket() *TimerBucket { 301 return &TimerBucket{ 302 Timers: list.New(), 303 expiration: -1, 304 } 305 } 306 307 // Expiration get expiration time 308 func (b *TimerBucket) Expiration() int64 { 309 return atomic.LoadInt64(&b.expiration) 310 } 311 312 // SetExpiration set expiration time 313 func (b *TimerBucket) SetExpiration(expiration int64) bool { 314 return atomic.SwapInt64(&b.expiration, expiration) != expiration 315 } 316 317 // Add a element to timers list 318 func (b *TimerBucket) Add(t *TimerElement) { 319 b.mu.Lock() 320 321 e := b.Timers.PushBack(t) 322 t.setBucket(b) 323 t.element = e 324 325 b.mu.Unlock() 326 } 327 328 // Remove a element to timers list 329 func (b *TimerBucket) Remove(t *TimerElement) bool { 330 b.mu.Lock() 331 defer b.mu.Unlock() 332 return b.remove(t) 333 } 334 335 func (b *TimerBucket) remove(t *TimerElement) bool { 336 if t.getBucket() != b { 337 // If remove is called from t.Stop, and this happens just after the timing wheel's goroutine has: 338 // 1. removed t from b (through b.Flush -> b.remove) 339 // 2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add) 340 // then t.getBucket will return nil for case 1, or ab (non-nil) for case 2. 341 // In either case, the returned value does not equal to b. 342 return false 343 } 344 b.Timers.Remove(t.element) 345 t.setBucket(nil) 346 t.element = nil 347 return true 348 } 349 350 // Flush timers list 351 func (b *TimerBucket) Flush(reinsert func(*TimerElement)) { 352 var ts []*TimerElement 353 354 b.mu.Lock() 355 for e := b.Timers.Front(); e != nil; { 356 next := e.Next() 357 358 t := e.Value.(*TimerElement) 359 b.remove(t) 360 ts = append(ts, t) 361 362 e = next 363 } 364 b.mu.Unlock() 365 366 b.SetExpiration(-1) 367 368 for _, t := range ts { 369 reinsert(t) 370 } 371 } 372 373 // The end of TimerElement implementation. 374 375 // The start of PriorityQueue implementation. 376 // Borrowed from https://github.com/nsqio/nsq/blob/master/internal/pqueue/pqueue.go 377 378 type priorityQueueItem struct { 379 Value interface{} 380 Priority int64 381 Index int 382 } 383 384 // this is a priority queue as implemented by a min heap 385 // ie. the 0th element is the *lowest* value 386 type priorityQueue []*priorityQueueItem 387 388 func newPriorityQueue(capacity int) priorityQueue { 389 return make(priorityQueue, 0, capacity) 390 } 391 392 func (pq priorityQueue) Len() int { 393 return len(pq) 394 } 395 396 func (pq priorityQueue) Less(i, j int) bool { 397 return pq[i].Priority < pq[j].Priority 398 } 399 400 func (pq priorityQueue) Swap(i, j int) { 401 pq[i], pq[j] = pq[j], pq[i] 402 pq[i].Index = i 403 pq[j].Index = j 404 } 405 406 func (pq *priorityQueue) Push(x interface{}) { 407 n := len(*pq) 408 c := cap(*pq) 409 if n+1 > c { 410 npq := make(priorityQueue, n, c*2) 411 copy(npq, *pq) 412 *pq = npq 413 } 414 *pq = (*pq)[0 : n+1] 415 item := x.(*priorityQueueItem) 416 item.Index = n 417 (*pq)[n] = item 418 } 419 420 func (pq *priorityQueue) Pop() interface{} { 421 n := len(*pq) 422 c := cap(*pq) 423 if n < (c/2) && c > 25 { 424 npq := make(priorityQueue, n, c/2) 425 copy(npq, *pq) 426 *pq = npq 427 } 428 item := (*pq)[n-1] 429 item.Index = -1 430 *pq = (*pq)[0 : n-1] 431 return item 432 } 433 434 func (pq *priorityQueue) PeekAndShift(max int64) (*priorityQueueItem, int64) { 435 if pq.Len() == 0 { 436 return nil, 0 437 } 438 439 item := (*pq)[0] 440 if item.Priority > max { 441 return nil, item.Priority - max 442 } 443 heap.Remove(pq, 0) 444 445 return item, 0 446 } 447 448 // The end of PriorityQueue implementation. 449 450 // timerDelayQueue is an unbounded blocking queue of *Delayed* elements, in which 451 // an element can only be taken when its delay has expired. The head of the 452 // queue is the *Delayed* element whose delay expired furthest in the past. 453 type timerDelayQueue struct { 454 C chan interface{} 455 456 mu sync.Mutex 457 pq priorityQueue 458 459 // Similar to the sleeping state of runtime.Timers. 460 sleeping int32 461 wakeupC chan struct{} 462 } 463 464 // newTimerDelayQueue creates an instance of delayQueue with the specified size. 465 func newTimerDelayQueue(size int) *timerDelayQueue { 466 return &timerDelayQueue{ 467 C: make(chan interface{}), 468 pq: newPriorityQueue(size), 469 wakeupC: make(chan struct{}), 470 } 471 } 472 473 // Offer inserts the element into the current queue. 474 func (dq *timerDelayQueue) Offer(elem interface{}, expiration int64) { 475 item := &priorityQueueItem{Value: elem, Priority: expiration} 476 477 dq.mu.Lock() 478 heap.Push(&dq.pq, item) 479 index := item.Index 480 dq.mu.Unlock() 481 482 if index == 0 { 483 // A new item with the earliest expiration is added. 484 if atomic.CompareAndSwapInt32(&dq.sleeping, 1, 0) { 485 dq.wakeupC <- struct{}{} 486 } 487 } 488 } 489 490 // Poll starts an infinite loop, in which it continually waits for an element 491 // to expire and then send the expired element to the channel C. 492 func (dq *timerDelayQueue) Poll(exitC chan struct{}, nowF func() int64) { 493 for { 494 now := nowF() 495 496 dq.mu.Lock() 497 item, delta := dq.pq.PeekAndShift(now) 498 if item == nil { 499 // No items left or at least one item is pending. 500 501 // We must ensure the atomicity of the whole operation, which is 502 // composed of the above PeekAndShift and the following StoreInt32, 503 // to avoid possible race conditions between Offer and Poll. 504 atomic.StoreInt32(&dq.sleeping, 1) 505 } 506 dq.mu.Unlock() 507 508 if item == nil { 509 if delta == 0 { 510 // No items left. 511 select { 512 case <-dq.wakeupC: 513 // Wait until a new item is added. 514 continue 515 case <-exitC: 516 goto exit 517 } 518 } else if delta > 0 { 519 // At least one item is pending. 520 select { 521 case <-dq.wakeupC: 522 // A new item with an "earlier" expiration than the current "earliest" one is added. 523 continue 524 case <-time.After(time.Duration(delta) * time.Millisecond): 525 // The current "earliest" item expires. 526 527 // Reset the sleeping state since there's no need to receive from wakeupC. 528 if atomic.SwapInt32(&dq.sleeping, 0) == 0 { 529 // A caller of Offer() is being blocked on sending to wakeupC, 530 // drain wakeupC to unblock the caller. 531 <-dq.wakeupC 532 } 533 continue 534 case <-exitC: 535 goto exit 536 } 537 } 538 } 539 540 select { 541 case dq.C <- item.Value: 542 // The expired element has been sent out successfully. 543 case <-exitC: 544 goto exit 545 } 546 } 547 548 exit: 549 // Reset the states 550 atomic.StoreInt32(&dq.sleeping, 0) 551 }