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 +4 -4
- data/README.md +21 -7
- data/lib/speed_limiter/errors/limit_exceeded_error.rb +35 -0
- data/lib/speed_limiter/errors/throttled_error.rb +11 -0
- data/lib/speed_limiter/state.rb +13 -1
- data/lib/speed_limiter/throttle.rb +14 -4
- data/lib/speed_limiter/throttle_params.rb +50 -0
- data/lib/speed_limiter/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 573c75da906a932bbc66fb90a6b2dbeaf7aabe12773178c28b197c81de4be9f0
|
4
|
+
data.tar.gz: f01dd67d1f923a5d63a29d67239f6a0a348a1236396a9e14ebefcf4702e51198
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
data/lib/speed_limiter/state.rb
CHANGED
@@ -19,7 +19,19 @@ module SpeedLimiter
|
|
19
19
|
|
20
20
|
attr_reader :params, :count, :ttl
|
21
21
|
|
22
|
-
|
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] =>
|
29
|
+
delegate %i[redis_client] => :@config
|
26
30
|
|
27
|
-
delegate %i[
|
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
|
-
|
76
|
-
|
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
|
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.
|
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-
|
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
|