Class: Restrainer
- Inherits:
-
Object
- Object
- Restrainer
- Defined in:
- lib/restrainer.rb
Overview
Redis backed throttling mechanism to ensure that only a limited number of processes can be executed at any one time.
Usage:
Restrainer.new(:foo, 10).throttle do
# Do something
end
If more than the specified number of processes as identified by the name argument is currently running, then the throttle block will raise an error.
Defined Under Namespace
Classes: ThrottledError
Instance Attribute Summary collapse
-
#limit ⇒ Object
readonly
Returns the value of attribute limit.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Class Method Summary collapse
-
.redis(&block) ⇒ Object
Either configure the redis instance using a block or yield the instance.
-
.redis=(conn) ⇒ Object
Set the redis instance to a specific instance.
Instance Method Summary collapse
-
#current(redis = nil) ⇒ Object
Get the number of processes currently being executed for this restrainer.
-
#initialize(name, limit:, timeout: 60) ⇒ Restrainer
constructor
Create a new restrainer.
-
#throttle(limit: nil) ⇒ Object
Wrap a block with this method to throttle concurrent execution.
Constructor Details
#initialize(name, limit:, timeout: 60) ⇒ Restrainer
Create a new restrainer. The name is used to identify the Restrainer and group processes together. You can create any number of Restrainers with different names.
The required limit parameter specifies the maximum number of processes that will be allowed to execute the throttle block at any point in time.
The timeout parameter is used for cleaning up internal data structures so that jobs aren’t orphaned if their process is killed. Processes will automatically be removed from the running jobs list after the specified number of seconds. Note that the Restrainer will not handle timing out any code itself. This value is just used to insure the integrity of internal data structures.
54 55 56 57 58 59 |
# File 'lib/restrainer.rb', line 54 def initialize(name, limit:, timeout: 60) @name = name @limit = limit @timeout = timeout @key = "#{self.class.name}.#{name.to_s}" end |
Instance Attribute Details
#limit ⇒ Object (readonly)
Returns the value of attribute limit.
16 17 18 |
# File 'lib/restrainer.rb', line 16 def limit @limit end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
16 17 18 |
# File 'lib/restrainer.rb', line 16 def name @name end |
Class Method Details
.redis(&block) ⇒ Object
Either configure the redis instance using a block or yield the instance. Configuring with a block allows you to use things like connection pools etc. without hard coding a single instance.
Example: ‘Restrainer.redis{ redis_pool.instance }
27 28 29 30 31 32 33 34 35 |
# File 'lib/restrainer.rb', line 27 def redis(&block) if block @redis = block elsif defined?(@redis) && @redis @redis.call else raise "#{self.class.name}.redis not configured" end end |
.redis=(conn) ⇒ Object
Set the redis instance to a specific instance. It is usually preferable to use the block form for configurating the instance.
39 40 41 |
# File 'lib/restrainer.rb', line 39 def redis=(conn) @redis = lambda{ conn } end |
Instance Method Details
#current(redis = nil) ⇒ Object
Get the number of processes currently being executed for this restrainer.
86 87 88 89 |
# File 'lib/restrainer.rb', line 86 def current(redis = nil) redis ||= self.class.redis redis.zcard(key).to_i end |
#throttle(limit: nil) ⇒ Object
Wrap a block with this method to throttle concurrent execution. If more than the alotted number of processes (as identified by the name) are currently executing, then a Restrainer::ThrottledError will be raised.
The limit argument can be used to override the value set in the constructor.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/restrainer.rb', line 66 def throttle(limit: nil) limit ||= self.limit # limit of less zero is no limit; limit of zero is allow none return yield if limit < 0 raise ThrottledError.new("#{self.class}: #{@name} is not allowing any processing") if limit == 0 # Grab a reference to the redis instance to that it will be consistent throughout the method redis = self.class.redis check_running_count!(redis, limit) process_id = SecureRandom.uuid begin add_process!(redis, process_id) yield ensure remove_process!(redis, process_id) end end |