github.com/zhiqiangxu/util@v0.0.0-20230112053021-0a7aee056cd5/wm/offset.go (about) 1 package wm 2 3 import ( 4 "context" 5 "sync/atomic" 6 7 "github.com/zhiqiangxu/rpheap" 8 ) 9 10 type wait struct { 11 expectOffset int64 12 waiter chan struct{} 13 } 14 15 // Offset watermark model 16 type Offset struct { 17 doneOffset int64 18 waitCh chan wait 19 doneCh chan struct{} 20 heap *rpheap.Heap 21 } 22 23 // NewOffset is ctor for Offset 24 func NewOffset() *Offset { 25 o := &Offset{waitCh: make(chan wait), doneCh: make(chan struct{}, 1), heap: rpheap.New()} 26 go o.process() 27 return o 28 } 29 30 // Done for update doneOffset 31 func (o *Offset) Done(offset int64) { 32 for { 33 doneOffset := atomic.LoadInt64(&o.doneOffset) 34 if doneOffset >= offset { 35 return 36 } 37 38 if atomic.CompareAndSwapInt64(&o.doneOffset, doneOffset, offset) { 39 break 40 } 41 } 42 43 select { 44 case o.doneCh <- struct{}{}: 45 default: 46 } 47 } 48 49 // Wait for doneOffset>= expectOffset 50 func (o *Offset) Wait(ctx context.Context, expectOffset int64) error { 51 if atomic.LoadInt64(&o.doneOffset) >= expectOffset { 52 return nil 53 } 54 55 waiter := make(chan struct{}) 56 select { 57 case <-ctx.Done(): 58 return ctx.Err() 59 case o.waitCh <- wait{expectOffset: expectOffset, waiter: waiter}: 60 select { 61 case <-ctx.Done(): 62 return ctx.Err() 63 case <-waiter: 64 return nil 65 } 66 } 67 } 68 69 func (o *Offset) process() { 70 71 waits := make(map[int64][]chan struct{}) 72 saveWait := func(w wait) { 73 ws := waits[w.expectOffset] 74 if ws == nil { 75 o.heap.Insert(w.expectOffset) 76 waits[w.expectOffset] = []chan struct{}{w.waiter} 77 } else { 78 waits[w.expectOffset] = append(ws, w.waiter) 79 } 80 81 } 82 notifyUntil := func(doneOffset int64) { 83 for { 84 if o.heap.Size() == 0 { 85 return 86 } 87 minOffset := o.heap.FindMin() 88 if minOffset <= doneOffset { 89 for _, w := range waits[minOffset] { 90 close(w) 91 } 92 delete(waits, minOffset) 93 o.heap.DeleteMin() 94 } else { 95 break 96 } 97 } 98 } 99 for { 100 select { 101 case w := <-o.waitCh: 102 doneOffset := atomic.LoadInt64(&o.doneOffset) 103 if w.expectOffset <= doneOffset { 104 close(w.waiter) 105 } else { 106 saveWait(w) 107 } 108 case <-o.doneCh: 109 doneOffset := atomic.LoadInt64(&o.doneOffset) 110 111 // notify all waiters until doneOffset 112 notifyUntil(doneOffset) 113 } 114 } 115 }