hystrix-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []