Class: Restrainer

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

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

#limitObject (readonly)

Returns the value of attribute limit.



16
17
18
# File 'lib/restrainer.rb', line 16

def limit
  @limit
end

#nameObject (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.

Raises:



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