ruby-limiter 1.0.1 → 2.2.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 +5 -5
- data/.rubocop.yml +1 -1
- data/.travis.yml +3 -3
- data/CHANGELOG.md +30 -0
- data/README.md +33 -6
- data/Rakefile +2 -1
- data/dev.yml +1 -1
- data/lib/limiter/clock.rb +21 -0
- data/lib/limiter/mixin.rb +4 -4
- data/lib/limiter/rate_queue.rb +28 -6
- data/lib/limiter/version.rb +1 -1
- data/lib/limiter.rb +1 -0
- data/lib/ruby-limiter.rb +1 -0
- data/limiter.gemspec +11 -3
- metadata +35 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8414ef377bee89d172c77d547a9cc0468bbfbf5a4a2b775ffa8faa137fdbed5b
|
4
|
+
data.tar.gz: 9065acd27f48647174b0cce72bb919869addb4dca0e53e8b5e5d446dfaff53d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58dbd481ed08a6ed0ab24664c94488acb2206590de98f6cc06b4770acd797d1744a39d51a362893b7162381edc6da005ee7ec247cdec1fab4f7d9d4686da8355
|
7
|
+
data.tar.gz: 90c28e01feb7964814da473d1bddbaca86a0167f9d580aa9ac3281fafff73bca8c8e7dbda4147a56fc53ad56b8f424f523c881294e888f810760ad6c47f44624
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
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
|
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
|
-
|
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
data/dev.yml
CHANGED
@@ -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
|
|
data/lib/limiter/rate_queue.rb
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
|
3
3
|
module Limiter
|
4
4
|
class RateQueue
|
5
|
-
EPOCH =
|
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 =
|
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] =
|
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 -
|
35
|
+
interval = time - clock.time
|
35
36
|
return unless interval.positive?
|
36
|
-
|
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
|
data/lib/limiter/version.rb
CHANGED
data/lib/limiter.rb
CHANGED
data/lib/ruby-limiter.rb
ADDED
@@ -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'
|
31
|
+
spec.add_development_dependency 'bundler'
|
25
32
|
spec.add_development_dependency 'minitest', '~> 5.0'
|
26
|
-
spec.add_development_dependency '
|
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:
|
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:
|
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: '
|
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: '
|
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:
|
42
|
+
name: minitest-focus
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
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: '
|
54
|
+
version: '1.3'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: mocha
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
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: '
|
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:
|
84
|
+
name: rubocop
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - "~>"
|
74
88
|
- !ruby/object:Gem::Version
|
75
|
-
version: 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.
|
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:
|
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
|
-
|
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.
|