supervision 0.1.0 → 0.2.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/CHANGELOG.md +12 -0
 - data/README.md +20 -10
 - data/lib/supervision.rb +30 -2
 - data/lib/supervision/circuit_breaker.rb +85 -21
 - data/lib/supervision/circuit_control.rb +61 -19
 - data/lib/supervision/circuit_monitor.rb +47 -2
 - data/lib/supervision/circuit_system.rb +31 -2
 - data/lib/supervision/configuration.rb +15 -2
 - data/lib/supervision/counter.rb +49 -0
 - data/lib/supervision/registry.rb +37 -3
 - data/lib/supervision/version.rb +1 -1
 - data/spec/spec_helper.rb +25 -0
 - data/spec/unit/circuit_breaker_spec.rb +37 -12
 - data/spec/unit/circuit_control_spec.rb +49 -18
 - data/spec/unit/circuit_monitor_spec.rb +21 -0
 - data/spec/unit/circuit_system_spec.rb +42 -0
 - data/spec/unit/configuration_spec.rb +29 -3
 - data/spec/unit/counter_spec.rb +34 -0
 - data/spec/unit/initialize_spec.rb +68 -11
 - data/spec/unit/registry_spec.rb +38 -7
 - data/supervision.gemspec +1 -1
 - data/tasks/console.rake +1 -1
 - metadata +10 -4
 
| 
         @@ -4,10 +4,55 @@ module Supervision 
     | 
|
| 
       4 
4 
     | 
    
         
             
              # A class responsible for recording circuit performance
         
     | 
| 
       5 
5 
     | 
    
         
             
              class CircuitMonitor
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
      
 7 
     | 
    
         
            +
                attr_reader :times_opened
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                # Timestamp for the last circuit open state
         
     | 
| 
      
 10 
     | 
    
         
            +
                #
         
     | 
| 
      
 11 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 12 
     | 
    
         
            +
                attr_reader :last_opened
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
       7 
14 
     | 
    
         
             
                def initialize
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @total_failed_calls  = Counter.new
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @total_success_calls = Counter.new
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @total_calls         = Counter.new
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @state_transitions   = Counter.new
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def total_calls
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @total_calls.value
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def total_success_calls
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @total_success_calls.value
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def total_failed_calls
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @total_failed_calls.value
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def record_success
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @total_success_calls.increment
         
     | 
| 
      
 35 
     | 
    
         
            +
                  @total_calls.increment
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def record_failure
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @total_failed_calls.increment
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @total_calls.increment
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def measure(type)
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
       8 
45 
     | 
    
         
             
                end
         
     | 
| 
       9 
46 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
                 
     | 
| 
      
 47 
     | 
    
         
            +
                # Reset the circuit statistics
         
     | 
| 
      
 48 
     | 
    
         
            +
                #
         
     | 
| 
      
 49 
     | 
    
         
            +
                # @return [nil]
         
     | 
| 
      
 50 
     | 
    
         
            +
                #
         
     | 
| 
      
 51 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 52 
     | 
    
         
            +
                def reset
         
     | 
| 
      
 53 
     | 
    
         
            +
                  total_calls.clear
         
     | 
| 
      
 54 
     | 
    
         
            +
                  total_success_calls.clear
         
     | 
| 
      
 55 
     | 
    
         
            +
                  total_failed_calls.clear
         
     | 
| 
       11 
56 
     | 
    
         
             
                end
         
     | 
| 
       12 
     | 
    
         
            -
              end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end # CircuitMonitor
         
     | 
| 
       13 
58 
     | 
    
         
             
            end # Supervision
         
     | 
| 
         @@ -5,12 +5,41 @@ module Supervision 
     | 
|
| 
       5 
5 
     | 
    
         
             
              class CircuitSystem
         
     | 
| 
       6 
6 
     | 
    
         
             
                extend Forwardable
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
                 
     | 
| 
       9 
     | 
    
         
            -
                               :register, :delete, :unregister
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :registry
         
     | 
| 
       10 
9 
     | 
    
         | 
| 
      
 10 
     | 
    
         
            +
                def_delegators '@registry', :[], :get, :[]=, :set, :register, :delete,
         
     | 
| 
      
 11 
     | 
    
         
            +
                               :unregister, :names, :empty?, :registered?
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # Create a CircuitSystem
         
     | 
| 
      
 14 
     | 
    
         
            +
                #
         
     | 
| 
      
 15 
     | 
    
         
            +
                # @api public
         
     | 
| 
       11 
16 
     | 
    
         
             
                def initialize
         
     | 
| 
       12 
