prorate 0.3.0 → 0.4.0

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
  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