prorate 0.7.0 → 0.7.3

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
  SHA256:
3
- metadata.gz: bb2c78403fd3d37fd073ccf736618673e532c6fc53efd7e1c342c7edebc4037f
4
- data.tar.gz: 497b74b1d07d1590e44f338f7150b6b75fe5ed154af82d052381915a2b174c69
3
+ metadata.gz: 8756bab3df013de9ef7a962f66827fdf1e14a1c8415e226820dfb252ccc91c9a
4
+ data.tar.gz: f25dd4668ce05034652a596bfcc713bd8bb2bdeaf95b131d4c70527f9f2656f8
5
5
  SHA512:
6
- metadata.gz: d2a262971d745073dfd385088d92bf40667fa8108e6c3b71982b17fad41d6ee94472e40f8189badfa6131ac853a0dfe381e9bfbef93a0b7bbd24c3f39339251f
7
- data.tar.gz: 33f4f60558e7cee9fd671ebe48f4e35fc67e58c2ea5ad5cde5e40368b8b486e941eabb71ddf3dfaa87a9380689d31c059d56ea81cfa8860a955409d075840824
6
+ metadata.gz: 5c49fe55ba5c947742ca46246da145b18603f4417fbfae59ea2432df4670c6a46973ae7f307e1449d432dc61106226bcd214b008beac2bc2035da8c38f06bf34
7
+ data.tar.gz: 5f65de699f025029b9923e6b93f82cb040f83b2a3d31d888e6d74763c7e50ba043f5e588ee57cc577baf7d84e3feafa282883f425d3cf97b70c3d886e91d295e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 0.7.1
2
+
3
+ * Fix use of a ConnectionPool as `redis:` argument which was broken in 0.7.0
4
+ * Use the Lua KEYS argument in `rate_limit.lua` for future-proof clustering support
5
+ instead of computing the touched keys inside the Lua script.
6
+
1
7
  # 0.7.0
2
8
 
3
9
  * Add a naked `LeakyBucket` object which allows one to build sophisticated rate limiting relying
data/Rakefile CHANGED
@@ -9,6 +9,10 @@ YARD::Rake::YardocTask.new(:doc) do |t|
9
9
  t.files = ['lib/**/*.rb', '-', 'LICENSE.txt', 'CHANGELOG.md']
10
10
  end
11
11
 
12
- RSpec::Core::RakeTask.new(:spec)
12
+ RSpec::Core::RakeTask.new(:spec) do |spec|
13
+ spec.rspec_opts = ["-c", "--order=rand"]
14
+ spec.pattern = FileList['spec/**/*_spec.rb']
15
+ end
16
+
13
17
  RuboCop::RakeTask.new(:rubocop)
14
18
  task default: [:spec, :rubocop]
@@ -60,7 +60,7 @@ module Prorate
60
60
  # If your bucket is specific to a user, a browser or an IP address you need to mix in
61
61
  # those values into the key prefix as appropriate.
62
62
  # @param leak_rate[Float] the leak rate of the bucket, in tokens per second
63
- # @param redis[Redis,#with] a Redis connection or a ConnectonPool instance
63
+ # @param redis[Redis,#with] a Redis connection or a ConnectionPool instance
64
64
  # if you are using the connection_pool gem. With a connection pool Prorate will
65
65
  # checkout a connection using `#with` and check it in when it's done.
66
66
  # @param bucket_capacity[Numeric] how many tokens is the bucket capped at.
@@ -70,7 +70,7 @@ module Prorate
70
70
  # of 12, and will then immediately start leaking again.
71
71
  def initialize(redis_key_prefix:, leak_rate:, redis:, bucket_capacity:)
72
72
  @redis_key_prefix = redis_key_prefix
73
- @redis = NullPool.new(redis) unless redis.respond_to?(:with)
73
+ @redis = redis.respond_to?(:with) ? redis : NullPool.new(redis)
74
74
  @leak_rate = leak_rate.to_f
75
75
  @capacity = bucket_capacity.to_f
76
76
  end
@@ -8,13 +8,21 @@
8
8
  redis.replicate_commands()
9
9
  -- make some nicer looking variable names:
10
10
  local retval = nil
11
- local bucket_level_key = ARGV[1] .. ".bucket_level"
12
- local last_updated_key = ARGV[1] .. ".last_updated"
13
- local block_key = ARGV[1] .. ".block"
14
- local max_bucket_capacity = tonumber(ARGV[2])
15
- local leak_rate = tonumber(ARGV[3])
16
- local block_duration = tonumber(ARGV[4])
17
- local n_tokens = tonumber(ARGV[5]) -- How many tokens this call adds to the bucket. Defaults to 1
11
+
12
+ -- Redis documentation recommends passing the keys separately so that Redis
13
+ -- can - in the future - verify that they live on the same shard of a cluster, and
14
+ -- raise an error if they are not. As far as can be understood this functionality is not
15
+ -- yet present, but if we can make a little effort to make ourselves more future proof
16
+ -- we should.
17
+ local bucket_level_key = KEYS[1]
18
+ local last_updated_key = KEYS[2]
19
+ local block_key = KEYS[3]
20
+
21
+ -- and the config variables
22
+ local max_bucket_capacity = tonumber(ARGV[1])
23
+ local leak_rate = tonumber(ARGV[2])
24
+ local block_duration = tonumber(ARGV[3])
25
+ local n_tokens = tonumber(ARGV[4]) -- How many tokens this call adds to the bucket. Defaults to 1
18
26
 