17 
     | 
    
         
             
                  @registry = Registry.new
         
     | 
| 
       13 
18 
     | 
    
         
             
                end
         
     | 
| 
       14 
19 
     | 
    
         | 
| 
      
 20 
     | 
    
         
            +
                # Shutdown this circuit system
         
     | 
| 
      
 21 
     | 
    
         
            +
                #
         
     | 
| 
      
 22 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 23 
     | 
    
         
            +
                def shutdown
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @registry.clear
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                # Detailed string representation of this circuit system
         
     | 
| 
      
 28 
     | 
    
         
            +
                #
         
     | 
| 
      
 29 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
      
 30 
     | 
    
         
            +
                #
         
     | 
| 
      
 31 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 32 
     | 
    
         
            +
                def inspect
         
     | 
| 
      
 33 
     | 
    
         
            +
                  "#<#{self.class.name}:#{object_id}> @names=#{names}>"
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                # Detailed string representation of this circuit system
         
     | 
| 
      
 37 
     | 
    
         
            +
                #
         
     | 
| 
      
 38 
     | 
    
         
            +
                # @return [String]
         
     | 
| 
      
 39 
     | 
    
         
            +
                #
         
     | 
| 
      
 40 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 41 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 42 
     | 
    
         
            +
                  "#<#{self.class.name}:#{object_id}> @names=#{names}>"
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
       15 
44 
     | 
    
         
             
              end # CircuitSystem
         
     | 
| 
       16 
45 
     | 
    
         
             
            end # Supervision
         
     | 
| 
         @@ -22,6 +22,19 @@ module Supervision 
     | 
|
| 
       22 
22 
     | 
    
         
             
                                                            DEFAULT_RESET_TIMEOUT))
         
     | 
| 
       23 
23 
     | 
    
         
             
                end
         
     | 
| 
       24 
24 
     | 
    
         | 
| 
      
 25 
     | 
    
         
            +
                # Evalutate this configuration
         
     | 
| 
      
 26 
     | 
    
         
            +
                #
         
     | 
| 
      
 27 
     | 
    
         
            +
                # @return [self]
         
     | 
| 
      
 28 
     | 
    
         
            +
                #
         
     | 
| 
      
 29 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 30 
     | 
    
         
            +
                def configure(&block)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  if block.arity.zero?
         
     | 
| 
      
 32 
     | 
    
         
            +
                    instance_eval(&block)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  else
         
     | 
| 
      
 34 
     | 
    
         
            +
                    yield self
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
       25 
38 
     | 
    
         
             
                def max_failures=(value)
         
     | 
| 
       26 
39 
     | 
    
         
             
                  @max_failures.set(value)
         
     | 
| 
       27 
40 
     | 
    
         
             
                end
         
     | 
| 
         @@ -64,9 +77,9 @@ module Supervision 
     | 
|
| 
       64 
77 
     | 
    
         
             
                  end
         
     | 
| 
       65 
78 
     | 
    
         
             
                end
         
     | 
| 
       66 
79 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
                # TODO: replace with custom error
         
     | 
| 
       68 
80 
     | 
    
         
             
                def raise_unknown_config_option(option)
         
     | 
| 
       69 
     | 
    
         
            -
                  raise  
     | 
| 
      
 81 
     | 
    
         
            +
                  raise InvalidParameterError,
         
     | 
| 
      
 82 
     | 
    
         
            +
                        "`#{option}` isn`t recognized as valid parameter." \
         
     | 
| 
       70 
83 
     | 
    
         
             
                        " Please use one of `#{known_options.join(', ')}`"
         
     | 
| 
       71 
84 
     | 
    
         
             
                end
         
     | 
| 
       72 
85 
     | 
    
         
             
              end # Configuration
         
     | 
| 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # encoding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Supervision
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A class responsible for measuring increments/decrements of value
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Counter
         
     | 
| 
      
 6 
     | 
    
         
            +
                # Create a Counter
         
     | 
| 
      
 7 
     | 
    
         
            +
                #
         
     | 
| 
      
 8 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @count = Atomic.new(0)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                # Reset the counter
         
     | 
| 
      
 14 
     | 
    
         
            +
                #
         
     | 
| 
      
 15 
     | 
    
         
            +
                # @return [nil]
         
     | 
| 
      
 16 
     | 
    
         
            +
                #
         
     | 
| 
      
 17 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 18 
     | 
    
         
            +
                def clear
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @count.set(0)
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                # Increment counter
         
     | 
| 
      
 23 
     | 
    
         
            +
                #
         
     | 
