prorate 0.3.0 → 0.4.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: db453351faca0b61a4517795368fe719fe5c07bb
4
- data.tar.gz: 165c6088be69a4a3b291059aa4e872061e5f999f
3
+ metadata.gz: 415042776ee7b18bf44e586fe148c822a44746bd
4
+ data.tar.gz: 7e249159cd2b18b699fc1f7b4fcb0f3923f2082d
5
5
  SHA512:
6
- metadata.gz: b9dd96de6c8915e8ef39f7737e930976d4f83909b8eb861456966ede6a2d62cd82f0b40b634af8da824bbd303d84e83205924d6ceb51369a17e82e6eec01523f
7
- data.tar.gz: 5e349bc7288a6da431d9ef7177fc77f2041638ace47fe319af1e533846472fe234e1760ccd3d26bb208ad3e649eb99ecbed7733dc14871f53f63e19dd4b512f7
6
+ metadata.gz: f84dc40f3b0d789a2a8a3b0d9739b958958d895c2a43d2a60265a325465c46923ec8b5ce39fbf94d7942ce806315ffcb19bf39d4de9039853285aadfa8ecb933
7
+ data.tar.gz: 9335ecd1f3c25f082515dc5a7396aee2670748e6f287b481ef723699baaf355e5b67d0e23123721aefca894f0a2dd2e03065700ef581cad1b6bf9a64f963a1f9
@@ -0,0 +1,2 @@
1
+ inherit_gem:
2
+ wetransfer_style: ruby/default.yml
@@ -10,6 +10,5 @@ dist: trusty # https://docs.travis-ci.com/user/trusty-ci-environment/
10
10
  sudo: false
11
11
  cache: bundler
12
12
 
13
- # Travis permits the following phases: before_install, install, after_install, before_script, script, after_script
14
13
  script:
15
- - bundle exec rspec
14
+ - bundle exec rake
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
+ require 'rubocop/rake_task'
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
6
+ RuboCop::RakeTask.new(:rubocop)
7
+ task default: [:spec, :rubocop]
@@ -1,10 +1,15 @@
1
1
  module Prorate
2
2
  module NullLogger
3
3
  def self.debug(*); end
4
+
4
5
  def self.info(*); end
6
+
5
7
  def self.warn(*); end
8
+
6
9
  def self.error(*); end
10
+
7
11
  def self.fatal(*); end
12
+
8
13
  def self.unknown(*); end
9
14
  end
10
15
  end
@@ -1,5 +1,7 @@
1
1
  module Prorate
2
2
  class NullPool < Struct.new(:conn)
3
- def with; yield conn; end
3
+ def with
4
+ yield conn
5
+ end
4
6
  end
5
7
  end
@@ -1,14 +1,6 @@
1
1
  require 'digest'
2
2
 
3
3
  module Prorate
4
- class Throttled < StandardError
5
- attr_reader :retry_in_seconds
6
- def initialize(try_again_in)
7
- @retry_in_seconds = try_again_in
8
- super("Throttled, please lower your temper and try again in #{retry_in_seconds} seconds")
9
- end
10
- end
11
-
12
4
  class ScriptHashMismatch < StandardError
13
5
  end
14
6
 
@@ -16,49 +8,48 @@ module Prorate
16
8
  end
17
9
 
18
10
  class Throttle < Ks.strict(:name, :limit, :period, :block_for, :redis, :logger)
19
-
20
- def self.get_script_hash
21
- script_filepath = File.join(__dir__,"rate_limit.lua")
11
+ def self.lua_script_hash
12
+ script_filepath = File.join(__dir__, "rate_limit.lua")
22
13
  script = File.read(script_filepath)
23
14
  Digest::SHA1.hexdigest(script)
24
15
  end
25
16
 
26
- CURRENT_SCRIPT_HASH = get_script_hash
17
+ CURRENT_SCRIPT_HASH = lua_script_hash
27
18
 
28
19
  def initialize(*)
29
20
  super
30
21
  @discriminators = [name.to_s]
31
22
  self.redis = NullPool.new(redis) unless redis.respond_to?(:with)
32
- raise MisconfiguredThrottle if ((period <= 0) || (limit <= 0))
23
+ raise MisconfiguredThrottle if (period <= 0) || (limit <= 0)
33
24
  @leak_rate = limit.to_f / period # tokens per second;
34
25
  end
35
-
26
+
36
27
  def <<(discriminator)
37
28
  @discriminators << discriminator
38
29
  end
39
-
30
+
40
31
  def throttle!
41
32
  discriminator = Digest::SHA1.hexdigest(Marshal.dump(@discriminators))
42
33
  identifier = [name, discriminator].join(':')
43
-
34
+
44
35
  redis.with do |r|
45
36
  logger.info { "Applying throttle counter %s" % name }
46
37
  remaining_block_time, bucket_level = run_lua_throttler(redis: r, identifier: identifier, bucket_capacity: limit, leak_rate: @leak_rate, block_for: block_for)
47
38
 
48
39
  if remaining_block_time > 0
49
40
  logger.warn { "Throttle %s exceeded limit of %d in %d seconds and is blocked for the next %d seconds" % [name, limit, period, remaining_block_time] }
50
- raise Throttled.new(remaining_block_time)
41
+ raise ::Prorate::Throttled.new(name, remaining_block_time)
51
42
  end
52
- available_calls = limit - bucket_level
43
+ return limit - bucket_level # How many calls remain
53
44
  end
54
45
  end
55
46
 
56
- def run_lua_throttler(redis: , identifier: , bucket_capacity: , leak_rate: , block_for: )
47
+ def run_lua_throttler(redis:, identifier:, bucket_capacity:, leak_rate:, block_for:)
57
48
  redis.evalsha(CURRENT_SCRIPT_HASH, [], [identifier, bucket_capacity, leak_rate, block_for])
