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.
- checksums.yaml +15 -0
- data/lib/hystrix.rb +16 -0
- data/lib/hystrix/circuit.rb +42 -0
- data/lib/hystrix/command.rb +79 -0
- data/lib/hystrix/configuration.rb +36 -0
- data/lib/hystrix/dsl.rb +13 -0
- data/lib/hystrix/executor_pool.rb +96 -0
- data/lib/hystrix/inline.rb +48 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -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=
|
data/lib/hystrix.rb
ADDED
@@ -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
|
data/lib/hystrix/dsl.rb
ADDED
@@ -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: []
|