| 
      
 24 
     | 
    
         
            +
                # @return [nil]
         
     | 
| 
      
 25 
     | 
    
         
            +
                #
         
     | 
| 
      
 26 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 27 
     | 
    
         
            +
                def increment(incr = 1)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @count.update { |v| v + incr }
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                # Decrement counter
         
     | 
| 
      
 32 
     | 
    
         
            +
                #
         
     | 
| 
      
 33 
     | 
    
         
            +
                # @param []
         
     | 
| 
      
 34 
     | 
    
         
            +
                #
         
     | 
| 
      
 35 
     | 
    
         
            +
                # @return [nil]
         
     | 
| 
      
 36 
     | 
    
         
            +
                #
         
     | 
| 
      
 37 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 38 
     | 
    
         
            +
                def decrement(decr = 1)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @count.update { |v| v + decr }
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                # Return the value
         
     | 
| 
      
 43 
     | 
    
         
            +
                #
         
     | 
| 
      
 44 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 45 
     | 
    
         
            +
                def value
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @count.value
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
              end # Counter
         
     | 
| 
      
 49 
     | 
    
         
            +
            end # Supervision
         
     | 
    
        data/lib/supervision/registry.rb
    CHANGED
    
    | 
         @@ -3,7 +3,6 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            module Supervision
         
     | 
| 
       4 
4 
     | 
    
         
             
              # A class responsible for registering/unregistering circuits
         
     | 
| 
       5 
5 
     | 
    
         
             
              class Registry
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
6 
     | 
    
         
             
                # Initialize a Registry
         
     | 
| 
       8 
7 
     | 
    
         
             
                #
         
     | 
| 
       9 
8 
     | 
    
         
             
                # @api public
         
     | 
| 
         @@ -14,10 +13,19 @@ module Supervision 
     | 
|
| 
       14 
13 
     | 
    
         | 
| 
       15 
14 
     | 
    
         
             
                # Register a circuit
         
     | 
| 
       16 
15 
     | 
    
         
             
                #
         
     | 
| 
      
 16 
     | 
    
         
            +
                # @param [String] name
         
     | 
| 
      
 17 
     | 
    
         
            +
                #   the name under which to register
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # @param [Supervision::CircuitBreaker] circuit
         
     | 
| 
      
 20 
     | 
    
         
            +
                #   the registered circuit breaker
         
     | 
| 
      
 21 
     | 
    
         
            +
                #
         
     | 
| 
       17 
22 
     | 
    
         
             
                # @api public
         
     | 
| 
       18 
23 
     | 
    
         
             
                def []=(name, circuit)
         
     | 
| 
       19 
24 
     | 
    
         
             
                  unless circuit.is_a?(CircuitBreaker)
         
     | 
| 
       20 
     | 
    
         
            -
                    raise TypeError, 'not a circuit'
         
     | 
| 
      
 25 
     | 
    
         
            +
                    raise TypeError, 'not a type of circuit breaker'
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                  if registered?(name)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    raise DuplicateEntryError, "`#{name}` is already registered"
         
     | 
| 
       21 
29 
     | 
    
         
             
                  end
         
     | 
| 
       22 
30 
     | 
    
         
             
                  @lock.synchronize do
         
     | 
| 
       23 
31 
     | 
    
         
             
                    @map[name.to_sym] = circuit
         
     | 
| 
         @@ -26,6 +34,8 @@ module Supervision 
     | 
|
| 
       26 
34 
     | 
    
         | 
| 
       27 
35 
     | 
    
         
             
                # Retrieve a circuit by name
         
     | 
| 
       28 
36 
     | 
    
         
             
                #
         
     | 
| 
      
 37 
     | 
    
         
            +
                # @param [String] name
         
     | 
| 
      
 38 
     | 
    
         
            +
                #
         
     | 
| 
       29 
39 
     | 
    
         
             
                # @api public
         
     | 
| 
       30 
40 
     | 
    
         
             
                def [](name)
         
     | 
| 
       31 
41 
     | 
    
         
             
                  @lock.synchronize do
         
     | 
| 
         @@ -48,15 +58,39 @@ module Supervision 
     | 
|
| 
       48 
58 
     | 
    
         | 
| 
       49 
59 
     | 
    
         
             
                # Check if circuit is in registry
         
     | 
| 
       50 
60 
     | 
    
         
             
                #
         
     | 
| 
      
 61 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
      
 62 
     | 
    
         
            +
                #
         
     | 
| 
       51 
63 
     | 
    
         
             
                # @api public
         
     | 
| 
       52 
64 
     | 
    
         
             
                def registered?(name)
         
     | 
