ruby-limiter 1.0.1 → 2.2.2

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
- SHA1:
3
- metadata.gz: c8b855660b5c5b40e96437d66444cbba63b07af6
4
- data.tar.gz: f4486818e1c73464c3b6545ee35601282a1cb798
2
+ SHA256:
3
+ metadata.gz: 8414ef377bee89d172c77d547a9cc0468bbfbf5a4a2b775ffa8faa137fdbed5b
4
+ data.tar.gz: 9065acd27f48647174b0cce72bb919869addb4dca0e53e8b5e5d446dfaff53d0
5
5
  SHA512:
6
- metadata.gz: 53f3886fe4a1f71c5f7454eebfbc764b4880cca6ab1026bfadc3e2ee16709a95c5f01e4e2ff732b49b8a0332ffba06206715fda1561032d0916c47850f289f89
7
- data.tar.gz: 4b33aeea30a63521f97adbaa290604cc176788e7a8ea4ea643a94bf08604ce779b53fcb36b9ec8f44c6a5d1d684897d1596635e21c4c3d6ee4cbdd8f3c8443a9
6
+ metadata.gz: 58dbd481ed08a6ed0ab24664c94488acb2206590de98f6cc06b4770acd797d1744a39d51a362893b7162381edc6da005ee7ec247cdec1fab4f7d9d4686da8355
7
+ data.tar.gz: 90c28e01feb7964814da473d1bddbaca86a0167f9d580aa9ac3281fafff73bca8c8e7dbda4147a56fc53ad56b8f424f523c881294e888f810760ad6c47f44624
data/.rubocop.yml CHANGED
@@ -2,4 +2,4 @@ inherit_from:
2
2
  - https://shopify.github.io/ruby-style-guide/rubocop.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 2.3
5
+ TargetRubyVersion: 2.7
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3
5
- - 2.4
6
- - 2.5
4
+ - 2.6
5
+ - 2.7
6
+ - 3.0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v2.2.2
4
+
5
+ - security update to rake 13.0.6 [CVE-2020-8130]
6
+
7
+ ## v2.2.0
8
+
9
+ - adds support for "balancing" requests over time
10
+
11
+ ## v2.1.0
12
+
13
+ - add support to call a block when limiting takes place
14
+
15
+ ## v2.0.1
16
+
17
+ - eliminate kwarg warning in ruby 2.7 (while still supporting 2.6)
18
+
19
+ ## v2.0.0
20
+
21
+ - end support for ruby 2.3/2.4/2.5
22
+ - test on ruby 2.6/2.7/3.0 (using ruby 2.7 for development)
23
+
24
+ ## v1.1.0
25
+
26
+ - using Process.clock_gettime(Process::CLOCK_MONOTONIC) instead of Time.now for improved accuracy
27
+
28
+ ## v1.0.2
29
+
30
+ - DOCFIX: fix name of gem in README
31
+ - BUGFIX: add ruby-limiter.rb so that it works better with bundler
32
+
3
33
  ## v1.0.1
4
34
 
5
35
  - BUGFIX: support arguments for throttled methods
data/README.md CHANGED
@@ -7,7 +7,7 @@ This gem implements a simple mechanism to throttle or rate-limit operations in R
7
7
  Add this line to your application's Gemfile:
8
8
 
9
9
  ```ruby
10
- gem 'limiter'
10
+ gem 'ruby-limiter'
11
11
  ```
12
12
 
13
13
  And then execute:
@@ -16,13 +16,13 @@ And then execute:
16
16
 
17
17
  Or install it yourself as:
18
18
 
19
- $ gem install limiter
19
+ $ gem install ruby-limiter
20
20
 
21
21
  ## Usage
22
22
 
23
23
  ### Basic Usage
24
24
 
25
- To rate limit calling an instance method, a mixin is provided. Simply specify the method to me limited, and the maximum
25
+ To rate limit calling an instance method, a mixin is provided. Simply specify the method to be limited, and the maximum
26
26
  rate that the method can be called. This rate is (by default) a number of requests per minute.
27
27
 
28
28
  ``` ruby
@@ -46,22 +46,49 @@ class Widget
46
46
 
47
47
  # limit the rate we can call tick to 5 times per second
48
48
  # when the rate has been exceeded, a call to tick will block until the rate limit would not be exceeded
49
- limit_method :tick, rate: 5, interval: 1
49
+ # and the provided block will be executed
50
+ limit_method(:tick, rate: 5, interval: 1) do
51
+ puts 'Limit reached'
52
+ end
50
53
 
51
54
  ...
52
55
  end
53
56
  ```
