github.com/whiteboxio/flow@v0.0.3-0.20190918184116-508d75d68a2c/pkg/corev1alpha1/actor/throttler.go (about) 1 package actor 2 3 import ( 4 "fmt" 5 "sync" 6 "sync/atomic" 7 _ "unsafe" 8 9 core "github.com/awesome-flow/flow/pkg/corev1alpha1" 10 ) 11 12 //go:noescape 13 //go:linkname nanotime runtime.nanotime 14 func nanotime() int64 15 16 type Throttler struct { 17 name string 18 ctx *core.Context 19 msgkey string 20 msgcost int64 21 bukcap int64 22 timefun func() int64 23 buckets map[string]*int64 24 mutex sync.RWMutex 25 queue chan *core.Message 26 wg sync.WaitGroup 27 } 28 29 var _ core.Actor = (*Throttler)(nil) 30 31 func NewThrottler(name string, ctx *core.Context, params core.Params) (core.Actor, error) { 32 rps, ok := params["rps"] 33 if !ok { 34 return nil, fmt.Errorf("throttler %s is missing `rps` config", name) 35 } 36 37 msgcost := 1000000000 / int64(rps.(int)) 38 bukcap := 1000000000 - msgcost 39 40 t := &Throttler{ 41 name: name, 42 ctx: ctx, 43 msgcost: msgcost, 44 bukcap: bukcap, 45 timefun: nanotime, 46 buckets: map[string]*int64{"": new(int64)}, 47 queue: make(chan *core.Message), 48 } 49 50 if msgkey, ok := params["msgkey"]; ok { 51 t.msgkey = msgkey.(string) 52 } 53 54 return t, nil 55 } 56 57 func (t *Throttler) Name() string { 58 return t.name 59 } 60 61 func (t *Throttler) Start() error { 62 return nil 63 } 64 65 func (t *Throttler) Stop() error { 66 close(t.queue) 67 t.wg.Wait() 68 69 return nil 70 } 71 72 func (t *Throttler) Connect(nthreads int, peer core.Receiver) error { 73 for i := 0; i < nthreads; i++ { 74 t.wg.Add(1) 75 go func() { 76 for msg := range t.queue { 77 if err := peer.Receive(msg); err != nil { 78 msg.Complete(core.MsgStatusFailed) 79 t.ctx.Logger().Error(err.Error()) 80 } 81 } 82 t.wg.Done() 83 }() 84 } 85 86 return nil 87 } 88 89 func (t *Throttler) Receive(msg *core.Message) error { 90 k := "" 91 if len(t.msgkey) > 0 { 92 if v, ok := msg.Meta(t.msgkey); ok { 93 k = v.(string) 94 } 95 } 96 if t.shouldPass(k) { 97 t.queue <- msg 98 return nil 99 } 100 msg.Complete(core.MsgStatusThrottled) 101 102 return nil 103 } 104 105 func (t *Throttler) getBucket(msgkey string) *int64 { 106 t.mutex.RLock() 107 if bucket, ok := t.buckets[msgkey]; ok { 108 t.mutex.RUnlock() 109 return bucket 110 } 111 112 b := new(int64) 113 *b = t.timefun() - 1 114 115 t.mutex.Lock() 116 defer t.mutex.Unlock() 117 118 if bucket, ok := t.buckets[msgkey]; ok { 119 return bucket 120 } 121 t.buckets[msgkey] = b 122 123 return b 124 } 125 126 func (t *Throttler) shouldPass(msgkey string) bool { 127 bucket := t.getBucket(msgkey) 128 129 for l := 0; l < 10; l++ { 130 now := t.timefun() 131 tat := atomic.LoadInt64(bucket) // theoretical arrival time 132 if now < tat-t.bukcap { 133 return false 134 } 135 var newtat int64 136 if now > tat { 137 newtat = now + t.msgcost 138 } else { 139 newtat = tat + t.msgcost 140 } 141 142 if atomic.CompareAndSwapInt64(bucket, tat, newtat) { 143 return true 144 } 145 } 146 147 return false 148 }