prop 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 850a96785d6d002acd243a5edf5cca965bd46bd1
4
- data.tar.gz: 9ffb56011562feb0d9cf2c5ab62705420d59d46e
3
+ metadata.gz: d0fe432ea00485ac163d99cc47cc62d515bf67fb
4
+ data.tar.gz: a6edac3e0e50fdaed49664fbdb4bd46419e81a27
5
5
  SHA512:
6
- metadata.gz: 8cfee5d2c8af78a8a9273bdc873ba83ee6bb81237ca7cb253fce98dbf6180cff96f1004759e5d2fc4ee8e01364dddfae7f4b94c3909f547b08bcd3b109c9d7d2
7
- data.tar.gz: 61e6d22651a9017e827d34220511fd8dd6dc60acaa69908968082d71a0ee6926859c0509f08b12fdf5a4d4310f83b95d0cf7b4137f713323d70fb88defcd0bbc
6
+ metadata.gz: 67390eaadb265153db7845785824a3f27b3bcb97db533b277d14b44a121c70deca9105bee5565541dabd53e374f67adff6da539346ea4f98f5bc8d730d156960
7
+ data.tar.gz: 66e0df0d0db24d5baf3ca3791b36dd0c9bf2f5bacc8d56637fb96d0f47266788483b752292e3b07337efc2d03c2e30d1b72e8330051ff64fa37984906566ad38
data/README.md CHANGED
@@ -227,6 +227,8 @@ You can add two additional configurations: `:strategy` and `:burst_rate` to use
227
227
  Prop will handle the details after configured, and you don't have to specify `:strategy`
228
228
  again when using `throttle`, `throttle!` or any other methods.
229
229
 
230
+ The leaky bucket algorithm used is "leaky bucket as a meter".
231
+
230
232
  ```ruby
231
233
  Prop.configure(:api_request, strategy: :leaky_bucket, burst_rate: 20, threshold: 5, interval: 1.minute)
232
234
  ```
@@ -3,7 +3,7 @@ require "prop/limiter"
3
3
  require "forwardable"
4
4
 
5
5
  module Prop
6
- VERSION = "2.4.0"
6
+ VERSION = "2.5.0"
7
7
 
8
8
  # Short hand for accessing Prop::Limiter methods
9
9
  class << self
@@ -5,40 +5,69 @@ require 'prop/key'
5
5
  module Prop
6
6
  class LeakyBucketStrategy
7
7
  class << self
8
+ def _throttle_leaky_bucket(handle, key, cache_key, options)
9
+ (over_limit, bucket) = options.key?(:decrement) ?
10
+ decrement(cache_key, options.fetch(:decrement), options) :
11
+ increment(cache_key, options.fetch(:increment, 1), options)
12
+
13
+ [over_limit, bucket]
14
+ end
15
+
8
16
  def counter(cache_key, options)
9
- bucket = Prop::Limiter.cache.read(cache_key) || zero_counter
10
- now = Time.now.to_i
11
- leak_rate = (now - bucket.fetch(:last_updated)) / options.fetch(:interval).to_f
12
- leak_amount = leak_rate * options.fetch(:threshold)
17
+ cache.read(cache_key) || zero_counter
18
+ end
19
+
20
+ def leak_amount(bucket, amount, options, now)
21
+ leak_rate = (now - bucket.fetch(:last_leak_time, 0)) / options.fetch(:interval).to_f
22
+ leak_amount = (leak_rate * options.fetch(:threshold).to_f)
23
+ leak_amount.to_i
24
+ end
13
25
 
14
- bucket[:bucket] = [(bucket.fetch(:bucket) - leak_amount).to_i, 0].max
15
- bucket[:last_updated] = now
16
- bucket
26
+ def update_bucket(current_bucket_size, max_bucket_size, amount)
27
+ over_limit = (max_bucket_size-current_bucket_size) < amount
28
+ updated_bucket_size = over_limit ? current_bucket_size : current_bucket_size + amount
29
+ [over_limit, updated_bucket_size]
17
30
  end
18
31
 
19
32
  # WARNING: race condition
20
33
  # this increment is not atomic, so it might miss counts when used frequently