| 
       53 
     | 
    
         
            -
                  names.include?(name)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  names.include?(name) || names.include?(name.to_sym)
         
     | 
| 
       54 
66 
     | 
    
         
             
                end
         
     | 
| 
       55 
67 
     | 
    
         | 
| 
      
 68 
     | 
    
         
            +
                # Retrieve registered circuits' names
         
     | 
| 
      
 69 
     | 
    
         
            +
                #
         
     | 
| 
      
 70 
     | 
    
         
            +
                # @return [Array]
         
     | 
| 
      
 71 
     | 
    
         
            +
                #
         
     | 
| 
      
 72 
     | 
    
         
            +
                # @api public
         
     | 
| 
       56 
73 
     | 
    
         
             
                def names
         
     | 
| 
       57 
74 
     | 
    
         
             
                  @lock.synchronize { @map.keys }
         
     | 
| 
       58 
75 
     | 
    
         
             
                end
         
     | 
| 
       59 
76 
     | 
    
         | 
| 
      
 77 
     | 
    
         
            +
                # Check if registry is empty or not
         
     | 
| 
      
 78 
     | 
    
         
            +
                #
         
     | 
| 
      
 79 
     | 
    
         
            +
                # @return [Boolean]
         
     | 
| 
      
 80 
     | 
    
         
            +
                #
         
     | 
| 
      
 81 
     | 
    
         
            +
                # @api public
         
     | 
| 
      
 82 
     | 
    
         
            +
                def empty?
         
     | 
| 
      
 83 
     | 
    
         
            +
                  @lock.synchronize { @map.empty? }
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                # Remove all registered circuits
         
     | 
| 
      
 87 
     | 
    
         
            +
                #
         
     | 
| 
      
 88 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 89 
     | 
    
         
            +
                #  registry.clear
         
     | 
| 
      
 90 
     | 
    
         
            +
                #
         
     | 
| 
      
 91 
     | 
    
         
            +
                # @return [Hash]
         
     | 
| 
      
 92 
     | 
    
         
            +
                #
         
     | 
| 
      
 93 
     | 
    
         
            +
                # @api public
         
     | 
| 
       60 
94 
     | 
    
         
             
                def clear
         
     | 
| 
       61 
95 
     | 
    
         
             
                  hash = nil
         
     | 
| 
       62 
96 
     | 
    
         
             
                  @lock.synchronize do
         
     | 
    
        data/lib/supervision/version.rb
    CHANGED
    
    
    
        data/spec/spec_helper.rb
    CHANGED
    
    | 
         @@ -1,5 +1,20 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # encoding: utf-8
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            if RUBY_VERSION > '1.9' and (ENV['COVERAGE'] || ENV['TRAVIS'])
         
     | 
| 
      
 4 
     | 
    
         
            +
              require 'simplecov'
         
     | 
| 
      
 5 
     | 
    
         
            +
              require 'coveralls'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
         
     | 
| 
      
 8 
     | 
    
         
            +
                SimpleCov::Formatter::HTMLFormatter,
         
     | 
| 
      
 9 
     | 
    
         
            +
                Coveralls::SimpleCov::Formatter
         
     | 
| 
      
 10 
     | 
    
         
            +
              ]
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              SimpleCov.start do
         
     | 
| 
      
 13 
     | 
    
         
            +
                command_name 'spec'
         
     | 
| 
      
 14 
     | 
    
         
            +
                add_filter 'spec'
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
       3 
18 
     | 
    
         
             
            require 'supervision'
         
     | 
| 
       4 
19 
     | 
    
         
             
            require 'timeout'
         
     | 
| 
       5 
20 
     | 
    
         | 
| 
         @@ -9,6 +24,16 @@ module Helpers 
     | 
|
| 
       9 
24 
     | 
    
         
             
                  sleep(duration || 0.01) until yield
         
     | 
| 
       10 
25 
     | 
    
         
             
                end
         
     | 
| 
       11 
26 
     | 
    
         
             
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              def spawn(threads = 2)
         
     | 
| 
      
 29 
     | 
    
         
            +
                threads.times.map do |i|
         
     | 
| 
      
 30 
     | 
    
         
            +
                  Thread.new do
         
     | 
| 
      
 31 
     | 
    
         
            +
                    yield i
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end.each do |thread|
         
     | 
| 
      
 34 
     | 
    
         
            +
                  thread.join
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
       12 
37 
     | 
    
         
             
            end
         
     | 
| 
       13 
38 
     | 
    
         | 
| 
       14 
39 
     | 
    
         
             
            RSpec.configure do |config|
         
     | 
