speed_limiter 0.1.0 → 0.2.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
  SHA256:
3
- metadata.gz: 49d4e898db4491c876e3cdf95a157aa2b9b832444c10a62598dae6214204e864
4
- data.tar.gz: f6c223a5b30ab88f844de353e9d4a893d40e629c7602ab521bf59d30a6e314e8
3
+ metadata.gz: b360a3c2df8c37b8ac47eec08222e784ef475966c5938c93f1206e56c2ec353d
4
+ data.tar.gz: 34a890cdbdf74a0fcb25b2140c124ec6f6487ebf2aec4115084fabbc2a437ddd
5
5
  SHA512:
6
- metadata.gz: eb26517138fca5d75e5a43a45765ccc48b54586e9bc38b7b644663a8954f7102a513d36e6a8e94575561ad575256c52aa2c1931f481c1f65c64a6a83c663be08
7
- data.tar.gz: 54edbdd81701def3bdc0c1ac738c835198245ca187cfe53393e3887c6a59691332d957d7c0d8cd7362feb3ba18e440524d075bbb493a7b61fc21c929de90a86f
6
+ metadata.gz: 0ed1f8c6f934e2955b5fd398f6cead553eff69e3e93cb2816a6a3cb41f61fe0848980b0fe1310d17f531ce8f4266de80b0599b7416d1c4d4d8bbda9a61f92c43
7
+ data.tar.gz: 6ef932d30fc72b180b2e968b3f57e75621b15d36b1afc33488cd80a2fa1e0c26802650db1902f97fac29c4294905f12abe17fdb1c99b7200772f77f7c5bab89e
@@ -12,7 +12,7 @@ jobs:
12
12
  timeout-minutes: 10
13
13
 
14
14
  steps:
15
- - uses: actions/checkout@v3
15
+ - uses: actions/checkout@v4
16
16
 
17
17
  - name: Set up Ruby
18
18
  uses: ruby/setup-ruby@v1
@@ -20,22 +20,39 @@ jobs:
20
20
  strategy:
21
21
  max-parallel: 6
22
22
  matrix:
23
- ruby-version: ['3.0', '3.1', '3.2', ruby-head]
24
- redis-version: ['5.0', '6.0', '6.2', '7.0', '7.2', latest]
23
+ set:
24
+ - ruby-version: '3.0'
25
+ redis-version: '7.2'
26
+ - ruby-version: '3.1'
27
+ redis-version: '7.2'
28
+ - ruby-version: '3.2'
29
+ redis-version: '5.0'
30
+ - ruby-version: '3.2'
31
+ redis-version: '6.0'
32
+ - ruby-version: '3.2'
33
+ redis-version: '6.2'
34
+ - ruby-version: '3.2'
35
+ redis-version: '7.0'
36
+ - ruby-version: '3.2'
37
+ redis-version: '7.2'
38
+ - ruby-version: '3.2'
39
+ redis-version: latest
40
+ - ruby-version: ruby-head
41
+ redis-version: '7.2'
25
42
 
26
43
  services:
27
44
  redis:
28
- image: redis:${{ matrix.redis-version }}
45
+ image: redis:${{ matrix.set.redis-version }}
29
46
  ports:
30
47
  - 6379:6379
31
48
 
32
49
  steps:
33
- - uses: actions/checkout@v3
50
+ - uses: actions/checkout@v4
34
51
 
35
- - name: Set up Ruby ${{ matrix.ruby-version }}
52
+ - name: Set up Ruby ${{ matrix.set.ruby-version }}
36
53
  uses: ruby/setup-ruby@v1
37
54
  with:
38
- ruby-version: ${{ matrix.ruby-version }}
55
+ ruby-version: ${{ matrix.set.ruby-version }}
39
56
  bundler-cache: true
40
57
 
41
58
  - name: Rackup test web server
data/Gemfile CHANGED
@@ -13,6 +13,7 @@ group :development do
13
13
  gem "rack-attack", "~> 6.7"
14
14
  gem "rackup", "~> 2.1"
15
15
  gem "rake"
16
+ gem "retryable"
16
17
  gem "rspec"
17
18
  gem "rubocop"
18
19
  gem "rubocop-rake"
data/README.md CHANGED
@@ -35,6 +35,15 @@ SpeedLimiter.throttle('server_name/method_name', limit: 10, period: 1) do |state
35
35
  puts state #=> <SpeedLimiter::State key=server_name/method_name count=1 ttl=0>
36
36
  http.get(path)
