speed_limiter 0.1.0 → 0.2.1

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
  SHA256:
3
- metadata.gz: 49d4e898db4491c876e3cdf95a157aa2b9b832444c10a62598dae6214204e864
4
- data.tar.gz: f6c223a5b30ab88f844de353e9d4a893d40e629c7602ab521bf59d30a6e314e8
3
+ metadata.gz: dcefe51efe099102f75381a902006faa8655f7207c7c971e0e0b0f356dc9c5e0
4
+ data.tar.gz: c3a3647ea48bb31a95dcd1bc70ddcb72c0402e4474c459e88271666021ba878f
5
5
  SHA512:
6
- metadata.gz: eb26517138fca5d75e5a43a45765ccc48b54586e9bc38b7b644663a8954f7102a513d36e6a8e94575561ad575256c52aa2c1931f481c1f65c64a6a83c663be08
7
- data.tar.gz: 54edbdd81701def3bdc0c1ac738c835198245ca187cfe53393e3887c6a59691332d957d7c0d8cd7362feb3ba18e440524d075bbb493a7b61fc21c929de90a86f
6
+ metadata.gz: bcfa6c8194738f83b7f688cc8211a79d10d8cee564a9b5099694c2730e71a1305c3d78dedad1b06654105d7da4b9070c9abb9d98aee97eca566eeaff32aed531
7
+ data.tar.gz: cb88e0be3f91a53919d019009c35cadad7e50a46e719f06405b13912f24f195030a06686e7d28241d30a26bdf516946b7f1ca37cb429473bda189815f85cc1c7
@@ -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
 
@@ -14,5 +14,9 @@ module SpeedLimiter
14
14
  end
15
15
 
16
16
  alias no_limit? no_limit
17
+
18
+ def redis_client
19
+ @redis_client ||= SpeedLimiter::Redis.new(@redis || ::Redis.new(url: redis_url))
20
+ end
17
21
  end
18
22
  end
@@ -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,27 +9,58 @@ 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[redis_client] => :config
26
+
27
+ delegate %i[key redis_key limit period on_throttled create_state] => :params
28
+
29
+ # @yield [state]
30
+ # @yieldparam state [SpeedLimiter::State]
31
+ # @return [any] block return value
32
+ def call(&block)
33
+ if use_retryable?
34
+ Retryable.retryable(**retryable_options) { run_block(&block) }
35
+ else
36
+ run_block(&block)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def use_retryable?
43
+ return false if params.retry == false || params.retry.nil?
44
+
45
+ unless Gem::Specification.find_by_name("retryable")
46
+ raise ArgumentError, "To use the 'retry' option, you need to install the Retryable gem."
47
+ end
27
48
 
28
- def throttle
49
+ require "retryable"
50
+ params.retry.is_a?(Hash) || params.retry == true
51
+ end
52
+
53
+ def retryable_options
54
+ return {} if params.retry == true
55
+
56
+ params.retry
57
+ end
58
+
59
+ def run_block(&block)
29
60
  return block.call(create_state) if config.no_limit?
30
61
 
31
62
  loop do
32
- count, ttl = redis.increment(redis_key, period)
63
+ count, ttl = redis_client.increment(redis_key, period)
33
64
 
34
65
  break(block.call(create_state(count: count, ttl: ttl))) if count <= limit
35
66
 
@@ -37,23 +68,17 @@ module SpeedLimiter
37
68
  end
38
69
  end
39
70
 
40
- private
41
-
42
71
  def wait_for_interval(count)
43
- ttl = redis.ttl(redis_key)
72
+ ttl = redis_client.ttl(redis_key)
44
73
  return if ttl.negative?
45
74
 
46
75
  config.on_throttled.call(create_state(count: count, ttl: ttl)) if config.on_throttled.respond_to?(:call)
47
76
  on_throttled.call(create_state(count: count, ttl: ttl)) if on_throttled.respond_to?(:call)
48
77
 
49
- ttl = redis.ttl(redis_key)
78
+ ttl = redis_client.ttl(redis_key)
50
79
  return if ttl.negative?
51
80
 
52
81
  sleep ttl
53
82
  end
54
-
55
- def redis
56
- @redis ||= SpeedLimiter::Redis.new(config.redis || ::Redis.new(url: config.redis_url))
57
- end
58
83
  end
59
84
  end
@@ -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.1"
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.1
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