speed_limiter 0.2.1 → 0.3.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
  SHA256:
3
- metadata.gz: dcefe51efe099102f75381a902006faa8655f7207c7c971e0e0b0f356dc9c5e0
4
- data.tar.gz: c3a3647ea48bb31a95dcd1bc70ddcb72c0402e4474c459e88271666021ba878f
3
+ metadata.gz: 573c75da906a932bbc66fb90a6b2dbeaf7aabe12773178c28b197c81de4be9f0
4
+ data.tar.gz: f01dd67d1f923a5d63a29d67239f6a0a348a1236396a9e14ebefcf4702e51198
5
5
  SHA512:
6
- metadata.gz: bcfa6c8194738f83b7f688cc8211a79d10d8cee564a9b5099694c2730e71a1305c3d78dedad1b06654105d7da4b9070c9abb9d98aee97eca566eeaff32aed531
7
- data.tar.gz: cb88e0be3f91a53919d019009c35cadad7e50a46e719f06405b13912f24f195030a06686e7d28241d30a26bdf516946b7f1ca37cb429473bda189815f85cc1c7
6
+ metadata.gz: f02a8e3fd56bd84f5bf5cf3eb3b24336a9805d04cf89f01fb53dcbc745125010e555f54bb2494689e05a0527819ab094d0eb461cb0cb3f876db0c423f684b123
7
+ data.tar.gz: c61501a41484331f0041b665407c1b0ff609f84f31ffac3f4b9725162aecdc1960f10ab8207cbce6a4a54b4790abdc532de70241e22eaaf2bc1db28833b97492
data/README.md CHANGED
@@ -66,20 +66,34 @@ SpeedLimiter.throttle('server_name/method_name', limit: 10, period: 1, on_thrott
66
66
  end
67
67
  ```
68
68
 
69
+ ### raise_on_throttled option
70
+
71
+ It raises an exception when the limit is exceeded.
72
+
73
+ ```ruby
74
+ begin
75
+ SpeedLimiter.throttle('server_name/method_name', limit: 10, period: 1, raise_on_throttled: true) do
76
+ http.get(path)
77
+ end
78
+ rescue SpeedLimiter::ThrottledError => e
79
+ logger.info(e.message) #=> "server_name/method_name rate limit exceeded. Retry after 0.9 seconds. limit=10, count=11, period=1"
80
+ e.state #=> <SpeedLimiter::State key=server_name/method_name count=11 ttl=0.9>
81
+ end
82
+ ```
83
+
69
84
  Reinitialize the queue instead of sleeping when the limit is reached in ActiveJob.
70
85
 
71
86
  ```ruby
72
87
  class CreateSlackChannelJob < ApplicationJob
73
- def perform(*args)
74
- on_throttled = proc do |state|
75
- raise Slack::LimitExceeded, state.ttl if state.ttl > 5
76
- end
88
+ rescue_from(SpeedLimiter::ThrottledError) do |e|
89
+ Rails.logger.warn("[#{e.class}] #{self.class} retry job. #{e.message}")
90
+ retry_job(wait: e.ttl, queue: 'low')
91
+ end
77
92
 
78
- SpeedLimiter.throttle("slack", limit: 20, period: 1.minute, on_throttled: on_throttled) do
93
+ def perform(*args)
94
+ SpeedLimiter.throttle("slack", limit: 20, period: 1.minute, raise_on_throttled: true) do
79
95
  create_slack_channel(*args)
80
96
  end
81
- rescue Slack::LimitExceeded => e
82
- self.class.set(wait: e.ttl).perform_later(*args)
83
97
  end
84
98
  end
85
99
  ```
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module SpeedLimiter
6
+ module Errors
7
+ # SpeedLimiter limit exceeded Base Error
8
+ class LimitExceededError < StandardError
9
+ extend Forwardable
10
+
11
+ # @param state [SpeedLimiter::State]
12
+ def initialize(state)
13
+ @state = state
14
+ super(error_message)
15
+ end
16
+ attr_reader :state
17
+
18
+ # @!method key
19
+ # @see SpeedLimiter::State#key
20
+ # @!method ttl
21
+ # @see SpeedLimiter::State#ttl
22
+ # @!method count
23
+ # @see SpeedLimiter::State#count
24
+ # @!method limit
25
+ # @see SpeedLimiter::State#limit
26
+ # @!method period
27
+ # @see SpeedLimiter::State#period
28
+ delegate %i[key ttl count limit period] => :@state
29
+
30
+ def error_message
31
+ "#{key} rate limit exceeded. Retry after #{ttl} seconds. limit=#{limit}, count=#{count}, period=#{period}"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "speed_limiter/errors/limit_exceeded_error"
4
+
5
+ module SpeedLimiter
6
+ module Errors
7
+ # SpeedLimiter::Throttled limit exceeded Error
8
+ class ThrottledError < LimitExceededError
9
+ end
10
+ end
11
+ end
@@ -19,7 +19,19 @@ module SpeedLimiter
19
19
 
20
20
  attr_reader :params, :count, :ttl
21
21
 
22
- def_delegators(:params, :config, :key, :limit, :period, :on_throttled, :retry)
22
+ # @!method config
23
+ # @see SpeedLimiter::ThrottleParams#config
24
+ # @!method key
25
+ # @see SpeedLimiter::ThrottleParams#key
26
+ # @!method limit
27
+ # @see SpeedLimiter::ThrottleParams#limit
28
+ # @!method period
29
+ # @see SpeedLimiter::ThrottleParams#period
30
+ # @!method on_throttled
31
+ # @see SpeedLimiter::ThrottleParams#on_throttled
32
+ # @!method retry
33
+ # @see SpeedLimiter::ThrottleParams#retry
34
+ delegate %i[config key limit period on_throttled retry] => :@params
23
35
 
24
36
  def inspect
25
37
  "<#{self.class.name} key=#{key.inspect} count=#{count} ttl=#{ttl}>"
@@ -13,6 +13,10 @@ module SpeedLimiter
13
13
  # @option params [Integer] :limit limit count per period
14
14
  # @option params [Integer] :period period time (seconds)
15
15
  # @option params [Proc, #call] :on_throttled Block called when limit exceeded, with ttl(Float) and key as argument
16
+ # @option params [true, Class] :raise_on_throttled
17
+ # Raise error when limit exceeded. If Class is given, it will be raised instead of SpeedLimiter::ThrottledError.
18
+ # If you want to specify a custom error class, please specify a class that inherits from
19
+ # SpeedLimiter::LimitExceededError or a class that accepts SpeedLimiter::State as an argument.
16
20
  # @option params [true, Hash] :retry Retry options. (see {Retryable.retryable} for details)
17
21
  def initialize(key, config:, **params)
18
22
  params[:key] = key.to_s
@@ -22,9 +26,11 @@ module SpeedLimiter
22
26
  end
23
27
  attr_reader :config, :params, :block
24
28
 
25
- delegate %i[redis_client] => :config
29
+ delegate %i[redis_client] => :@config
26
30
 
27
- delegate %i[key redis_key limit period on_throttled create_state] => :params
31
+ delegate %i[
32
+ key redis_key limit period on_throttled raise_on_throttled_class raise_on_throttled? create_state
33
+ ] => :@params
28
34
 
29
35
  # @yield [state]
30
36
  # @yieldparam state [SpeedLimiter::State]
@@ -72,8 +78,12 @@ module SpeedLimiter
72
78
  ttl = redis_client.ttl(redis_key)
73
79
  return if ttl.negative?
74
80
 
75
- config.on_throttled.call(create_state(count: count, ttl: ttl)) if config.on_throttled.respond_to?(:call)
76
- on_throttled.call(create_state(count: count, ttl: ttl)) if on_throttled.respond_to?(:call)
81
+ create_state(count: count, ttl: ttl).tap do |state|
82
+ raise raise_on_throttled_class, state if raise_on_throttled?
83
+
84
+ config.on_throttled.call(state) if config.on_throttled.respond_to?(:call)
85
+ on_throttled.call(state) if on_throttled.respond_to?(:call)
86
+ end
77
87
 
78
88
  ttl = redis_client.ttl(redis_key)
79
89
  return if ttl.negative?
@@ -1,32 +1,82 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "speed_limiter/state"
4
+ require "speed_limiter/errors/throttled_error"
4
5
 
5
6
  module SpeedLimiter
6
7
  # Throttle params model
7
8
  class ThrottleParams
9
+ KNOWN_OPTIONS = %i[on_throttled retry raise_on_throttled].freeze
10
+
11
+ # @param config [SpeedLimiter::Config]
12
+ # @param key [String]
13
+ # @param limit [Integer] limit count per period
14
+ # @param period [Integer] period time (seconds)
15
+ # @param options [Hash] options
16
+ # @option options [Proc, #call] :on_throttled Block called when limit exceeded, with ttl(Float) and key as argument
17
+ # @option options [true, Class] :raise_on_throttled
18
+ # Raise error when limit exceeded. If Class is given, it will be raised instead of SpeedLimiter::ThrottledError.
19
+ # If you want to specify a custom error class, please specify a class that inherits from
20
+ # SpeedLimiter::LimitExceededError or a class that accepts SpeedLimiter::State as an argument.
21
+ # @option options [true, Hash] :retry Retry options. (see {Retryable.retryable} for details)
8
22
  def initialize(config:, key:, limit:, period:, **options)
9
23
  @config = config
10
24
  @key = key
11
25
  @limit = limit
12
26
  @period = period
13
27
  @options = options
28
+
29
+ return unless (unknown_options = options.keys - KNOWN_OPTIONS).any?
30
+
31
+ raise ArgumentError, "Unknown options: #{unknown_options.join(', ')}"
14
32
  end
15
33
 
34
+ # @!method config
35
+ # @return [SpeedLimiter::Config]
36
+ # @!method key
37
+ # @return [String] Throttle key name
38
+ # @!method limit
39
+ # @return [Integer] limit count per period
40
+ # @!method period
41
+ # @return [Integer] period time (seconds)
16
42
  attr_reader :config, :key, :limit, :period
17
43
 
18
44
  def on_throttled
19
45
  @options[:on_throttled]
20
46
  end
21
47
 
48
+ # @return [Boolean, Class]
49
+ def raise_on_throttled
50
+ @options[:raise_on_throttled]
51
+ end
52
+
53
+ # @return [Boolean]
54
+ def raise_on_throttled?
55
+ !!raise_on_throttled
56
+ end
57
+
58
+ # @return [Class]
59
+ def raise_on_throttled_class
60
+ if raise_on_throttled.is_a?(Class)
61
+ raise_on_throttled
62
+ else
63
+ SpeedLimiter::Errors::ThrottledError
64
+ end
65
+ end
66
+
67
+ # @return [Boolean, Hash]
22
68
  def retry
23
69
  @options[:retry]
24
70
  end
25
71
 
72
+ # @return [String]
26
73
  def redis_key
27
74
  "#{config.prefix}:#{key}"
28
75
  end
29
76
 
77
+ # @param count [Integer, nil]
78
+ # @param ttl [Float, nil]
79
+ # @return [SpeedLimiter::State]
30
80
  def create_state(count: nil, ttl: nil)
31
81
  State.new(params: self, count: count, ttl: ttl)
32
82
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SpeedLimiter
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.0"
5
5
  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.2.1
4
+ version: 0.3.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: 2024-04-13 00:00:00.000000000 Z
11
+ date: 2024-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -45,6 +45,8 @@ files:
45
45
  - compose.yaml
46
46
  - lib/speed_limiter.rb
47
47
  - lib/speed_limiter/config.rb
48
+ - lib/speed_limiter/errors/limit_exceeded_error.rb
49
+ - lib/speed_limiter/errors/throttled_error.rb
48
50
  - lib/speed_limiter/redis.rb
49
51
  - lib/speed_limiter/state.rb
50
52
  - lib/speed_limiter/throttle.rb