54
57
 
58
+ #### Load balancing
59
+
60
+ By default all calls to the `limit_method` will be bursted, e.g. as quick as possible, until the rate is exceeded.
61
+ Then we wait for the remainder of the interval to continue. To even out the burst, an optional `balanced` parameter can be
62
+ provided to enable interleaving between the method calls, e.g: `interleave = interval / size`.
63
+
64
+ ``` ruby
65
+ ...
66
+ limit_method :tick, rate: 60, balanced: true
67
+ ...
68
+ ```
69
+
70
+ For example: with an interval of 60 seconds and a rate of 60:
71
+
72
+ `balanced: false`
73
+ : As quickly as possible we call the method 60 times, then we wait for the remainder of the time.
74
+
75
+ `balanced: true`
76
+ : We interleave each call with 1 second so we call this method every second.
77
+
78
+
55
79
  ### Advanced Usage
56
80
 
57
81
  In cases where the mixin is not appropriate the `RateQueue` class can be used directly. As in the mixin examples above,
58
- the `interval` parameter is optional (and defaults to 1 minute).
82
+ the `interval` parameter is optional (and defaults to 1 minute). It is also possible
83
+ to provide the block to `RateQueue`, which will be executed on each limit hit (useful for metrics).
59
84
 
60
85
  ``` ruby
61
86
  class Widget
62
87
  def initialize
63
88
  # create a rate-limited queue which allows 10000 operations per hour
64
- @queue = RateQueue.new(10000, interval: 3600)
89
+ @queue = Limiter::RateQueue.new(10000, interval: 3600) do
90
+ puts "Hit the limit, waiting"
91
+ end
65
92
  end
66
93
 
67
94
  def tick
data/Rakefile CHANGED
@@ -7,6 +7,7 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.libs << "test"
8
8
  t.libs << "lib"
9
9
  t.test_files = FileList["test/**/*_test.rb"]
10
+ t.warning = true
10
11
  end
11
12
 
12
- task default: :test
13
+ task default: :test
data/dev.yml CHANGED
@@ -3,7 +3,7 @@
3
3
  name: limiter
4
4
 
5
5
  up:
6
- - ruby: 2.4.4
6
+ - ruby: 2.7.3
7
7
  - bundler
8
8
 
9
9
  commands:
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'forwardable'
5
+
6
+ module Limiter
7
+ class Clock
8
+ include Singleton
9
+
10
+ extend SingleForwardable
11
+ def_single_delegators :instance, :sleep, :time
12
+
13
+ def sleep(interval)
14
+ Kernel.sleep(interval)
15
+ end
16
+
17
+ def time
18
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
+ end
20
+ end
21
+ end
data/lib/limiter/mixin.rb CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  module Limiter
4
4
  module Mixin
5
- def limit_method(method, rate:, interval: 60)
6
- queue = RateQueue.new(rate, interval: interval)
5
+ def limit_method(method, rate:, interval: 60, balanced: false, &b)
6
+ queue = RateQueue.new(rate, interval: interval, balanced: balanced, &b)
7
7
 
8
8
  mixin = Module.new do
9
- define_method(method) do |*args|
9
+ define_method(method) do |*args, **options, &blk|
10
10
  queue.shift
11
- super(*args)
11
+ options.empty? ? super(*args, &blk) : super(*args, **options, &blk)
12
12
  end
13
13
  end
14
14
 
@@ -2,15 +2,16 @@
2
2
 
3
3
  module Limiter
4
4
  class RateQueue
5
- EPOCH = Time.at(0)
5
+ EPOCH = 0.0
6
6
 
7
- def initialize(size, interval: 60)
7
+ def initialize(size, interval: 60, balanced: false, &blk)
8
8
  @size = size
9
9
  @interval = interval
10
10
 
11
- @ring = Array.new(size, EPOCH)
11
+ @ring = balanced ? balanced_ring : unbalanced_ring
12
12
  @head = 0
13
13
  @mutex = Mutex.new
14
+ @blk = blk
14
15
  end
15
16
 
16
17
  def shift
@@ -21,7 +22,7 @@ module Limiter
21
22
 
22
23
  sleep_until(time + @interval)
23
24
 
24
- @ring[@head] = Time.now
25
+ @ring[@head] = clock.time
25
26
  @head = (@head + 1) % @size
26
27
  end
27
28
 
@@ -31,9 +32,30 @@ module Limiter
31
32
  private
32
33
 
33
34
  def sleep_until(time)
34
- interval = time - Time.now
35
+ interval = time - clock.time
35
36
  return unless interval.positive?
36
- sleep(interval)
37
+ @blk.call if @blk
38
+ clock.sleep(interval)
39
+ end
40
+
41
+ def clock
42
+ Clock
43
+ end
44
+
45
+ def unbalanced_ring
46
+ Array.new(@size, EPOCH)
47
+ end
48
+
49
+ def balanced_ring
50
+ (0...@size).map { |i| base_time + (gap * i) }
51
+ end
52
+
53
+ def gap
54
+ @interval.to_f / @size.to_f
55
+ end
56
+
57
+ def base_time
58
+ clock.time - @interval
37
59
  end
38
60
  end
39
61
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Limiter
4
- VERSION = '1.0.1'
4
+ VERSION = '2.2.2'
5
5
  end
data/lib/limiter.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'limiter/clock'
3
4
  require 'limiter/mixin'
4
5
  require 'limiter/rate_queue'
5
6
  require 'limiter/version'
@@ -0,0 +1 @@
1
+ require 'limiter'
data/limiter.gemspec CHANGED
@@ -13,6 +13,13 @@ Gem::Specification.new do |spec|
13
13
  spec.summary = 'Simple Ruby rate limiting mechanism.'
14
14
  spec.homepage = 'https://github.com/Shopify/limiter'
15
15
  spec.license = 'MIT'
16
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
17
+
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22
+ end
16
23
 
17
24
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
25
  f.match(%r{^(test|spec|features)/})
@@ -21,9 +28,10 @@ Gem::Specification.new do |spec|
21
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
29
  spec.require_paths = %w(lib)
23
30
 
24
- spec.add_development_dependency 'bundler', '~> 1.15'
31
+ spec.add_development_dependency 'bundler'
25
32
  spec.add_development_dependency 'minitest', '~> 5.0'
26
- spec.add_development_dependency 'rake', '~> 10.0'
33
+ spec.add_development_dependency 'minitest-focus', '~> 1.3'
34
+ spec.add_development_dependency 'mocha', '~> 1.11'
35
+ spec.add_development_dependency 'rake', '~> 13.0'
27
36
  spec.add_development_dependency 'rubocop', '~> 0.56'
28
- spec.add_development_dependency 'timecop', '~> 0.8.0'
29
37
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-limiter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - S. Brent Faulkner
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-07-06 00:00:00.000000000 Z
11
+ date: 2022-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.15'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.15'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -39,47 +39,61 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '5.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rake
42
+ name: minitest-focus
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: '1.3'
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: '10.0'
54
+ version: '1.3'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rubocop
56
+ name: mocha
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.56'
61
+ version: '1.11'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.56'
68
+ version: '1.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: timecop
84
+ name: rubocop
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: 0.8.0
89
+ version: '0.56'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: 0.8.0
96
+ version: '0.56'
83
97
  description:
84
98
  email:
85
99
  - brent.faulkner@shopify.com
@@ -100,14 +114,17 @@ files:
100
114
  - bin/setup
101
115
  - dev.yml
102
116
  - lib/limiter.rb
117
+ - lib/limiter/clock.rb
103
118
  - lib/limiter/mixin.rb
104
119
  - lib/limiter/rate_queue.rb
105
120
  - lib/limiter/version.rb
121
+ - lib/ruby-limiter.rb
106
122
  - limiter.gemspec
107
123
  homepage: https://github.com/Shopify/limiter
108
124
  licenses:
109
125
  - MIT
110
- metadata: {}
126
+ metadata:
127
+ allowed_push_host: https://rubygems.org
111
128
  post_install_message:
112
129
  rdoc_options: []
113
130
  require_paths:
@@ -116,15 +133,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
133
  requirements:
117
134
  - - ">="
118
135
  - !ruby/object:Gem::Version
119
- version: '0'
136
+ version: 2.6.0
120
137
  required_rubygems_version: !ruby/object:Gem::Requirement
121
138
  requirements:
122
139
  - - ">="
123
140
  - !ruby/object:Gem::Version
124
141
  version: '0'
125
142
  requirements: []
126
- rubyforge_project:
127
- rubygems_version: 2.6.14
143
+ rubygems_version: 3.2.20
128
144
  signing_key:
129
145
  specification_version: 4
130
146
  summary: Simple Ruby rate limiting mechanism.