semian_extension 0.11.4.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 +7 -0
- data/ext/semian/extconf.rb +33 -0
- data/ext/semian/resource.c +394 -0
- data/ext/semian/resource.h +133 -0
- data/ext/semian/semian.c +72 -0
- data/ext/semian/semian.h +14 -0
- data/ext/semian/sysv_semaphores.c +271 -0
- data/ext/semian/sysv_semaphores.h +122 -0
- data/ext/semian/tickets.c +76 -0
- data/ext/semian/tickets.h +13 -0
- data/ext/semian/types.h +41 -0
- data/lib/semian/adapter.rb +75 -0
- data/lib/semian/circuit_breaker.rb +167 -0
- data/lib/semian/grpc.rb +104 -0
- data/lib/semian/instrumentable.rb +28 -0
- data/lib/semian/lru_hash.rb +174 -0
- data/lib/semian/mysql2.rb +135 -0
- data/lib/semian/net_http.rb +117 -0
- data/lib/semian/platform.rb +16 -0
- data/lib/semian/protected_resource.rb +65 -0
- data/lib/semian/rails.rb +7 -0
- data/lib/semian/redis.rb +143 -0
- data/lib/semian/resource.rb +65 -0
- data/lib/semian/simple_integer.rb +38 -0
- data/lib/semian/simple_sliding_window.rb +68 -0
- data/lib/semian/simple_state.rb +50 -0
- data/lib/semian/typhoeus.rb +103 -0
- data/lib/semian/unprotected_resource.rb +73 -0
- data/lib/semian/version.rb +3 -0
- data/lib/semian.rb +310 -0
- metadata +260 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'semian/adapter'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Net
|
5
|
+
ProtocolError.include(::Semian::AdapterError)
|
6
|
+
|
7
|
+
class SemianError < ::Net::ProtocolError
|
8
|
+
def initialize(semian_identifier, *args)
|
9
|
+
super(*args)
|
10
|
+
@semian_identifier = semian_identifier
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
ResourceBusyError = Class.new(SemianError)
|
15
|
+
CircuitOpenError = Class.new(SemianError)
|
16
|
+
end
|
17
|
+
|
18
|
+
module Semian
|
19
|
+
module NetHTTP
|
20
|
+
include Semian::Adapter
|
21
|
+
|
22
|
+
ResourceBusyError = ::Net::ResourceBusyError
|
23
|
+
CircuitOpenError = ::Net::CircuitOpenError
|
24
|
+
|
25
|
+
class SemianConfigurationChangedError < RuntimeError
|
26
|
+
def initialize(msg = "Cannot re-initialize semian_configuration")
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def semian_identifier
|
32
|
+
"nethttp_#{raw_semian_options[:name]}"
|
33
|
+
end
|
34
|
+
|
35
|
+
DEFAULT_ERRORS = [
|
36
|
+
::Timeout::Error, # includes ::Net::ReadTimeout and ::Net::OpenTimeout
|
37
|
+
::SocketError,
|
38
|
+
::Net::HTTPBadResponse,
|
39
|
+
::Net::HTTPHeaderSyntaxError,
|
40
|
+
::Net::ProtocolError,
|
41
|
+
::EOFError,
|
42
|
+
::IOError,
|
43
|
+
::SystemCallError, # includes ::Errno::EINVAL, ::Errno::ECONNRESET, ::Errno::ECONNREFUSED, ::Errno::ETIMEDOUT, and more
|
44
|
+
].freeze # Net::HTTP can throw many different errors, this tries to capture most of them
|
45
|
+
|
46
|
+
class << self
|
47
|
+
attr_accessor :exceptions
|
48
|
+
attr_reader :semian_configuration
|
49
|
+
|
50
|
+
def semian_configuration=(configuration)
|
51
|
+
raise Semian::NetHTTP::SemianConfigurationChangedError unless @semian_configuration.nil?
|
52
|
+
@semian_configuration = configuration
|
53
|
+
end
|
54
|
+
|
55
|
+
def retrieve_semian_configuration(host, port)
|
56
|
+
@semian_configuration.call(host, port) if @semian_configuration.respond_to?(:call)
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset_exceptions
|
60
|
+
self.exceptions = Semian::NetHTTP::DEFAULT_ERRORS.dup
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Semian::NetHTTP.reset_exceptions
|
65
|
+
|
66
|
+
def raw_semian_options
|
67
|
+
@raw_semian_options ||= begin
|
68
|
+
@raw_semian_options = Semian::NetHTTP.retrieve_semian_configuration(address, port)
|
69
|
+
@raw_semian_options = @raw_semian_options.dup unless @raw_semian_options.nil?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def resource_exceptions
|
74
|
+
Semian::NetHTTP.exceptions
|
75
|
+
end
|
76
|
+
|
77
|
+
def disabled?
|
78
|
+
raw_semian_options.nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
def connect
|
82
|
+
return super if disabled?
|
83
|
+
acquire_semian_resource(adapter: :http, scope: :connection) { super }
|
84
|
+
end
|
85
|
+
|
86
|
+
def transport_request(*)
|
87
|
+
return super if disabled?
|
88
|
+
acquire_semian_resource(adapter: :http, scope: :query) do
|
89
|
+
handle_error_responses(super)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def with_resource_timeout(timeout)
|
94
|
+
prev_read_timeout = read_timeout
|
95
|
+
prev_open_timeout = open_timeout
|
96
|
+
begin
|
97
|
+
self.read_timeout = timeout
|
98
|
+
self.open_timeout = timeout
|
99
|
+
yield
|
100
|
+
ensure
|
101
|
+
self.read_timeout = prev_read_timeout
|
102
|
+
self.open_timeout = prev_open_timeout
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def handle_error_responses(result)
|
109
|
+
if raw_semian_options.fetch(:open_circuit_server_errors, false)
|
110
|
+
semian_resource.mark_failed(result) if result.is_a?(::Net::HTTPServerError)
|
111
|
+
end
|
112
|
+
result
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
Net::HTTP.prepend(Semian::NetHTTP)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Semian
|
2
|
+
extend self
|
3
|
+
|
4
|
+
# Determines if Semian supported on the current platform.
|
5
|
+
def sysv_semaphores_supported?
|
6
|
+
/linux/.match(RUBY_PLATFORM)
|
7
|
+
end
|
8
|
+
|
9
|
+
def semaphores_enabled?
|
10
|
+
!disabled? && sysv_semaphores_supported?
|
11
|
+
end
|
12
|
+
|
13
|
+
def disabled?
|
14
|
+
ENV['SEMIAN_SEMAPHORES_DISABLED'] || ENV['SEMIAN_DISABLED']
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Semian
|
2
|
+
class ProtectedResource
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def_delegators :@bulkhead, :destroy, :count, :semid, :tickets, :registered_workers
|
6
|
+
def_delegators :@circuit_breaker, :reset, :mark_failed, :mark_success, :request_allowed?,
|
7
|
+
:open?, :closed?, :half_open?
|
8
|
+
|
9
|
+
attr_reader :bulkhead, :circuit_breaker, :name
|
10
|
+
attr_accessor :updated_at
|
11
|
+
|
12
|
+
def initialize(name, bulkhead, circuit_breaker)
|
13
|
+
@name = name
|
14
|
+
@bulkhead = bulkhead
|
15
|
+
@circuit_breaker = circuit_breaker
|
16
|
+
@updated_at = Time.now
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy
|
20
|
+
@bulkhead.destroy unless @bulkhead.nil?
|
21
|
+
@circuit_breaker.destroy unless @circuit_breaker.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
def acquire(timeout: nil, scope: nil, adapter: nil, resource: nil)
|
25
|
+
acquire_circuit_breaker(scope, adapter, resource) do
|
26
|
+
acquire_bulkhead(timeout, scope, adapter) do |_, wait_time|
|
27
|
+
Semian.notify(:success, self, scope, adapter, wait_time)
|
28
|
+
yield self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def in_use?
|
34
|
+
circuit_breaker&.in_use? || bulkhead&.in_use?
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def acquire_circuit_breaker(scope, adapter, resource)
|
40
|
+
if @circuit_breaker.nil?
|
41
|
+
yield self
|
42
|
+
else
|
43
|
+
@circuit_breaker.acquire(resource) do
|
44
|
+
yield self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
rescue ::Semian::OpenCircuitError
|
48
|
+
Semian.notify(:circuit_open, self, scope, adapter)
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
|
52
|
+
def acquire_bulkhead(timeout, scope, adapter)
|
53
|
+
if @bulkhead.nil?
|
54
|
+
yield self, 0
|
55
|
+
else
|
56
|
+
@bulkhead.acquire(timeout: timeout) do |wait_time|
|
57
|
+
yield self, wait_time
|
58
|
+
end
|
59
|
+
end
|
60
|
+
rescue ::Semian::TimeoutError
|
61
|
+
Semian.notify(:busy, self, scope, adapter)
|
62
|
+
raise
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/semian/rails.rb
ADDED
data/lib/semian/redis.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'semian/adapter'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
class Redis
|
5
|
+
Redis::BaseConnectionError.include(::Semian::AdapterError)
|
6
|
+
::Errno::EINVAL.include(::Semian::AdapterError)
|
7
|
+
|
8
|
+
class SemianError < Redis::BaseConnectionError
|
9
|
+
def initialize(semian_identifier, *args)
|
10
|
+
super(*args)
|
11
|
+
@semian_identifier = semian_identifier
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class OutOfMemoryError < Redis::CommandError
|
16
|
+
include ::Semian::AdapterError
|
17
|
+
end
|
18
|
+
|
19
|
+
ResourceBusyError = Class.new(SemianError)
|
20
|
+
CircuitOpenError = Class.new(SemianError)
|
21
|
+
ResolveError = Class.new(SemianError)
|
22
|
+
|
23
|
+
alias_method :_original_initialize, :initialize
|
24
|
+
|
25
|
+
def initialize(*args, &block)
|
26
|
+
_original_initialize(*args, &block)
|
27
|
+
|
28
|
+
# This reference is necessary because during a `pipelined` block the client
|
29
|
+
# is replaced by an instance of `Redis::Pipeline` and there is no way to
|
30
|
+
# access the original client which references the Semian resource.
|
31
|
+
@original_client = _client
|
32
|
+
end
|
33
|
+
|
34
|
+
def semian_resource
|
35
|
+
@original_client.semian_resource
|
36
|
+
end
|
37
|
+
|
38
|
+
def semian_identifier
|
39
|
+
semian_resource.name
|
40
|
+
end
|
41
|
+
|
42
|
+
# Compatibility with old versions of the Redis gem
|
43
|
+
unless instance_methods.include?(:_client)
|
44
|
+
def _client
|
45
|
+
@client
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module Semian
|
51
|
+
module Redis
|
52
|
+
include Semian::Adapter
|
53
|
+
|
54
|
+
ResourceBusyError = ::Redis::ResourceBusyError
|
55
|
+
CircuitOpenError = ::Redis::CircuitOpenError
|
56
|
+
ResolveError = ::Redis::ResolveError
|
57
|
+
|
58
|
+
# The naked methods are exposed as `raw_query` and `raw_connect` for instrumentation purpose
|
59
|
+
def self.included(base)
|
60
|
+
base.send(:alias_method, :raw_io, :io)
|
61
|
+
base.send(:remove_method, :io)
|
62
|
+
|
63
|
+
base.send(:alias_method, :raw_connect, :connect)
|
64
|
+
base.send(:remove_method, :connect)
|
65
|
+
end
|
66
|
+
|
67
|
+
def semian_identifier
|
68
|
+
@semian_identifier ||= begin
|
69
|
+
name = semian_options && semian_options[:name]
|
70
|
+
name ||= "#{location}/#{db}"
|
71
|
+
:"redis_#{name}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def io(&block)
|
76
|
+
acquire_semian_resource(adapter: :redis, scope: :query) do
|
77
|
+
reply = raw_io(&block)
|
78
|
+
raise_if_out_of_memory(reply)
|
79
|
+
reply
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def connect
|
84
|
+
acquire_semian_resource(adapter: :redis, scope: :connection) do
|
85
|
+
begin
|
86
|
+
raw_connect
|
87
|
+
rescue SocketError, RuntimeError => e
|
88
|
+
raise ResolveError.new(semian_identifier) if dns_resolve_failure?(e.cause || e)
|
89
|
+
raise
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def with_resource_timeout(temp_timeout)
|
95
|
+
timeout = options[:timeout]
|
96
|
+
connect_timeout = options[:connect_timeout]
|
97
|
+
read_timeout = options[:read_timeout]
|
98
|
+
write_timeout = options[:write_timeout]
|
99
|
+
|
100
|
+
begin
|
101
|
+
connection.timeout = temp_timeout if connected?
|
102
|
+
options[:timeout] = Float(temp_timeout),
|
103
|
+
options[:connect_timeout] = Float(temp_timeout)
|
104
|
+
options[:read_timeout] = Float(temp_timeout)
|
105
|
+
options[:write_timeout] = Float(temp_timeout)
|
106
|
+
yield
|
107
|
+
ensure
|
108
|
+
options[:timeout] = timeout
|
109
|
+
options[:connect_timeout] = connect_timeout
|
110
|
+
options[:read_timeout] = read_timeout
|
111
|
+
options[:write_timeout] = write_timeout
|
112
|
+
connection.timeout = self.timeout if connected?
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def resource_exceptions
|
119
|
+
[
|
120
|
+
::Redis::BaseConnectionError,
|
121
|
+
::Errno::EINVAL, # Hiredis bug: https://github.com/redis/hiredis-rb/issues/21
|
122
|
+
::Redis::OutOfMemoryError,
|
123
|
+
]
|
124
|
+
end
|
125
|
+
|
126
|
+
def raw_semian_options
|
127
|
+
return options[:semian] if options.key?(:semian)
|
128
|
+
return options['semian'.freeze] if options.key?('semian'.freeze)
|
129
|
+
end
|
130
|
+
|
131
|
+
def raise_if_out_of_memory(reply)
|
132
|
+
return unless reply.is_a?(::Redis::CommandError)
|
133
|
+
return unless reply.message =~ /OOM command not allowed when used memory > 'maxmemory'\.\s*\z/
|
134
|
+
raise ::Redis::OutOfMemoryError.new(reply.message)
|
135
|
+
end
|
136
|
+
|
137
|
+
def dns_resolve_failure?(e)
|
138
|
+
e.to_s.match?(/(can't resolve)|(name or service not known)|(nodename nor servname provided, or not known)|(failure in name resolution)/i)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
::Redis::Client.include(Semian::Redis)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Semian
|
2
|
+
class Resource #:nodoc:
|
3
|
+
attr_reader :tickets, :name
|
4
|
+
|
5
|
+
class << Semian::Resource
|
6
|
+
# Ensure that there can only be one resource of a given type
|
7
|
+
def instance(name, **kwargs)
|
8
|
+
Semian.resources[name] ||= ProtectedResource.new(name, new(name, **kwargs), nil)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(name, tickets: nil, quota: nil, permissions: Semian.default_permissions, timeout: 0)
|
13
|
+
unless name.is_a?(String) || name.is_a?(Symbol)
|
14
|
+
raise TypeError, "name must be a string or symbol, got: #{name.class}"
|
15
|
+
end
|
16
|
+
|
17
|
+
if Semian.semaphores_enabled?
|
18
|
+
if respond_to?(:initialize_semaphore)
|
19
|
+
initialize_semaphore("#{Semian.namespace}#{name}", tickets, quota, permissions, timeout)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
Semian.issue_disabled_semaphores_warning
|
23
|
+
end
|
24
|
+
@name = name
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset_registered_workers!
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy
|
31
|
+
end
|
32
|
+
|
33
|
+
def unregister_worker
|
34
|
+
end
|
35
|
+
|
36
|
+
def acquire(*)
|
37
|
+
wait_time = 0
|
38
|
+
yield wait_time
|
39
|
+
end
|
40
|
+
|
41
|
+
def count
|
42
|
+
0
|
43
|
+
end
|
44
|
+
|
45
|
+
def tickets
|
46
|
+
0
|
47
|
+
end
|
48
|
+
|
49
|
+
def registered_workers
|
50
|
+
0
|
51
|
+
end
|
52
|
+
|
53
|
+
def semid
|
54
|
+
0
|
55
|
+
end
|
56
|
+
|
57
|
+
def key
|
58
|
+
'0x00000000'
|
59
|
+
end
|
60
|
+
|
61
|
+
def in_use?
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Semian
|
4
|
+
module Simple
|
5
|
+
class Integer #:nodoc:
|
6
|
+
attr_accessor :value
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
reset
|
10
|
+
end
|
11
|
+
|
12
|
+
def increment(val = 1)
|
13
|
+
@value += val
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset
|
17
|
+
@value = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy
|
21
|
+
reset
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ThreadSafe
|
27
|
+
class Integer < Simple::Integer
|
28
|
+
def initialize(*)
|
29
|
+
super
|
30
|
+
@lock = Mutex.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def increment(*)
|
34
|
+
@lock.synchronize { super }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Semian
|
4
|
+
module Simple
|
5
|
+
class SlidingWindow #:nodoc:
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :@window, :size, :last
|
9
|
+
attr_reader :max_size
|
10
|
+
|
11
|
+
# A sliding window is a structure that stores the most @max_size recent timestamps
|
12
|
+
# like this: if @max_size = 4, current time is 10, @window =[5,7,9,10].
|
13
|
+
# Another push of (11) at 11 sec would make @window [7,9,10,11], shifting off 5.
|
14
|
+
|
15
|
+
def initialize(max_size:)
|
16
|
+
@max_size = max_size
|
17
|
+
@window = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def reject!(&block)
|
21
|
+
@window.reject!(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def push(value)
|
25
|
+
resize_to(@max_size - 1) # make room
|
26
|
+
@window << value
|
27
|
+
self
|
28
|
+
end
|
29
|
+
alias_method :<<, :push
|
30
|
+
|
31
|
+
def clear
|
32
|
+
@window.clear
|
33
|
+
self
|
34
|
+
end
|
35
|
+
alias_method :destroy, :clear
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def resize_to(size)
|
40
|
+
@window = @window.last(size) if @window.size >= size
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module ThreadSafe
|
46
|
+
class SlidingWindow < Simple::SlidingWindow
|
47
|
+
def initialize(**)
|
48
|
+
super
|
49
|
+
@lock = Mutex.new
|
50
|
+
end
|
51
|
+
|
52
|
+
# #size, #last, and #clear are not wrapped in a mutex. For the first two,
|
53
|
+
# the worst-case is a thread-switch at a timing where they'd receive an
|
54
|
+
# out-of-date value--which could happen with a mutex as well.
|
55
|
+
#
|
56
|
+
# As for clear, it's an all or nothing operation. Doesn't matter if we
|
57
|
+
# have the lock or not.
|
58
|
+
|
59
|
+
def reject!(*)
|
60
|
+
@lock.synchronize { super }
|
61
|
+
end
|
62
|
+
|
63
|
+
def push(*)
|
64
|
+
@lock.synchronize { super }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Semian
|
2
|
+
module Simple
|
3
|
+
class State #:nodoc:
|
4
|
+
def initialize
|
5
|
+
reset
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :value
|
9
|
+
|
10
|
+
def open?
|
11
|
+
value == :open
|
12
|
+
end
|
13
|
+
|
14
|
+
def closed?
|
15
|
+
value == :closed
|
16
|
+
end
|
17
|
+
|
18
|
+
def half_open?
|
19
|
+
value == :half_open
|
20
|
+
end
|
21
|
+
|
22
|
+
def open!
|
23
|
+
@value = :open
|
24
|
+
end
|
25
|
+
|
26
|
+
def close!
|
27
|
+
@value = :closed
|
28
|
+
end
|
29
|
+
|
30
|
+
def half_open!
|
31
|
+
@value = :half_open
|
32
|
+
end
|
33
|
+
|
34
|
+
def reset
|
35
|
+
close!
|
36
|
+
end
|
37
|
+
|
38
|
+
def destroy
|
39
|
+
reset
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ThreadSafe
|
45
|
+
class State < Simple::State
|
46
|
+
# These operations are already safe for a threaded environment since it's
|
47
|
+
# a simple assignment.
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'semian/adapter'
|
2
|
+
require 'typhoeus'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Typhoeus
|
6
|
+
Errors::TyphoeusError.include(::Semian::AdapterError)
|
7
|
+
|
8
|
+
class SemianError < ::Typhoeus::Errors::TyphoeusError
|
9
|
+
def initialize(semian_identifier, *args)
|
10
|
+
super(*args)
|
11
|
+
@semian_identifier = semian_identifier
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
ResourceBusyError = Class.new(SemianError)
|
16
|
+
CircuitOpenError = Class.new(SemianError)
|
17
|
+
end
|
18
|
+
|
19
|
+
module Semian
|
20
|
+
module Typhoeus
|
21
|
+
include Semian::Adapter
|
22
|
+
|
23
|
+
ResourceBusyError = ::Typhoeus::ResourceBusyError
|
24
|
+
CircuitOpenError = ::Typhoeus::CircuitOpenError
|
25
|
+
|
26
|
+
class SemianConfigurationChangedError < RuntimeError
|
27
|
+
def initialize(msg = "Cannot re-initialize semian_configuration")
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def semian_identifier
|
33
|
+
"typhoeus_#{raw_semian_options[:name]}"
|
34
|
+
end
|
35
|
+
|
36
|
+
DEFAULT_ERRORS = [
|
37
|
+
::Timeout::Error,
|
38
|
+
::SocketError,
|
39
|
+
::Typhoeus::Errors::NoStub,
|
40
|
+
::Typhoeus::Errors::TyphoeusError,
|
41
|
+
::EOFError,
|
42
|
+
::IOError,
|
43
|
+
::SystemCallError, # includes ::Errno::EINVAL, ::Errno::ECONNRESET, ::Errno::ECONNREFUSED, ::Errno::ETIMEDOUT, and more
|
44
|
+
].freeze # Typhoeus can throw many different errors, this tries to capture most of them
|
45
|
+
|
46
|
+
class << self
|
47
|
+
attr_accessor :exceptions
|
48
|
+
attr_reader :semian_configuration
|
49
|
+
|
50
|
+
def semian_configuration=(configuration)
|
51
|
+
raise Semian::Typhoeus::SemianConfigurationChangedError unless @semian_configuration.nil?
|
52
|
+
@semian_configuration = configuration
|
53
|
+
end
|
54
|
+
|
55
|
+
def retrieve_semian_configuration(host, port)
|
56
|
+
@semian_configuration.call(host, port) if @semian_configuration.respond_to?(:call)
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset_exceptions
|
60
|
+
self.exceptions = Semian::Typhoeus::DEFAULT_ERRORS.dup
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Semian::Typhoeus.reset_exceptions
|
65
|
+
|
66
|
+
def raw_semian_options
|
67
|
+
@raw_semian_options ||= begin
|
68
|
+
uri = URI.parse(url)
|
69
|
+
@raw_semian_options = Semian::Typhoeus.retrieve_semian_configuration(uri.host, uri.port)
|
70
|
+
@raw_semian_options = @raw_semian_options.dup unless @raw_semian_options.nil?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def resource_exceptions
|
75
|
+
Semian::Typhoeus.exceptions
|
76
|
+
end
|
77
|
+
|
78
|
+
def disabled?
|
79
|
+
raw_semian_options.nil?
|
80
|
+
end
|
81
|
+
|
82
|
+
def run
|
83
|
+
return super if disabled?
|
84
|
+
|
85
|
+
acquire_semian_resource(adapter: :typhoeus, scope: :query) { handle_error_responses(super) }
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def handle_error_responses(result)
|
91
|
+
semian_resource.mark_failed(TyphoeusError.new(result.return_message)) if !result.success?
|
92
|
+
result
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class TyphoeusError < StandardError
|
98
|
+
def initialize(msg)
|
99
|
+
super(msg)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
Typhoeus::Request.prepend(Semian::Typhoeus)
|