21
34
  def increment(cache_key, amount, options)
22
- counter = counter(cache_key, options)
23
- counter[:bucket] += amount
24
- Prop::Limiter.cache.write(cache_key, counter)
25
- counter
35
+ bucket = counter(cache_key, options)
36
+ now = Time.now.to_i
37
+ max_bucket_size = options.fetch(:burst_rate)
38
+ current_bucket_size = bucket.fetch(:bucket, 0)
39
+ leak_amount = leak_amount(bucket, amount, options, now)
40
+ if leak_amount > 0
41
+ # maybe TODO, update last_leak_time to reflect the exact time for the current leak amount
42
+ # the current strategy will always reflect a little less leakage, probably not an issue though
43
+ bucket[:last_leak_time] = now
44
+ current_bucket_size = [(current_bucket_size - leak_amount), 0].max
45
+ end
46
+
47
+ over_limit, updated_bucket_size = update_bucket(current_bucket_size, max_bucket_size, amount)
48
+ bucket[:bucket] = updated_bucket_size
49
+ bucket[:over_limit] = over_limit
50
+ cache.write(cache_key, bucket)
51
+ [over_limit, bucket]
26
52
  end
27
53
 
28
54
  def decrement(cache_key, amount, options)
29
- counter = counter(cache_key, options)
30
- counter[:bucket] -= amount
31
- counter[:bucket] = 0 unless counter[:bucket] > 0
32
- Prop::Limiter.cache.write(cache_key, counter)
33
- counter
55
+ now = Time.now.to_i
56
+ bucket = counter(cache_key, options)
57
+ leak_amount = leak_amount(bucket, amount, options, now)
58
+ bucket[:bucket] = [bucket[:bucket] - amount - leak_amount, 0].max
59
+ bucket[:last_leak_time] = now if leak_amount > 0
60
+ bucket[:over_limit] = false
61
+ cache.write(cache_key, bucket)
62
+ [false, bucket]
34
63
  end
35
64
 
36
65
  def reset(cache_key)
37
- Prop::Limiter.cache.write(cache_key, zero_counter)
66
+ cache.write(cache_key, zero_counter, raw: true)
38
67
  end
39
68
 
40
- def compare_threshold?(counter, operator, options)
41
- counter.fetch(:bucket).to_i.send operator, options.fetch(:burst_rate)
69
+ def compare_threshold?(bucket, operator, options)
70
+ bucket.fetch(:over_limit, false)
42
71
  end
43
72
 
44
73
  def build(options)
@@ -70,7 +99,11 @@ module Prop
70
99
  end
71
100
 
72
101
  def zero_counter
73
- { bucket: 0, last_updated: 0 }
102
+ { bucket: 0, last_leak_time: 0, over_limit: false }
103
+ end
104
+
105
+ def cache
106
+ Prop::Limiter.cache
74
107
  end
75
108
  end
76
109
  end
@@ -142,9 +142,17 @@ module Prop
142
142
 
143
143
  private
144
144
 
145
+ def leaky_bucket_strategy?(strategy)
146
+ strategy == Prop::LeakyBucketStrategy
147
+ end
148
+
145
149
  def _throttle(strategy, handle, key, cache_key, options)
146
150
  return [false, strategy.zero_counter] if disabled?
147
151
 
152
+ if leaky_bucket_strategy?(strategy)
153
+ return Prop::LeakyBucketStrategy._throttle_leaky_bucket(handle, key, cache_key, options)
154
+ end
155
+
148
156
  counter = options.key?(:decrement) ?
149
157
  strategy.decrement(cache_key, options.fetch(:decrement), options) :
150
158
  strategy.increment(cache_key, options.fetch(:increment, 1), options)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prop
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Morten Primdahl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-22 00:00:00.000000000 Z
11
+ date: 2020-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -116,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
116
  version: '0'
117
117
  requirements: []
118
118
  rubyforge_project:
119
- rubygems_version: 2.6.14.4
119
+ rubygems_version: 2.6.14
120
120
  signing_key:
121
121
  specification_version: 4
122
122
  summary: Gem for implementing rate limits.