| 
         @@ -12,17 +12,34 @@ describe Supervision::CircuitBreaker do 
     | 
|
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
              let(:object) { described_class }
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
      
 15 
     | 
    
         
            +
              it "fails without a block" do
         
     | 
| 
      
 16 
     | 
    
         
            +
                expect { object.new }.to raise_error(Supervision::InvalidParameterError)
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              it "resets circuit" do
         
     | 
| 
      
 20 
     | 
    
         
            +
                circuit = object.new { }
         
     | 
| 
      
 21 
     | 
    
         
            +
                expect(circuit.control).to receive(:reset!)
         
     | 
| 
      
 22 
     | 
    
         
            +
                circuit.reset!
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              it "configures the control" do
         
     | 
| 
      
 26 
     | 
    
         
            +
                block = -> { }
         
     | 
| 
      
 27 
     | 
    
         
            +
                circuit = object.new { }
         
     | 
| 
      
 28 
     | 
    
         
            +
                expect(circuit.control.config).to receive(:configure).with(&block)
         
     | 
| 
      
 29 
     | 
    
         
            +
                circuit.configure(&block)
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
       15 
31 
     | 
    
         | 
| 
       16 
32 
     | 
    
         
             
              context 'when closed' do
         
     | 
| 
       17 
33 
     | 
    
         
             
                it "successfully calls the method" do
         
     | 
| 
       18 
34 
     | 
    
         
             
                  circuit = object.new call_timeout: 1.milli do |arg|
         
     | 
| 
       19 
35 
     | 
    
         
             
                    arg == :danger ? dangerouse_call_error : safe_call
         
     | 
| 
       20 
36 
     | 
    
         
             
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                  expect(circuit.current).to be(:closed)
         
     | 
| 
       21 
38 
     | 
    
         
             
                  expect(circuit.call(:safe)).to eql(safe_call)
         
     | 
| 
       22 
39 
     | 
    
         
             
                end
         
     | 
| 
       23 
40 
     | 
    
         | 
| 
       24 
41 
     | 
    
         
             
                it "increments a failure counter for exceptions" do
         
     | 
| 
       25 
     | 
    
         
            -
                  circuit = object.new call_timeout: 1.milli do
         
     | 
| 
      
 42 
     | 
    
         
            +
                  circuit = object.new call_timeout: 1.milli do |arg|
         
     | 
| 
       26 
43 
     | 
    
         
             
                    arg == :danger ? dangerouse_call_error : safe_call
         
     | 
| 
       27 
44 
     | 
    
         
             
                  end
         
     | 
| 
       28 
45 
     | 
    
         
             
                  circuit.call(:danger)
         
     | 
| 
         @@ -49,39 +66,39 @@ describe Supervision::CircuitBreaker do 
     | 
|
| 
       49 
66 
     | 
    
         
             
                end
         
     | 
| 
       50 
67 
     | 
    
         | 
| 
       51 
68 
     | 
    
         
             
                it "enters a :half_open state after the :reset_timeout" do
         
     | 
| 
       52 
     | 
    
         
            -
                  circuit = object.new reset_timeout: 0. 
     | 
| 
      
 69 
     | 
    
         
            +
                  circuit = object.new reset_timeout: 0.2.sec, max_failures: 0 do
         
     | 
| 
       53 
70 
     | 
    
         
             
                    dangerous_call_error
         
     | 
| 
       54 
71 
     | 
    
         
             
                  end
         
     | 
| 
       55 
72 
     | 
    
         
             
                  expect { circuit.call }.to raise_error(Supervision::CircuitBreakerOpenError)
         
     | 
| 
       56 
73 
     | 
    
         
             
                  expect(circuit.control.current).to eq(:open)
         
     | 
| 
       57 
     | 
    
         
            -
                  sleep 0. 
     | 
| 
      
 74 
     | 
    
         
            +
                  sleep 0.4
         
     | 
| 
       58 
75 
     | 
    
         
             
                  expect(circuit.control.current).to eq(:half_open)
         
     | 
| 
       59 
76 
     | 
    
         
             
                end
         
     | 
| 
       60 
77 
     | 
    
         
             
              end
         
     | 
| 
       61 
78 
     | 
    
         | 
| 
       62 
79 
     | 
    
         
             
              context 'when half open' do
         
     | 
| 
       63 
80 
     | 
    
         
             
                it "resets the breaker back to :closed state on successful call" do
         
     | 
| 
       64 
     | 
    
         
            -
                  circuit = object.new reset_timeout:  
     | 