37
37
  end
38
+
39
+ # or
40
+
41
+ throttle_limit_10_par_sec = SpeedLimiter.throttle('server_name/method_name', limit: 10, period: 1)
42
+
43
+ throttle_limit_10_par_sec.call do |state|
44
+ puts state #=> <SpeedLimiter::State key=server_name/method_name count=1 ttl=0>
45
+ http.get(path)
46
+ end
38
47
  ```
39
48
 
40
49
  It returns the result of the block execution.
@@ -46,6 +55,8 @@ end
46
55
  puts result.code #=> 200
47
56
  ```
48
57
 
58
+ ### on_throttled option
59
+
49
60
  Specify the process when the limit is exceeded.
50
61
 
51
62
  ```ruby
@@ -73,9 +84,40 @@ class CreateSlackChannelJob < ApplicationJob
73
84
  end
74
85
  ```
75
86
 
76
- ### Configuration
87
+ ### retry option
88
+
89
+ To use the `retry:` option, you need to introduce the [Retryable gem](https://github.com/nfedyashev/retryable).
90
+ By specifying the options of the Retryable gem, you can retry when an exception occurs.
91
+
92
+ ```ruby
93
+ # Gemfile
94
+ gem 'retryable'
95
+ ```
96
+
97
+ ```ruby
98
+ SpeedLimiter.throttle('server_name/method_name', limit: 10, period: 1, retry: { tries: 3, on: OpenURI::HTTPError }) do
99
+ http.get(path)
100
+ end
101
+
102
+ # equivalent to
103
+ SpeedLimiter.throttle('server_name/method_name', limit: 10, period: 1) do
104
+ Retryable.retryable(tries: 3, on: OpenURI::HTTPError) do
105
+ http.get(path)
106
+ end
107
+ end
108
+ ```
109
+
110
+ `retry: true` or `retry: {}` is default use of `Retryable.configure`.
111
+
112
+ ```ruby
113
+ SpeedLimiter.throttle('server_name/method_name', limit: 10, period: 1, retry: true) do
114
+ http.get(path)
115
+ end
116
+ ```
117
+
118
+ ## Configuration
77
119
 
78
- #### Redis configuration
120
+ ### Redis configuration
79
121
 
80
122
  Redis can be specified as follows
81
123
 
@@ -99,7 +141,7 @@ SpeedLimiter.configure do |config|
99
141
  end
100
142
  ```
101
143
 
102
- #### Other configuration defaults
144
+ ### Other configuration defaults
103
145
 
104
146
  ```ruby
105
147
  SpeedLimiter.configure do |config|
@@ -111,7 +153,7 @@ SpeedLimiter.configure do |config|
111
153
  end
112
154
  ```
113
155
 
114
- #### Example
156
+ ### Example
115
157
 
116
158
  If you do not want to impose a limit in the test environment, please set it as follows.
117
159
 
@@ -8,6 +8,9 @@ module SpeedLimiter
8
8
  class State
9
9
  extend Forwardable
10
10
 
11
+ # @param params [SpeedLimiter::ThrottleParams]
12
+ # @param count [Integer] current count
13
+ # @param ttl [Float] remaining time to reset
11
14
  def initialize(params:, count:, ttl:)
12
15
  @params = params
13
16
  @count = count
@@ -16,7 +19,7 @@ module SpeedLimiter
16
19
 
17
20
  attr_reader :params, :count, :ttl
18
21
 
19
- def_delegators(:params, :config, :key, :limit, :period, :on_throttled)
22
+ def_delegators(:params, :config, :key, :limit, :period, :on_throttled, :retry)
20
23
 
21
24
  def inspect
22
25
  "<#{self.class.name} key=#{key.inspect} count=#{count} ttl=#{ttl}>"
@@ -9,23 +9,52 @@ module SpeedLimiter
9
9
  class Throttle
10
10
  extend Forwardable
11
11
 
12
- # @option params [String] :key key name
12
+ # @param key [String, #to_s] Throttle key name
13
13
  # @option params [Integer] :limit limit count per period
14
14
  # @option params [Integer] :period period time (seconds)
15
- # @option params [Proc] :on_throttled Block called when limit exceeded, with ttl(Float) and key as argument
16
- # @yield [count] Block called to not reach limit
17
- # @yieldparam count [Integer] count of period
18
- # @yieldreturn [any] block return value
19
- def initialize(config:, **params, &block)
15
+ # @option params [Proc, #call] :on_throttled Block called when limit exceeded, with ttl(Float) and key as argument
16
+ # @option params [true, Hash] :retry Retry options. (see {Retryable.retryable} for details)
17
+ def initialize(key, config:, **params)
18
+ params[:key] = key.to_s
19
+
20
20
  @config = config
21
21
  @params = ThrottleParams.new(config: config, **params)
22
- @block = block
23
22
  end
24
23
  attr_reader :config, :params, :block
25
24
 
26
- def_delegators(:params, :key, :redis_key, :limit, :period, :on_throttled, :create_state)
25
+ delegate %i[key redis_key limit period on_throttled create_state] => :params
26
+
27
+ # @yield [state]
28
+ # @yieldparam state [SpeedLimiter::State]
29
+ # @return [any] block return value
30
+ def call(&block)
31
+ if use_retryable?
32
+ Retryable.retryable(**retryable_options) { run_block(&block) }
33
+ else
34
+ run_block(&block)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def use_retryable?
41
+ return false if params.retry == false || params.retry.nil?
42
+
43
+ unless Gem::Specification.find_by_name("retryable")
44
+ raise ArgumentError, "To use the 'retry' option, you need to install the Retryable gem."
45
+ end
46
+
47
+ require "retryable"
48
+ params.retry.is_a?(Hash) || params.retry == true
49
+ end
50
+
51
+ def retryable_options
52
+ return {} if params.retry == true
27
53
 
28
- def throttle
54
+ params.retry
55
+ end
56
+
57
+ def run_block(&block)
29
58
  return block.call(create_state) if config.no_limit?
30
59
 
31
60
  loop do
@@ -37,8 +66,6 @@ module SpeedLimiter
37
66
  end
38
67
  end
39
68
 
40
- private
41
-
42
69
  def wait_for_interval(count)
43
70
  ttl = redis.ttl(redis_key)
44
71
  return if ttl.negative?
@@ -5,15 +5,23 @@ require "speed_limiter/state"
5
5
  module SpeedLimiter
6
6
  # Throttle params model
7
7
  class ThrottleParams
8
- def initialize(config:, key:, limit:, period:, on_throttled: nil)
8
+ def initialize(config:, key:, limit:, period:, **options)
9
9
  @config = config
10
10
  @key = key
11
11
  @limit = limit
12
12
  @period = period
13
- @on_throttled = on_throttled
13
+ @options = options
14
14
  end
15
15
 
16
- attr_reader :config, :key, :limit, :period, :on_throttled
16
+ attr_reader :config, :key, :limit, :period
17
+
18
+ def on_throttled
19
+ @options[:on_throttled]
20
+ end
21
+
22
+ def retry
23
+ @options[:retry]
24
+ end
17
25
 
18
26
  def redis_key
19
27
  "#{config.prefix}:#{key}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SpeedLimiter
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/speed_limiter.rb CHANGED
@@ -16,19 +16,19 @@ module SpeedLimiter
16
16
  yield(config)
17
17
  end
18
18
 
19
- def redis
20
- @redis ||= config.redis || Redis.new(url: config.redis_url)
21
- end
22
-
23
- # @param key [String] key name
24
- # @param limit [Integer] limit count per period
25
- # @param period [Integer] period time (seconds)
26
- # @param on_throttled [Proc] Block called when limit exceeded, with ttl(Float) and key as argument
27
- # @yield [count] Block called to not reach limit
28
- # @yieldparam count [Integer] count of period
29
- # @yieldreturn [any] block return value
19
+ # @param key (see Throttle#initialize)
20
+ # @option (see Throttle#initialize)
21
+ # @yield (see Throttle#call)
22
+ # @yieldparam (see Throttle#call)
23
+ # @return Return value of block if argument contains block, otherwise Throttle instance
30
24
  def throttle(key, **params, &block)
31
- Throttle.new(config: config, key: key, **params, &block).throttle
25
+ throttle = Throttle.new(key, config: config, **params)
26
+
27
+ if block
28
+ throttle.call(&block)
29
+ else
30
+ throttle
31
+ end
32
32
  end
33
33
  end
34
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: speed_limiter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yuhei mukoyama
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-16 00:00:00.000000000 Z
11
+ date: 2024-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
75
  - !ruby/object:Gem::Version
76
76
  version: '0'
77
77
  requirements: []
78
- rubygems_version: 3.2.22
78
+ rubygems_version: 3.5.7
79
79
  signing_key:
80
80
  specification_version: 4
81
81
  summary: Limit the frequency of execution across multiple threads and processes