Class: DoubleRestraint

Inherits:
Object
  • Object
show all
Defined in:
lib/double_restraint.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, timeout:, long_running_timeout:, long_running_limit:, limit: nil, timeout_errors: [Timeout::Error], redis: nil) ⇒ DoubleRestraint

Returns a new instance of DoubleRestraint.

Parameters:

  • name (String, Symbol)

    The name of the restraint.

  • timeout (Numeric)

    The first timeout that will be yielded to the block.

  • long_running_timeout (Numeric)

    The timeout that will be yielded to the block if the block times out the first time it is executed.

  • long_running_limit (Integer)

    The maximum number of concurrent executions of the block with the long running timeout across all processes.

  • limit (Integer) (defaults to: nil)

    The maximum number of concurrent executions of the block with the initial timeout across all processes. If this is nil, then no limit will be applied.

  • timeout_errors (Array<Module>) (defaults to: [Timeout::Error])

    List of errors that will be considered a timeout. This needs to be customized depending on what the code in the block could throw to indicate a timeout has occurred.

  • redis (Redis) (defaults to: nil)

    Redis connection to use. If this is not set, the default value set for Restrainer.redis will be used.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/double_restraint.rb', line 22

def initialize(name, timeout:, long_running_timeout:, long_running_limit:, limit: nil, timeout_errors: [Timeout::Error], redis: nil)
  @timeout = timeout
  @long_running_timeout = long_running_timeout
  @timeout_errors = Array(timeout_errors)
  limit = -1 if limit.nil?
  # Slots in the underlying restrainers expire as a safeguard against orphaned locks,
  # so the expiration must comfortably exceed the longest time a block can legitimately
  # hold a slot.
  restrainer_timeout = [60, long_running_timeout.to_f * 2].max
  # The restrainer names use "DoubleRestrainer" rather than "DoubleRestraint" for
  # backward compatibility with the Redis keys created by earlier versions of the gem.
  @restrainer = Restrainer.new("DoubleRestrainer(#{name})", limit: limit, timeout: restrainer_timeout, redis: redis)
  @long_running_restrainer = Restrainer.new("DoubleRestrainer(#{name}).long_running", limit: long_running_limit, timeout: restrainer_timeout, redis: redis)
end

Instance Attribute Details

#long_running_timeoutObject (readonly)

Returns the value of attribute long_running_timeout.



7
8
9
# File 'lib/double_restraint.rb', line 7

def long_running_timeout
  @long_running_timeout
end

#timeoutObject (readonly)

Returns the value of attribute timeout.



7
8
9
# File 'lib/double_restraint.rb', line 7

def timeout
  @timeout
end

Instance Method Details

#default_pool_limitInteger

Get the limit for the default pool. This will return -1 if there is no limit set on that pool.

Returns:

  • (Integer)


84
85
86
# File 'lib/double_restraint.rb', line 84

def default_pool_limit
  @restrainer.limit
end

#default_pool_sizeInteger

Get the current size of the default pool. This can be useful in collecting realtime stats about how the pool is being utilized.

Returns:

  • (Integer)


68
69
70
# File 'lib/double_restraint.rb', line 68

def default_pool_size
  @restrainer.current
end

#execute {|the| ... } ⇒ Object

Execute a block of code. The block will be yielded with the timeout value. If the block raises a timeout error, then it will be called again with the long running timeout. The code in the block must be idempotent since it can be run twice.

Yield Parameters:

  • the (Numeric)

    timeout value to use in the block.

Returns:

  • (Object)

    the value returned by the block.

Raises:

  • (Restrainer::ThrottledError)

    if too many concurrent processes are trying to use the restraint.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/double_restraint.rb', line 44

def execute
  timed_out = false
  result = @restrainer.throttle do
    yield @timeout
  rescue *@timeout_errors
    # Just flag the timeout here so the retry happens after the throttle
    # block exits and releases its slot in the default pool.
    timed_out = true
    nil
  end

  if timed_out
    result = @long_running_restrainer.throttle do
      yield @long_running_timeout
    end
  end

  result
end

#long_running?(timeout) ⇒ Boolean

Helper method to determine if a timeout represents the long running timeout. Note that the timeout and long running timeouts need to be different values in order for this to work.

Returns:

  • (Boolean)


100
101
102
# File 'lib/double_restraint.rb', line 100

def long_running?(timeout)
  timeout.to_f.round(6) == @long_running_timeout.to_f.round(6)
end

#long_running_pool_limitInteger

Get the limit for the long running pool.

Returns:

  • (Integer)


91
92
93
# File 'lib/double_restraint.rb', line 91

def long_running_pool_limit
  @long_running_restrainer.limit
end

#long_running_pool_sizeInteger

Get the current size of the long running pool. This can be useful in collecting realtime stats about how the pool is being utilized.

Returns:

  • (Integer)


76
77
78
# File 'lib/double_restraint.rb', line 76

def long_running_pool_size
  @long_running_restrainer.current
end