| 
      
 81 
     | 
    
         
            +
                  circuit = object.new reset_timeout: 0.2.sec, max_failures: 0 do |arg|
         
     | 
| 
       65 
82 
     | 
    
         
             
                    arg == :danger ? dangerous_call_error : safe_call
         
     | 
| 
       66 
83 
     | 
    
         
             
                  end
         
     | 
| 
       67 
84 
     | 
    
         
             
                  expect {
         
     | 
| 
       68 
85 
     | 
    
         
             
                    circuit.call(:danger)
         
     | 
| 
       69 
86 
     | 
    
         
             
                  }.to raise_error(Supervision::CircuitBreakerOpenError)
         
     | 
| 
       70 
87 
     | 
    
         
             
                  expect(circuit.control.current).to eql(:open)
         
     | 
| 
       71 
     | 
    
         
            -
                  sleep 0. 
     | 
| 
      
 88 
     | 
    
         
            +
                  sleep 0.4
         
     | 
| 
       72 
89 
     | 
    
         
             
                  expect(circuit.control.current).to eql(:half_open)
         
     | 
| 
       73 
90 
     | 
    
         
             
                  circuit.call(:safe)
         
     | 
| 
       74 
91 
     | 
    
         
             
                  expect(circuit.control.current).to eql(:closed)
         
     | 
| 
       75 
92 
     | 
    
         
             
                end
         
     | 
| 
       76 
93 
     | 
    
         
             
              end
         
     | 
| 
       77 
94 
     | 
    
         | 
| 
       78 
     | 
    
         
            -
              context ' 
     | 
| 
      
 95 
     | 
    
         
            +
              context 'with notification' do
         
     | 
| 
       79 
96 
     | 
    
         
             
                it "notifies about successful call" do
         
     | 
| 
       80 
97 
     | 
    
         
             
                  callbacks = []
         
     | 
| 
       81 
98 
     | 
    
         
             
                  circuit = object.new do safe_call end
         
     | 
| 
       82 
99 
     | 
    
         
             
                  circuit.on_success { callbacks << 'on_success' }
         
     | 
| 
       83 
100 
     | 
    
         
             
                  circuit.call
         
     | 
| 
       84 
     | 
    
         
            -
                  expect(callbacks).to  
     | 
| 
      
 101 
     | 
    
         
            +
                  expect(callbacks).to match_array(["on_success"])
         
     | 
| 
       85 
102 
     | 
    
         
             
                end
         
     | 
| 
       86 
103 
     | 
    
         | 
| 
       87 
104 
     | 
    
         
             
                it "notifies about failed call" do
         
     | 
| 
         @@ -90,13 +107,21 @@ describe Supervision::CircuitBreaker do 
     | 
|
| 
       90 
107 
     | 
    
         
             
                  circuit.on_failure { callbacks << 'on_failure'}
         
     | 
| 
       91 
108 
     | 
    
         
             
                  circuit.before { callbacks << 'before'}
         
     | 
| 
       92 
109 
     | 
    
         
             
                  circuit.call
         
     | 
| 
       93 
     | 
    
         
            -
                  expect(callbacks).to  
     | 
| 
      
 110 
     | 
    
         
            +
                  expect(callbacks).to match_array(['before', 'on_failure'])
         
     | 
| 
       94 
111 
     | 
    
         
             
                end
         
     | 
| 
       95 
112 
     | 
    
         
             
              end
         
     | 
| 
       96 
113 
     | 
    
         | 
| 
       97 
     | 
    
         
            -
               
     | 
| 
       98 
     | 
    
         
            -
                 
     | 
| 
       99 
     | 
    
         
            -
                  object.new  
     | 
| 
       100 
     | 
    
         
            -
             
     | 
| 
      
 114 
     | 
    
         
            +
              describe "#to_s" do
         
     | 
| 
      
 115 
     | 
    
         
            +
                it 'prints object info' do
         
     | 
| 
      
 116 
     | 
    
         
            +
                  circuit = object.new(name: :danger) { }
         
     | 
| 
      
 117 
     | 
    
         
            +
                  expect(circuit.to_s).to include("@name=danger")
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
              end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
              describe "#inspect" do
         
     | 
| 
      
 122 
     | 
    
         
            +
                it 'prints object info' do
         
     | 
| 
      
 123 
     | 
    
         
            +
                  circuit = object.new(name: :danger) { }
         
     | 
| 
      
 124 
     | 
    
         
            +
                  expect(circuit.inspect).to include("@name=danger")
         
     | 
| 
      
 125 
     | 
    
         
            +
                end
         
     | 
| 
       101 