58
49
  rescue Redis::CommandError => e
59
50
  if e.message.include? "NOSCRIPT"
60
51
  # The Redis server has never seen this script before. Needs to run only once in the entire lifetime of the Redis server (unless the script changes)
61
- script_filepath = File.join(__dir__,"rate_limit.lua")
52
+ script_filepath = File.join(__dir__, "rate_limit.lua")
62
53
  script = File.read(script_filepath)
63
54
  raise ScriptHashMismatch if Digest::SHA1.hexdigest(script) != CURRENT_SCRIPT_HASH
64
55
  redis.script(:load, script)
@@ -0,0 +1,8 @@
1
+ class Prorate::Throttled < StandardError
2
+ attr_reader :throttle_name, :retry_in_seconds
3
+ def initialize(throttle_name, try_again_in)
4
+ @throttle_name = throttle_name
5
+ @retry_in_seconds = try_again_in
6
+ super("Throttled, please lower your temper and try again in #{retry_in_seconds} seconds")
7
+ end
8
+ end
@@ -1,3 +1,3 @@
1
1
  module Prorate
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,4 +1,4 @@
1
- # coding: utf-8
1
+
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'prorate/version'
@@ -29,8 +29,9 @@ Gem::Specification.new do |spec|
29
29
 
30
30
  spec.add_dependency "ks"
31
31
  spec.add_dependency "redis", ">= 2"
32
- spec.add_development_dependency "connection_pool", "~> 1"
32
+ spec.add_development_dependency "connection_pool", "~> 2"
33
33
  spec.add_development_dependency "bundler", "~> 1.12"
34
- spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "rake", "~> 12.3"
35
35
  spec.add_development_dependency "rspec", "~> 3.0"
36
+ spec.add_development_dependency 'wetransfer_style', '0.6.0'
36
37
  end
@@ -6,7 +6,7 @@ require 'redis'
6
6
  require 'securerandom'
7
7
 
8
8
  def average_ms(ary)
9
- ary.map{|x| x*1000}.inject(0,&:+) / ary.length
9
+ ary.map { |x| x * 1000 }.inject(0, &:+) / ary.length
10
10
  end
11
11
 
12
12
  r = Redis.new
@@ -6,7 +6,7 @@ require 'redis'
6
6
  require 'securerandom'
7
7
 
8
8
  def average_ms(ary)
9
- ary.map{|x| x*1000}.inject(0,&:+) / ary.length
9
+ ary.map { |x| x * 1000 }.inject(0, &:+) / ary.length
10
10
  end
11
11
 
12
12
  r = Redis.new
@@ -31,24 +31,23 @@ end
31
31
  puts average_ms times
32
32
  def key_for_ts(ts)
33
33
  "th:%s:%d" % [@id, ts]
34
- end
34
+ end
35
35
 
36
36
  times = []
37
37
  15.times do
38
- id = SecureRandom.hex(10)
39
38
  sec, _ = r.time # Use Redis time instead of the system timestamp, so that all the nodes are consistent
40
39
  ts = sec.to_i # All Redis results are strings
41
40
  k = key_for_ts(ts)
42
41
  times << Benchmark.realtime {
43
42
  r.multi do |txn|
44
- # Increment the counter
43
+ # Increment the counter
45
44
  txn.incr(k)
46
45
  txn.expire(k, 120)
47
46
 
48
47
  span_start = ts - 120
49
48
  span_end = ts + 1
50
- possible_keys = (span_start..span_end).map{|prev_time| key_for_ts(prev_time) }
51
-
49
+ possible_keys = (span_start..span_end).map { |prev_time| key_for_ts(prev_time) }
50
+
52
51
  # Fetch all the counter values within the time window. Despite the fact that this
53
52
  # will return thousands of elements for large sliding window sizes, the values are
54
53
  # small and an MGET in Redis is pretty cheap, so perf should stay well within limits.
@@ -58,4 +57,3 @@ times = []
58
57
  end
59
58
 
60
59
  puts average_ms times
61
-
@@ -2,5 +2,5 @@
2
2
  require 'redis'
3
3
  r = Redis.new
4
4
  script = File.read('../lib/prorate/rate_limit.lua')
5
- sha = r.script(:load,script)
5
+ sha = r.script(:load, script)
6
6
  puts sha
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-07-18 00:00:00.000000000 Z
11
+ date: 2019-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ks
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1'
47
+ version: '2'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1'
54
+ version: '2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '10.0'
75
+ version: '12.3'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '10.0'
82
+ version: '12.3'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rspec
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: wetransfer_style
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 0.6.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 0.6.0
97
111
  description: Can be used to implement all kinds of throttles
98
112
  email:
99
113
  - me@julik.nl
@@ -103,6 +117,7 @@ extra_rdoc_files: []
103
117
  files:
104
118
  - ".gitignore"
105
119
  - ".rspec"
120
+ - ".rubocop.yml"
106
121
  - ".travis.yml"
107
122
  - Gemfile
108
123
  - LICENSE.txt
@@ -115,6 +130,7 @@ files:
115
130
  - lib/prorate/null_pool.rb
116
131
  - lib/prorate/rate_limit.lua
117
132
  - lib/prorate/throttle.rb
133
+ - lib/prorate/throttled.rb
118
134
  - lib/prorate/version.rb
119
135
  - prorate.gemspec
120
136
  - scripts/bm.rb
@@ -141,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
157
  version: '0'
142
158
  requirements: []
143
159
  rubyforge_project:
144
- rubygems_version: 2.4.5.1
160
+ rubygems_version: 2.6.11
145
161
  signing_key:
146
162
  specification_version: 4
147
163
  summary: Time-restricted rate limiter using Redis