19
27
  -- Take the Redis timestamp
20
28
  local redis_time = redis.call("TIME") -- Array of [seconds, microseconds]
@@ -23,7 +31,8 @@ local key_lifetime = math.ceil(max_bucket_capacity / leak_rate)
23
31
 
24
32
  local blocked_until = redis.call("GET", block_key)
25
33
  if blocked_until then
26
- return {(tonumber(blocked_until) - now), 0}
34
+ -- see https://redis.io/docs/manual/programmability/lua-api/ : floats should be returned as strings or they will be cast into integers
35
+ return {(tostring(tonumber(blocked_until) - now)), -1}
27
36
  end
28
37
 
29
38
  -- get current bucket level. The throttle key might not exist yet in which
@@ -41,7 +50,7 @@ if (new_bucket_level + n_tokens) <= max_bucket_capacity then
41
50
  retval = {0, math.ceil(new_bucket_level)}
42
51
  else
43
52
  redis.call("SETEX", block_key, block_duration, now + block_duration)
44
- retval = {block_duration, 0}
53
+ retval = {tostring(block_duration), -1}
45
54
  end
46
55
 
47
56
  -- Save the new bucket level
@@ -13,7 +13,7 @@ module Prorate
13
13
  def initialize(name:, limit:, period:, block_for:, redis:, logger: Prorate::NullLogger)
14
14
  @name = name.to_s
15
15
  @discriminators = [name.to_s]
16
- @redis = NullPool.new(redis) unless redis.respond_to?(:with)
16
+ @redis = redis.respond_to?(:with) ? redis : NullPool.new(redis)
17
17
  @logger = logger
18
18
  @block_for = block_for
19
19
 
@@ -95,9 +95,9 @@ module Prorate
95
95
  block_for: @block_for,
96
96
  n_tokens: n_tokens)
97
97
 
98
- if remaining_block_time > 0
98
+ if bucket_level == -1
99
99
  @logger.warn do
100
- "Throttle %s exceeded limit of %d in %d seconds and is blocked for the next %d seconds" % [@name, @limit, @period, remaining_block_time]
100
+ "Throttle %s exceeded limit of %d in %d seconds and is blocked for the next %s seconds" % [@name, @limit, @period, remaining_block_time]
101
101
  end
102
102
  raise ::Prorate::Throttled.new(@name, remaining_block_time)
103
103
  end
@@ -135,9 +135,15 @@ module Prorate
135
135
  end
136
136
 
137
137
  def run_lua_throttler(identifier:, bucket_capacity:, leak_rate:, block_for:, n_tokens:)
138
+ # Computing the identifier is somewhat involved so we should avoid doing it too often
139
+ id = identifier
140
+ bucket_level_key = "#{id}.bucket_level"
141
+ last_updated_key = "#{id}.last_updated"
142
+ block_key = "#{id}.block"
143
+
138
144
  @redis.with do |redis|
139
145
  begin
140
- redis.evalsha(LUA_SCRIPT_HASH, [], [identifier, bucket_capacity, leak_rate, block_for, n_tokens])
146
+ redis.evalsha(LUA_SCRIPT_HASH, keys: [bucket_level_key, last_updated_key, block_key], argv: [bucket_capacity, leak_rate, block_for, n_tokens])
141
147
  rescue Redis::CommandError => e
142
148
  if e.message.include? "NOSCRIPT"
143
149
  # The Redis server has never seen this script before. Needs to run only once in the entire lifetime
@@ -1,3 +1,3 @@
1
1
  module Prorate
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.3"
3
3
  end
data/prorate.gemspec CHANGED
@@ -34,5 +34,5 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency "rspec", "~> 3.0"
35
35
  spec.add_development_dependency 'wetransfer_style', '0.6.5'
36
36
  spec.add_development_dependency 'yard', '~> 0.9'
37
- spec.add_development_dependency 'pry', '~> 0.13.1'
37
+ spec.add_development_dependency 'pry', '~> 0.14.2'
38
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prorate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-17 00:00:00.000000000 Z
11
+ date: 2023-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 0.13.1
117
+ version: 0.14.2
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 0.13.1
124
+ version: 0.14.2
125
125
  description: Can be used to implement all kinds of throttles
126
126
  email:
127
127
  - me@julik.nl
@@ -173,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
173
  - !ruby/object:Gem::Version
174
174
  version: '0'
175
175
  requirements: []
176
- rubygems_version: 3.0.3
176
+ rubygems_version: 3.0.3.1
177
177
  signing_key:
178
178
  specification_version: 4
179
179
  summary: Time-restricted rate limiter using Redis