github.com/weedge/lib@v0.0.0-20230424045628-a36dcc1d90e4/timingwheel/bucket.go (about) 1 package timingwheel 2 3 import ( 4 "container/list" 5 "sync" 6 "sync/atomic" 7 "unsafe" 8 ) 9 10 // Timer represents a single event. When the Timer expires, the given 11 // task will be executed. 12 type Timer struct { 13 expiration int64 // in milliseconds 14 task func() 15 16 // The bucket that holds the list to which this timer's element belongs. 17 // 18 // NOTE: This field may be updated and read concurrently, 19 // through Timer.Stop() and Bucket.Flush(). 20 b unsafe.Pointer // type: *bucket 21 22 // The timer's element. 23 element *list.Element 24 } 25 26 func (t *Timer) getBucket() *bucket { 27 return (*bucket)(atomic.LoadPointer(&t.b)) 28 } 29 30 func (t *Timer) setBucket(b *bucket) { 31 atomic.StorePointer(&t.b, unsafe.Pointer(b)) 32 } 33 34 // Stop prevents the Timer from firing. It returns true if the call 35 // stops the timer, false if the timer has already expired or been stopped. 36 // 37 // If the timer t has already expired and the t.task has been started in its own 38 // goroutine; Stop does not wait for t.task to complete before returning. If the caller 39 // needs to know whether t.task is completed, it must coordinate with t.task explicitly. 40 func (t *Timer) Stop() bool { 41 stopped := false 42 for b := t.getBucket(); b != nil; b = t.getBucket() { 43 // If b.Remove is called just after the timing wheel's goroutine has: 44 // 1. removed t from b (through b.Flush -> b.remove) 45 // 2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add) 46 // this may fail to remove t due to the change of t's bucket. 47 stopped = b.Remove(t) 48 49 // Thus, here we re-get t's possibly new bucket (nil for case 1, or ab (non-nil) for case 2), 50 // and retry until the bucket becomes nil, which indicates that t has finally been removed. 51 } 52 return stopped 53 } 54 55 type bucket struct { 56 // 64-bit atomic operations require 64-bit alignment, but 32-bit 57 // compilers do not ensure it. So we must keep the 64-bit field 58 // as the first field of the struct. 59 // 60 // For more explanations, see https://golang.org/pkg/sync/atomic/#pkg-note-BUG 61 // and https://go101.org/article/memory-layout.html. 62 expiration int64 63 64 mu sync.Mutex 65 timers *list.List 66 } 67 68 func newBucket() *bucket { 69 return &bucket{ 70 timers: list.New(), 71 expiration: -1, 72 } 73 } 74 75 func (b *bucket) Expiration() int64 { 76 return atomic.LoadInt64(&b.expiration) 77 } 78 79 func (b *bucket) SetExpiration(expiration int64) bool { 80 return atomic.SwapInt64(&b.expiration, expiration) != expiration 81 } 82 83 func (b *bucket) Add(t *Timer) { 84 b.mu.Lock() 85 86 e := b.timers.PushBack(t) 87 t.setBucket(b) 88 t.element = e 89 90 b.mu.Unlock() 91 } 92 93 func (b *bucket) remove(t *Timer) bool { 94 if t.getBucket() != b { 95 // If remove is called from t.Stop, and this happens just after the timing wheel's goroutine has: 96 // 1. removed t from b (through b.Flush -> b.remove) 97 // 2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add) 98 // then t.getBucket will return nil for case 1, or ab (non-nil) for case 2. 99 // In either case, the returned value does not equal to b. 100 return false 101 } 102 b.timers.Remove(t.element) 103 t.setBucket(nil) 104 t.element = nil 105 return true 106 } 107 108 func (b *bucket) Remove(t *Timer) bool { 109 b.mu.Lock() 110 defer b.mu.Unlock() 111 return b.remove(t) 112 } 113 114 func (b *bucket) Flush(reinsert func(*Timer)) { 115 var ts []*Timer 116 117 b.mu.Lock() 118 for e := b.timers.Front(); e != nil; { 119 next := e.Next() 120 121 t := e.Value.(*Timer) 122 b.remove(t) 123 ts = append(ts, t) 124 125 e = next 126 } 127 b.mu.Unlock() 128 129 b.SetExpiration(-1) // TODO: Improve the coordination with b.Add() 130 131 for _, t := range ts { 132 reinsert(t) 133 } 134 }