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  }