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  }