126 
     | 
    
         
             
              end
         
     | 
| 
       102 
127 
     | 
    
         
             
            end
         
     | 
| 
         @@ -7,7 +7,7 @@ describe Supervision::CircuitControl do 
     | 
|
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
              let(:max_failures) { 1 }
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
              let(:reset_timeout) { 0. 
     | 
| 
      
 10 
     | 
    
         
            +
              let(:reset_timeout) { 0.2.sec }
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
              subject(:control) {
         
     | 
| 
       13 
13 
     | 
    
         
             
                object.new max_failures: max_failures,
         
     | 
| 
         @@ -17,56 +17,87 @@ describe Supervision::CircuitControl do 
     | 
|
| 
       17 
17 
     | 
    
         
             
              context 'when closed' do
         
     | 
| 
       18 
18 
     | 
    
         
             
                it "resets the failure count on success" do
         
     | 
| 
       19 
19 
     | 
    
         
             
                  expect(control.failure_count).to eql(0)
         
     | 
| 
       20 
     | 
    
         
            -
                  expect(control. 
     | 
| 
      
 20 
     | 
    
         
            +
                  expect(control.current).to eql(:closed)
         
     | 
| 
       21 
21 
     | 
    
         
             
                  control.record_failure
         
     | 
| 
       22 
22 
     | 
    
         
             
                  expect(control.failure_count).to eql(1)
         
     | 
| 
       23 
23 
     | 
    
         
             
                  control.reset_failure
         
     | 
| 
       24 
24 
     | 
    
         
             
                  expect(control.failure_count).to eql(0)
         
     | 
| 
       25 
     | 
    
         
            -
                  expect(control. 
     | 
| 
      
 25 
     | 
    
         
            +
                  expect(control.current).to eql(:closed)
         
     | 
| 
       26 
26 
     | 
    
         
             
                end
         
     | 
| 
       27 
27 
     | 
    
         | 
| 
       28 
28 
     | 
    
         
             
                it "increments failure count on exceptions and trips the wire" do
         
     | 
| 
       29 
29 
     | 
    
         
             
                  expect(control.failure_count).to eql(0)
         
     | 
| 
       30 
     | 
    
         
            -
                  expect(control. 
     | 
| 
      
 30 
     | 
    
         
            +
                  expect(control.current).to eql(:closed)
         
     | 
| 
       31 
31 
     | 
    
         | 
| 
       32 
     | 
    
         
            -
                  control. 
     | 
| 
      
 32 
     | 
    
         
            +
                  control.handle_failure
         
     | 
| 
       33 
33 
     | 
    
         
             
                  expect(control.failure_count).to eql(1)
         
     | 
| 
       34 
     | 
    
         
            -
                  expect(control. 
     | 
| 
      
 34 
     | 
    
         
            +
                  expect(control.current).to eql(:closed)
         
     | 
| 
       35 
35 
     | 
    
         | 
| 
       36 
     | 
    
         
            -
                  expect{ 
     | 
| 
      
 36 
     | 
    
         
            +
                  expect {
         
     | 
| 
      
 37 
     | 
    
         
            +
                    control.handle_failure
         
     | 
| 
      
 38 
     | 
    
         
            +
                  }.to raise_error(Supervision::CircuitBreakerOpenError)
         
     | 
| 
       37 
39 
     | 
    
         
             
                  expect(control.failure_count).to eql(2)
         
     | 
| 
       38 
     | 
    
         
            -
                  expect(control. 
     | 
| 
      
 40 
     | 
    
         
            +
                  expect(control.current).to eql(:open)
         
     | 
| 
       39 
41 
     | 
    
         
             
                end
         
     | 
| 
       40 
42 
     | 
    
         
             
              end
         
     | 
| 
       41 
43 
     | 
    
         | 
| 
       42 
44 
     | 
    
         
             
              context 'when open' do
         
     | 
| 
       43 
45 
     | 
    
         
             
                it "fails all calls fast with CircuitBreakerOpenError" do
         
     | 
| 
       44 
     | 
    
         
            -
                  control. 
     | 
| 
       45 
     | 
    
         
            -
                  expect { 
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
      
 46 
     | 
    
         
            +
                  control.trip!
         
     | 
| 
      
 47 
     | 
    
         
            +
                  expect {
         
     | 
| 
      
 48 
     | 
    
         
            +
                    control.handle_failure
         
     | 
| 
      
 49 
     | 
    
         
            +
                  }.to raise_error(Supervision::CircuitBreakerOpenError)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  expect(control.current).to eq(:open)
         
     | 
| 
       47 
51 
     | 
    
         
             
                end
         
     | 
| 
       48 
52 
     | 
    
         | 
| 
       49 
53 
     | 
    
         
             
                it "enters :half_open state after the configured :reset_timeout" do
         
     | 
| 
       50 
54 
     | 
    
         
             
                  control.record_failure
         
     | 
| 
       51 
     | 
    
         
            -
                  expect{ 
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
                   
     | 
| 
      
 55 
     | 
    
         
            +
                  expect {
         
     | 
| 
      
 56 
     | 
    
         
            +
                    control.handle_failure
         
     | 
| 
      
 57 
     | 
    
         
            +
                  }.to raise_error(Supervision::CircuitBreakerOpenError)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  sleep 2 * reset_timeout
         
     | 
| 
      
 59 
     | 
    
         
            +
                  expect(control.current).to eq(:half_open)
         
     | 
| 
       54 
60 
     | 
    
         
             
                end
         
     | 
| 
       55 
61 
     | 
    
         
             
              end
         
     | 
| 
       56 
62 
     | 
    
         | 
| 
       57 
63 
     | 
    
         
             
              context 'when half open' do
         
     | 
| 
       58 
     | 
    
         
            -
                before { control. 
     | 
| 
      
 64 
     | 
    
         
            +
                before { control.attempt_reset! }
         
     | 
| 
       59 
65 
     | 
    
         | 
| 
       60 
66 
     | 
    
         
             
                it "resets the breaker back to :closed state on successful call" do
         
     | 
| 
      
 67 
     | 
    
         
            +
                  expect(control.current).to eq(:half_open)
         
     | 
| 
       61 
68 
     | 
    
         
             
                  control.record_success
         
     | 
| 
       62 
     | 
    
         
            -
                  expect(control. 
     | 
| 
      
 69 
     | 
    
         
            +
                  expect(control.current).to eql(:closed)
         
     | 
| 
       63 
70 
     | 
    
         
             
                  expect(control.failure_count).to eql(0)
         
     | 
| 
       64 
71 
     | 
    
         
             
                end
         
     | 
| 
       65 
72 
     | 
    
         | 
| 
       66 
73 
     | 
    
         
             
                it "trips the breaker back to :open state on failed call" do
         
     | 
| 
       67 
     | 
    
         
            -
                  expect 
     | 
| 
      
 74 
     | 
    
         
            +
                  expect(control.current).to eq(:half_open)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  expect {
         
     | 
| 
      
 76 
     | 
    
         
            +
                    control.handle_failure
         
     | 
| 
      
 77 
     | 
    
         
            +
                  }.to raise_error(Supervision::CircuitBreakerOpenError)
         
     | 
| 
       68 
78 
     | 
    
         
             
                  expect(control.failure_count).to eql(1)
         
     | 
| 
       69 
     | 
    
         
            -
                  expect(control. 
     | 
| 
      
 79 
     | 
    
         
            +
                  expect(control.current).to eql(:open)
         
     | 
| 
       70 
80 
     | 
    
         
             
                end
         
     | 
| 
       71 
81 
     | 
    
         
             
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
              describe "#measure_timeout" do
         
     | 
| 
      
 84 
     | 
    
         
            +
                it "kills the scheduler thread" do
         
     | 
| 
      
 85 
     | 
    
         
            +
                  expect {
         
     | 
| 
      
 86 
     | 
    
         
            +
                    control.trip
         
     | 
| 
      
 87 
     | 
    
         
            +
                  }.to raise_error(Supervision::CircuitBreakerOpenError)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  expect(control.current).to eq(:open)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  expect(control.scheduler).to receive(:kill).once
         
     | 
| 
      
 90 
     | 
    
         
            +
                  control.stub(:max_thread_lifetime).and_return 0
         
     | 
| 
      
 91 
     | 
    
         
            +
                  sleep reset_timeout
         
     | 
| 
      
 92 
     | 
    
         
            +
                  expect(control.current).to eq(:open)
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
              end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
              it "forces reset to closed state" do
         
     | 
| 
      
 97 
     | 
    
         
            +
                control.attempt_reset!
         
     | 
| 
      
 98 
     | 
    
         
            +
                expect(control.current).to eq(:half_open)
         
     | 
| 
      
 99 
     | 
    
         
            +
                expect(control).to receive(:reset_failure)
         
     | 
| 
      
 100 
     | 
    
         
            +
                control.reset!
         
     | 
| 
      
 101 
     | 
    
         
            +
                expect(control.current).to eq(:closed)
         
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
       72 
103 
     | 
    
         
             
            end
         
     |