Class: Pecorino::Throttle
- Inherits:
-
Object
- Object
- Pecorino::Throttle
- Defined in:
- lib/pecorino/throttle.rb
Overview
Provides a throttle with a block based on the LeakyBucket. Once a bucket fills up,
a block will be installed and an exception will be raised. Once a block is set, no
checks will be done on the leaky bucket - any further requests will be refused until
the block is lifted. The block time can be arbitrarily higher or lower than the amount
of time it takes for the leaky bucket to leak out
Defined Under Namespace
Instance Attribute Summary collapse
-
#key ⇒ String
readonly
The key for that throttle.
Instance Method Summary collapse
-
#able_to_accept?(n_tokens = 1) ⇒ boolean
Tells whether the throttle will let this number of requests pass without raising a Throttled.
-
#initialize(key:, block_for: nil, adapter: Pecorino.adapter, **leaky_bucket_options) ⇒ Throttle
constructor
A new instance of Throttle.
-
#request(n = 1) ⇒ State
Register that a request is being performed.
-
#request!(n = 1) ⇒ State
Register that a request is being performed.
-
#throttled(&blk) ⇒ Object
Fillup the throttle with 1 request and then perform the passed block.
Constructor Details
#initialize(key:, block_for: nil, adapter: Pecorino.adapter, **leaky_bucket_options) ⇒ Throttle
Returns a new instance of Throttle.
105 106 107 108 109 110 111 |
# File 'lib/pecorino/throttle.rb', line 105 def initialize(key:, block_for: nil, adapter: Pecorino.adapter, **) @adapter = adapter .delete(:adapter) @bucket = Pecorino::LeakyBucket.new(key: key, adapter: @adapter, **) @key = key.to_s @block_for = block_for ? block_for.to_f : (@bucket.capacity / @bucket.leak_rate) end |
Instance Attribute Details
#key ⇒ String (readonly)
The key for that throttle. Each key defines a unique throttle based on either a given name or
discriminators. If there is a component you want to key your throttle by, include it in the
key keyword argument to the constructor, like "t-ip-#{your_rails_request.ip}"
97 98 99 |
# File 'lib/pecorino/throttle.rb', line 97 def key @key end |
Instance Method Details
#able_to_accept?(n_tokens = 1) ⇒ boolean
Tells whether the throttle will let this number of requests pass without raising
a Throttled. Note that this is not race-safe. Another request could overflow the bucket
after you call able_to_accept? but before you call throttle!. So before performing
the action you still need to call throttle!. You may still use able_to_accept? to
provide better UX to your users before they cause an action that would otherwise throttle.
121 122 123 |
# File 'lib/pecorino/throttle.rb', line 121 def able_to_accept?(n_tokens = 1) @adapter.blocked_until(key: @key).nil? && @bucket.able_to_accept?(n_tokens) end |
#request(n = 1) ⇒ State
Register that a request is being performed. Will not raise any exceptions but return the time at which the block will be lifted if a block resulted from this request or was already in effect. Can be used for registering actions which already took place, but should result in subsequent actions being blocked.
161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/pecorino/throttle.rb', line 161 def request(n = 1) existing_blocked_until = Pecorino::Block.blocked_until(key: @key, adapter: @adapter) return State.new(existing_blocked_until.utc) if existing_blocked_until # Topup the leaky bucket, and if the topup gets rejected - block the caller fillup = @bucket.fillup_conditionally(n) if fillup.accepted? State.new(nil) else # and set the block if the fillup was rejected fresh_blocked_until = Pecorino::Block.set!(key: @key, block_for: @block_for, adapter: @adapter) State.new(fresh_blocked_until.utc) end end |
#request!(n = 1) ⇒ State
Register that a request is being performed. Will raise Throttled if there is a block in place for that throttle, or if the bucket cannot accept this fillup and the block has just been installed as a result of this particular request.
The exception can be rescued later to provide a 429 response. This method is better to use before performing the unit of work that the throttle is guarding:
If the method call returns it means that the request is not getting throttled.
143 144 145 146 147 |
# File 'lib/pecorino/throttle.rb', line 143 def request!(n = 1) request(n).tap do |state_after| raise Throttled.new(self, state_after) if state_after.blocked? end end |
#throttled(&blk) ⇒ Object
Fillup the throttle with 1 request and then perform the passed block. This is useful to perform actions which should be rate-limited - alerts, calls to external services and the like. If the call is allowed to proceed, the passed block will be executed. If the throttle is in the blocked state or if the call puts the throttle in the blocked state the block will not be executed
186 187 188 189 |
# File 'lib/pecorino/throttle.rb', line 186 def throttled(&blk) return if request(1).blocked? yield end |