package ratelimiter import ( "sync" "github.com/prometheus/client_golang/prometheus" "github.com/ethereum/go-ethereum/event" "github.com/harmony-one/harmony/p2p/stream/common/streammanager" sttypes "github.com/harmony-one/harmony/p2p/stream/types" p2ptypes "github.com/harmony-one/harmony/p2p/types" "go.uber.org/ratelimit" ) // RateLimiter is the interface to limit the incoming request. // The purpose of rate limiter is to prevent the node from running out of resource // for consensus on DDoS attacks. type RateLimiter interface { LimitRequest(stid sttypes.StreamID) p2ptypes.LifeCycle } // rateLimiter is the implementation of RateLimiter. // The rateLimiter limit request rate: // 1. For global stream requests // 2. For requests from a stream // // TODO: make request weighted in rate limiter type rateLimiter struct { globalLimiter ratelimit.Limiter streamRate int limiters map[sttypes.StreamID]ratelimit.Limiter sm streammanager.Subscriber lock sync.RWMutex closeC chan struct{} } // NewRateLimiter creates a new rate limiter func NewRateLimiter(sm streammanager.Subscriber, global, single int) RateLimiter { return &rateLimiter{ globalLimiter: ratelimit.New(global), streamRate: single, limiters: make(map[sttypes.StreamID]ratelimit.Limiter), sm: sm, closeC: make(chan struct{}), } } // Start start the rate limiter func (rl *rateLimiter) Start() { rmStC := make(chan streammanager.EvtStreamRemoved) sub := rl.sm.SubscribeRemoveStreamEvent(rmStC) go rl.rmStreamLoop(rmStC, sub) } // Close close the rate limiter func (rl *rateLimiter) Close() { close(rl.closeC) } func (rl *rateLimiter) rmStreamLoop(rmStC chan streammanager.EvtStreamRemoved, sub event.Subscription) { defer sub.Unsubscribe() for { select { case evt := <-rmStC: stid := evt.ID rl.unRegStream(stid) case <-rl.closeC: return } } } func (rl *rateLimiter) LimitRequest(stid sttypes.StreamID) { serverRequestCounter.Inc() timer := prometheus.NewTimer(serverRequestDelayDuration) defer timer.ObserveDuration() rl.lock.RLock() limiter, ok := rl.limiters[stid] rl.lock.RUnlock() // double lock to ensure concurrency if !ok { rl.lock.Lock() if limiter2, ok := rl.limiters[stid]; ok { limiter = limiter2 } else { limiter = ratelimit.New(rl.streamRate) rl.limiters[stid] = limiter } rl.lock.Unlock() } rl.globalLimiter.Take() limiter.Take() } func (rl *rateLimiter) unRegStream(stid sttypes.StreamID) { rl.lock.Lock() delete(rl.limiters, stid) defer rl.lock.Unlock() }