hystrix-ruby 0.0.1

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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDZmNzZiMzUwMWYzNjk3NzY1M2Y0MWFjZjMwOGY2ODI0YmYzNDI0Zg==
5
+ data.tar.gz: !binary |-
6
+ YjhjZjM4MzY1NjA1ZmUwYzlkMTExZjBjMWZhYmM3ZTk1MDk0NjY4Mw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZTVhMjlhOWQzNzA3ZjA2ZWQ5ZDg0YzFiODRlNGY1MDRmMTJlOTY5MzJmZDRj
10
+ MGZkZDE3ZDhjZjA1YzA1Yjg0YzBkZmI5Njg1ZDAyNGRmNThlY2UzZDg4YmNh
11
+ ZDVhYTcxYTk5ZWQwZjliMjZiNzAwZGQzNDhjMDRkMzJiNTA3NDk=
12
+ data.tar.gz: !binary |-
13
+ ZTYwYTMwY2MyNTBlNzA3NmE3MTZiZjNkZGRhNjE0ZDBjNDI4MDIwNzM3NDYx
14
+ ZmYyYzgzMjY3ZGRkMDBhMWNiYjE0NzA4MWZiNWM3ODU5NTY2NzZjMjY1ZTY1
15
+ YjkzNTljMWEyZDU2M2Q2ZjBjY2JkNTg3NGQ4NjBmNGE4MDU2MmI=
@@ -0,0 +1,16 @@
1
+ require 'celluloid'
2
+
3
+ require 'hystrix/command'
4
+ require 'hystrix/configuration'
5
+ require 'hystrix/circuit'
6
+ require 'hystrix/dsl'
7
+ require 'hystrix/executor_pool'
8
+ require 'hystrix/inline'
9
+
10
+ module Hystrix
11
+ extend DSL
12
+
13
+ def self.reset
14
+ CommandExecutorPools.instance.shutdown
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ # Circuit-breaker to track and disable commands based on previous attempts
2
+ module Hystrix
3
+ class Circuit
4
+ include Celluloid
5
+
6
+ attr_accessor :lock, :health, :recent_latency_errors, :last_health_check_time
7
+
8
+ def initialize
9
+ self.lock = Mutex.new
10
+ self.recent_latency_errors = []
11
+ self.health = 0
12
+ self.last_health_check_time = nil
13
+ end
14
+
15
+ def is_closed?
16
+ async.calculate_health if self.last_health_check_time == nil or self.last_health_check_time < Time.now.to_f - 10.0
17
+
18
+ return false unless self.is_healthy?
19
+ return true
20
+ end
21
+
22
+ def add_latency_error(duration)
23
+ self.lock.synchronize do
24
+ self.recent_latency_errors << {duration: duration, timestamp: Time.now.to_i}
25
+ end
26
+ async.calculate_health
27
+ end
28
+
29
+ def calculate_health
30
+ now = Time.now.to_i
31
+ self.lock.synchronize do
32
+ self.recent_latency_errors = self.recent_latency_errors.reject{|error| error[:timestamp] < now - 10}
33
+ self.health = self.recent_latency_errors.size * 0.2
34
+ self.last_health_check_time = now.to_f
35
+ end
36
+ end
37
+
38
+ def is_healthy?
39
+ self.health < 1
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,79 @@
1
+ # TODO: implement pluggable metrics collection. implement separate statsd impl
2
+
3
+ module Hystrix
4
+ class ExecutorPoolFullError < StandardError; end
5
+ class CircuitClosedError < StandardError; end
6
+
7
+ class Command
8
+ include Celluloid
9
+
10
+ attr_accessor :executor_pool, :circuit
11
+
12
+ @default_pool_size = 10
13
+ def self.default_pool_size
14
+ @default_pool_size
15
+ end
16
+
17
+ def initialize(*args)
18
+ self.executor_pool = CommandExecutorPools.instance.get_pool(executor_pool_name, self.class.default_pool_size)
19
+ self.circuit = self.executor_pool.circuit_supervisor.actors.first
20
+ end
21
+
22
+ # Run the command synchronously
23
+ def execute
24
+ raise 'No executor pool found! Did you forget to call super in your initialize method?' unless executor_pool
25
+
26
+ executor = nil
27
+ start_time = Time.now
28
+
29
+ begin
30
+ raise CircuitClosedError unless self.circuit.is_closed?
31
+
32
+ executor = executor_pool.take
33
+
34
+ result = executor.run(self)
35
+ duration = Time.now - start_time
36
+
37
+ Configuration.notify_success(executor_pool_name, duration)
38
+ rescue Exception => main_error
39
+ duration = Time.now - start_time
40
+
41
+ begin
42
+ if main_error.respond_to?(:cause)
43
+ result = fallback(main_error.cause)
44
+ Configuration.notify_fallback(executor_pool_name, duration, main_error.cause)
45
+ else
46
+ result = fallback(main_error)
47
+ Configuration.notify_fallback(executor_pool_name, duration, main_error)
48
+ end
49
+ rescue NotImplementedError => fallback_error
50
+ Configuration.notify_failure(executor_pool_name, duration, main_error)
51
+ raise main_error
52
+ end
53
+ ensure
54
+ executor.unlock if executor
55
+ self.terminate
56
+ end
57
+
58
+ return result
59
+ end
60
+
61
+ # Commands which share the value of executor_pool_name will use the same pool
62
+ def executor_pool_name
63
+ @executor_pool_name || self.class.name
64
+ end
65
+
66
+ # Run the command asynchronously
67
+ def queue
68
+ future.execute
69
+ end
70
+
71
+ def fallback(error)
72
+ raise NotImplementedError
73
+ end
74
+
75
+ def self.pool_size(size)
76
+ @default_pool_size = size
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,36 @@
1
+ module Hystrix
2
+ class Configuration
3
+ def self.on_success(&block)
4
+ @on_success = block
5
+ end
6
+ def self.notify_success(command_name, duration)
7
+ if @on_success
8
+ @on_success.call(command_name, duration)
9
+ end
10
+ end
11
+
12
+ def self.on_fallback(&block)
13
+ @on_fallback = block
14
+ end
15
+ def self.notify_fallback(command_name, duration, error)
16
+ if @on_fallback
17
+ @on_fallback.call(command_name, duration, error)
18
+ end
19
+ end
20
+
21
+ def self.on_failure(&block)
22
+ @on_failure = block
23
+ end
24
+ def self.notify_failure(command_name, duration, error)
25
+ if @on_failure
26
+ @on_failure.call(command_name, duration, error)
27
+ end
28
+ end
29
+
30
+ def self.reset
31
+ @on_success = nil
32
+ @on_fallback = nil
33
+ @on_failure = nil
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ module Hystrix
2
+ module DSL
3
+ def configure(&block)
4
+ Configuration.class_eval(&block)
5
+ end
6
+
7
+ def inline(executor_pool_name = nil, &block)
8
+ inline = InlineDSL.new(executor_pool_name)
9
+ inline.instance_eval(&block)
10
+ inline.run
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,96 @@
1
+ require 'singleton'
2
+
3
+ module Hystrix
4
+ class CommandExecutorPools
5
+ include Singleton
6
+
7
+ attr_accessor :pools, :lock
8
+
9
+ def initialize
10
+ self.lock = Mutex.new
11
+ self.pools = {}
12
+ end
13
+
14
+ def get_pool(pool_name, size = nil)
15
+ lock.synchronize do
16
+ pools[pool_name] ||= CommandExecutorPool.new(pool_name, size || 10)
17
+ end
18
+ end
19
+
20
+ def shutdown
21
+ lock.synchronize do
22
+ for pool_name, pool in pools
23
+ pool.shutdown
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ class CommandExecutorPool
30
+ attr_accessor :name, :size
31
+ attr_accessor :executors, :lock
32
+ attr_accessor :circuit_supervisor
33
+
34
+ def initialize(name, size)
35
+ self.name = name
36
+ self.size = size
37
+ self.executors = []
38
+ self.lock = Mutex.new
39
+ self.circuit_supervisor = Circuit.supervise
40
+ size.times do
41
+ self.executors << CommandExecutor.new
42
+ end
43
+ end
44
+
45
+ def take
46
+ lock.synchronize do
47
+ for executor in self.executors
48
+ unless executor.locked?
49
+ executor.lock
50
+ return executor
51
+ end
52
+ end
53
+ end
54
+
55
+ raise ExecutorPoolFullError.new("Unable to get executor from #{self.name} pool.")
56
+ end
57
+
58
+ def shutdown
59
+ lock.synchronize do
60
+ until executors.size == 0 do
61
+ for i in (0...executors.size)
62
+ unless executors[i].locked?
63
+ executors[i] = nil
64
+ end
65
+ end
66
+ executors.compact!
67
+ sleep 0.1
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ class CommandExecutor
74
+ attr_accessor :owner
75
+
76
+ def initialize
77
+ self.owner = nil
78
+ end
79
+
80
+ def lock
81
+ self.owner = Thread.current
82
+ end
83
+
84
+ def unlock
85
+ self.owner = nil
86
+ end
87
+
88
+ def locked?
89
+ !self.owner.nil?
90
+ end
91
+
92
+ def run(command)
93
+ command.run
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,48 @@
1
+ module Hystrix
2
+ class InlineDSL
3
+ def initialize(executor_pool_name = nil)
4
+ @executor_pool_name = executor_pool_name
5
+ @mode = :execute
6
+ end
7
+
8
+ def execute(&block)
9
+ @mode = :execute
10
+ @run_block = block
11
+ end
12
+
13
+ def queue(&block)
14
+ @mode = :queue
15
+ @run_block = block
16
+ end
17
+
18
+ def fallback(&block)
19
+ @fallback_block = block
20
+ end
21
+
22
+ def run
23
+ cmd = InlineCommand.new(@executor_pool_name, @run_block, @fallback_block)
24
+ cmd.send(@mode)
25
+ end
26
+ end
27
+
28
+ class InlineCommand < Command
29
+ def initialize(executor_pool_name, run_block, fallback_block)
30
+ @run_block = run_block
31
+ @fallback_block = fallback_block
32
+ @executor_pool_name = executor_pool_name
33
+ super
34
+ end
35
+
36
+ def run
37
+ @run_block.yield
38
+ end
39
+
40
+ def fallback(error)
41
+ if @fallback_block
42
+ @fallback_block.yield(error)
43
+ else
44
+ raise NotImplementedError
45
+ end
46
+ end
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hystrix-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Keith Thornhill
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ! '>='
17
+ - !ruby/object:Gem::Version
18
+ version: 0.13.0
19
+ type: :runtime
20
+ prerelease: false
21
+ name: celluloid
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.13.0
27
+ - !ruby/object:Gem::Dependency
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ name: rspec
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ name: simplecov-rcov
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ name: timecop
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Hystrix for Ruby
70
+ email: keith.thornhill@gmail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/hystrix/circuit.rb
76
+ - lib/hystrix/command.rb
77
+ - lib/hystrix/configuration.rb
78
+ - lib/hystrix/dsl.rb
79
+ - lib/hystrix/executor_pool.rb
80
+ - lib/hystrix/inline.rb
81
+ - lib/hystrix.rb
82
+ homepage:
83
+ licenses: []
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.0.7
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Hystrix for Ruby
105
+ test_files: []