Class: Prop::LeakyBucketStrategy

Inherits:
Object
  • Object
show all
Defined in:
lib/prop/leaky_bucket_strategy.rb

Class Method Summary collapse

Class Method Details

.build(options) ⇒ Object



44
45
46
47
48
49
50
51
# File 'lib/prop/leaky_bucket_strategy.rb', line 44

def build(options)
  key       = options.fetch(:key)
  handle    = options.fetch(:handle)

  cache_key = Prop::Key.normalize([ handle, key ])

  "prop/leaky_bucket/#{Digest::MD5.hexdigest(cache_key)}"
end

.compare_threshold?(counter, operator, options) ⇒ Boolean

Returns:

  • (Boolean)


40
41
42
# File 'lib/prop/leaky_bucket_strategy.rb', line 40

def compare_threshold?(counter, operator, options)
  counter.fetch(:bucket).to_i.send operator, options.fetch(:burst_rate)
end

.counter(cache_key, options) ⇒ Object



8
9
10
11
12
13
14
15
16
17
# File 'lib/prop/leaky_bucket_strategy.rb', line 8

def counter(cache_key, options)
  bucket = Prop::Limiter.cache.read(cache_key) || zero_counter
  now = Time.now.to_i
  leak_rate = (now - bucket.fetch(:last_updated)) / options.fetch(:interval).to_f
  leak_amount =  leak_rate * options.fetch(:threshold)

  bucket[:bucket] = [(bucket.fetch(:bucket) - leak_amount).to_i, 0].max
  bucket[:last_updated] = now
  bucket
end

.decrement(cache_key, amount, options) ⇒ Object



28
29
30
31
32
33
34
# File 'lib/prop/leaky_bucket_strategy.rb', line 28

def decrement(cache_key, amount, options)
  counter = counter(cache_key, options)
  counter[:bucket] -= amount
  counter[:bucket] = 0 unless counter[:bucket] > 0
  Prop::Limiter.cache.write(cache_key, counter)
  counter
end

.increment(cache_key, amount, options) ⇒ Object

WARNING: race condition this increment is not atomic, so it might miss counts when used frequently



21
22
23
24
25
26
# File 'lib/prop/leaky_bucket_strategy.rb', line 21

def increment(cache_key, amount, options)
  counter = counter(cache_key, options)
  counter[:bucket] += amount
  Prop::Limiter.cache.write(cache_key, counter)
  counter
end

.reset(cache_key) ⇒ Object



36
37
38
# File 'lib/prop/leaky_bucket_strategy.rb', line 36

def reset(cache_key)
  Prop::Limiter.cache.write(cache_key, zero_counter)
end

.threshold_reached(options) ⇒ Object



53
54
55
56
57
58
# File 'lib/prop/leaky_bucket_strategy.rb', line 53

def threshold_reached(options)
  burst_rate = options.fetch(:burst_rate)
  threshold  = options.fetch(:threshold)

  "#{options[:handle]} threshold of #{threshold} tries per #{options[:interval]}s and burst rate #{burst_rate} tries exceeded for key #{options[:key].inspect}, hash #{options[:cache_key]}"
end

.validate_options!(options) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
# File 'lib/prop/leaky_bucket_strategy.rb', line 60

def validate_options!(options)
  Prop::IntervalStrategy.validate_options!(options)

  if !options[:burst_rate].is_a?(Integer) || options[:burst_rate] < options[:threshold]
    raise ArgumentError.new(":burst_rate must be an Integer and not less than :threshold")
  end

  if options[:first_throttled]
    raise ArgumentError.new(":first_throttled is not supported")
  end
end

.zero_counterObject



72
73
74
# File 'lib/prop/leaky_bucket_strategy.rb', line 72

def zero_counter
  { bucket: 0, last_updated: 0 }
end