prorate 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb2c78403fd3d37fd073ccf736618673e532c6fc53efd7e1c342c7edebc4037f
4
- data.tar.gz: 497b74b1d07d1590e44f338f7150b6b75fe5ed154af82d052381915a2b174c69
3
+ metadata.gz: 941127ba358ced7518e1c69224680aadb4beceac96127013f77f26dd8013619d
4
+ data.tar.gz: 9e517111477fc854e1404bedbd89126d0002f3d98386091180e757d9dcfdf992
5
5
  SHA512:
6
- metadata.gz: d2a262971d745073dfd385088d92bf40667fa8108e6c3b71982b17fad41d6ee94472e40f8189badfa6131ac853a0dfe381e9bfbef93a0b7bbd24c3f39339251f
7
- data.tar.gz: 33f4f60558e7cee9fd671ebe48f4e35fc67e58c2ea5ad5cde5e40368b8b486e941eabb71ddf3dfaa87a9380689d31c059d56ea81cfa8860a955409d075840824
6
+ metadata.gz: 6478f0fb8c2df29254cae40e47ba5ffaa6bf09fce03543907edce681056e342eed6fdca57b4025449081c7c768f435ecb9e33cb2a76cc727500dd34b63c7b65c
7
+ data.tar.gz: 0e2a1376cd72013f9511b87a01143003c8ffe7a093004b9dbe13130008fef006e65452d1000e6998d997599f11120b883bca5f09eaeab5462b3eb6297f5e3cf7
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,7 +95,7 @@ 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
100
  "Throttle %s exceeded limit of %d in %d seconds and is blocked for the next %d seconds" % [@name, @limit, @period, remaining_block_time]
101
101
  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.2"
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.2
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-24 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