fl-thrift_client 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +37 -3
- data/Manifest +1 -0
- data/README.rdoc +10 -0
- data/Rakefile +2 -4
- data/lib/thrift_client.rb +8 -20
- data/lib/thrift_client/abstract_thrift_client.rb +141 -153
- data/lib/thrift_client/connection.rb +4 -5
- data/lib/thrift_client/event_machine.rb +20 -20
- data/lib/thrift_client/simple.rb +11 -2
- data/lib/thrift_client/thrift.rb +19 -0
- data/test/greeter/greeter.rb +48 -4
- data/test/greeter/greeter.thrift +1 -0
- data/test/greeter/server.rb +4 -0
- data/test/multiple_working_servers_test.rb +1 -1
- data/test/simple_test.rb +1 -2
- data/test/test_helper.rb +1 -1
- data/test/thrift_client_http_test.rb +2 -10
- data/test/thrift_client_test.rb +137 -26
- data/thrift_client.gemspec +1 -1
- metadata +3 -3
data/CHANGELOG
CHANGED
@@ -1,9 +1,43 @@
|
|
1
|
-
v0.
|
1
|
+
v0.7.1 Added support for :before_method and :on_exception callback types.
|
2
|
+
Added support for registering multiple callbacks of a given type.
|
2
3
|
|
3
|
-
v0.
|
4
|
+
v0.7.0 Updated thrift gem dependency to 0.7.0
|
5
|
+
|
6
|
+
v0.6.3 Document the :connect_timeout option.
|
7
|
+
Add support for specifying client-side timeouts when using FramedTransport
|
8
|
+
set transport timeout after connection is established
|
9
|
+
Add a method `add_callback` allowing a client to register a block that is invoked at a certain event.
|
10
|
+
Fixup socket timeouts.
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
v0.6.2 Remove lingering thrift v0.5.0 reference.
|
15
|
+
|
16
|
+
v0.6.1 Add connect timeout. Bump thrift dependency to ~> v0.6.0.
|
17
|
+
|
18
|
+
v0.6.0 Fix bug where we'd try to mark the current server down when we didn't have a current server.
|
19
|
+
Upgrade to thrift 0.5.
|
20
|
+
|
21
|
+
v0.5.0 Add support for wrapping exceptions, so that Thrift::Foo can become Greeter::Foo.
|
22
|
+
Make server_retry_period work the way you expect.
|
23
|
+
Better bookkeeping around marking servers as dead.
|
24
|
+
|
25
|
+
v0.4.7 fix thrift gem dependency
|
26
|
+
|
27
|
+
v0.4.6 Add support for oneway methods.
|
28
|
+
|
29
|
+
v0.4.5. Fix broken retries.
|
30
|
+
|
31
|
+
v0.4.4. Default to 0 retries rather than the number of servers.
|
32
|
+
|
33
|
+
v0.4.3. Bug fixes: handle_exception could be called more than once. Integer types are read signed.
|
34
|
+
|
35
|
+
v0.4.2. Allow per-method overrides of retries. Fix several bugs with EventMachine.
|
36
|
+
|
37
|
+
v0.4.1. Making ThriftClient decoratable. Able to add new functionality a class definition.
|
4
38
|
|
5
39
|
v0.4.0. Add new EventMachine transport. This requires two layers of transport
|
6
|
-
configurability:
|
40
|
+
configurability:
|
7
41
|
options[:transport] for EventMachine or Socket transports
|
8
42
|
options[:transport_wrapper] for optional Buffered or Framed Transport.
|
9
43
|
Clients will need to update their options to ensure they don't conflict with this change. (mperham)
|
data/Manifest
CHANGED
data/README.rdoc
CHANGED
@@ -30,6 +30,16 @@ You can then make calls to the server via the <tt>client</tt> instance as if was
|
|
30
30
|
|
31
31
|
On failures, the client will try the remaining servers in the list before giving up. See ThriftClient for more.
|
32
32
|
|
33
|
+
== Timeouts
|
34
|
+
|
35
|
+
Timeouts are enforced per-try, so if you have a timeout of n and do m retries, the total time it could take is n*m.
|
36
|
+
|
37
|
+
== Connection Handling
|
38
|
+
|
39
|
+
The library will shuffle the host list then work its way down this list, only moving to the next host if it received an error or you've doing more than server_max_requests requests with that host (defaults to 0 which means there's no limit).
|
40
|
+
|
41
|
+
Servers that throw an error get marked as dead and will only be retried every server_retry_period seconds (at that time all dead servers are retried, no matter long they've been marked as dead).
|
42
|
+
|
33
43
|
== Installation
|
34
44
|
|
35
45
|
You need Ruby 1.8 or 1.9. If you have those, just run:
|
data/Rakefile
CHANGED
@@ -3,14 +3,12 @@ require 'rubygems'
|
|
3
3
|
require 'echoe'
|
4
4
|
|
5
5
|
Echoe.new("thrift_client") do |p|
|
6
|
-
p.author = "Evan Weaver"
|
6
|
+
p.author = ["Evan Weaver", "Ryan King", "Jeff Hodges"]
|
7
7
|
p.project = "fauna"
|
8
8
|
p.summary = "A Thrift client wrapper that encapsulates some common failover behavior."
|
9
9
|
p.rubygems_version = ">= 0.8"
|
10
|
-
p.dependencies = ['thrift']
|
10
|
+
p.dependencies = ['thrift ~>0.7.0']
|
11
11
|
p.ignore_pattern = /^(vendor\/thrift)/
|
12
12
|
p.rdoc_pattern = /^(lib|bin|tasks|ext)|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
|
13
|
-
p.url = "http://blog.evanweaver.com/files/doc/fauna/thrift_client/"
|
14
|
-
p.docs_host = "blog.evanweaver.com:~/www/bax/public/files/doc/"
|
15
13
|
p.spec_pattern = "spec/*_spec.rb"
|
16
14
|
end
|
data/lib/thrift_client.rb
CHANGED
@@ -1,23 +1,10 @@
|
|
1
|
-
|
2
|
-
$LOAD_PATH.unshift("/Users/eweaver/p/twitter/rails/vendor/gems/thrift-751142/lib")
|
3
|
-
$LOAD_PATH.unshift("/Users/eweaver/p/twitter/rails/vendor/gems/thrift-751142/ext")
|
4
|
-
require 'thrift'
|
5
|
-
else
|
6
|
-
require 'rubygems'
|
7
|
-
require 'thrift'
|
8
|
-
end
|
9
|
-
|
10
|
-
require 'rubygems'
|
1
|
+
require 'thrift'
|
11
2
|
require 'thrift_client/thrift'
|
12
3
|
require 'thrift_client/connection'
|
13
4
|
require 'thrift_client/abstract_thrift_client'
|
14
5
|
|
15
6
|
class ThriftClient < AbstractThriftClient
|
16
|
-
# This error is for backwards compatibility only. If defined in
|
17
|
-
# RetryingThriftClient instead, causes the test suite will break.
|
18
7
|
class NoServersAvailable < StandardError; end
|
19
|
-
include RetryingThriftClient
|
20
|
-
include TimingOutThriftClient
|
21
8
|
|
22
9
|
=begin rdoc
|
23
10
|
Create a new ThriftClient instance. Accepts an internal Thrift client class (such as CassandraRb::Client), a list of servers with ports, and optional parameters.
|
@@ -26,17 +13,18 @@ Valid optional parameters are:
|
|
26
13
|
|
27
14
|
<tt>:protocol</tt>:: Which Thrift protocol to use. Defaults to <tt>Thrift::BinaryProtocol</tt>.
|
28
15
|
<tt>:protocol_extra_params</tt>:: An array of additional parameters to pass to the protocol initialization call. Defaults to <tt>[]</tt>.
|
29
|
-
<tt>:transport</tt>:: Which Thrift transport to use. Defaults to <tt>Thrift::
|
30
|
-
<tt>:
|
31
|
-
<tt>:exception_classes</tt>:: Which exceptions to catch and retry when sending a request. Defaults to <tt>[IOError, Thrift::Exception, Thrift::
|
16
|
+
<tt>:transport</tt>:: Which Thrift transport to use. Defaults to <tt>Thrift::Socket</tt>.
|
17
|
+
<tt>:transport_wrapper</tt>:: Which Thrift transport wrapper to use. Defaults to <tt>Thrift::FramedTransport</tt>.
|
18
|
+
<tt>:exception_classes</tt>:: Which exceptions to catch and retry when sending a request. Defaults to <tt>[IOError, Thrift::Exception, Thrift::ApplicationException, Thrift::TransportException, NoServersAvailable]</tt>
|
19
|
+
<tt>:exception_class_overrides</tt>:: For specifying children of classes in exception_classes for which you don't want to retry or reconnect.
|
32
20
|
<tt>:raise</tt>:: Whether to reraise errors if no responsive servers are found. Defaults to <tt>true</tt>.
|
33
|
-
<tt>:retries</tt>:: How many times to retry a request. Defaults to
|
34
|
-
<tt>:server_retry_period</tt>:: How many seconds to wait before trying to reconnect
|
21
|
+
<tt>:retries</tt>:: How many times to retry a request. Defaults to 0.
|
22
|
+
<tt>:server_retry_period</tt>:: How many seconds to wait before trying to reconnect to a dead server. Defaults to <tt>1</tt>. Set to <tt>nil</tt> to disable.
|
35
23
|
<tt>:server_max_requests</tt>:: How many requests to perform before moving on to the next server in the pool, regardless of error status. Defaults to <tt>nil</tt> (no limit).
|
36
24
|
<tt>:timeout</tt>:: Specify the default timeout in seconds. Defaults to <tt>1</tt>.
|
25
|
+
<tt>:connect_timeout</tt>:: Specify the connection timeout in seconds. Defaults to <tt>0.1</tt>.
|
37
26
|
<tt>:timeout_overrides</tt>:: Specify additional timeouts on a per-method basis, in seconds. Only works with <tt>Thrift::BufferedTransport</tt>.
|
38
27
|
<tt>:defaults</tt>:: Specify default values to return on a per-method basis, if <tt>:raise</tt> is set to false.
|
39
|
-
|
40
28
|
=end rdoc
|
41
29
|
|
42
30
|
def initialize(client_class, servers, options = {})
|
@@ -1,29 +1,95 @@
|
|
1
1
|
class AbstractThriftClient
|
2
2
|
|
3
|
+
class Server
|
4
|
+
attr_reader :connection_string, :marked_down_at
|
5
|
+
|
6
|
+
def initialize(connection_string)
|
7
|
+
@connection_string = connection_string
|
8
|
+
end
|
9
|
+
alias to_s connection_string
|
10
|
+
|
11
|
+
def mark_down!
|
12
|
+
@marked_down_at = Time.now
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
DISCONNECT_ERRORS = [
|
17
|
+
IOError,
|
18
|
+
Thrift::Exception,
|
19
|
+
Thrift::ApplicationException,
|
20
|
+
Thrift::TransportException
|
21
|
+
]
|
22
|
+
|
23
|
+
DEFAULT_WRAPPED_ERRORS = [
|
24
|
+
Thrift::ApplicationException,
|
25
|
+
Thrift::TransportException,
|
26
|
+
]
|
27
|
+
|
3
28
|
DEFAULTS = {
|
4
29
|
:protocol => Thrift::BinaryProtocol,
|
5
30
|
:protocol_extra_params => [],
|
6
31
|
:transport => Thrift::Socket,
|
7
32
|
:transport_wrapper => Thrift::FramedTransport,
|
8
33
|
:raise => true,
|
9
|
-
:defaults => {}
|
10
|
-
|
34
|
+
:defaults => {},
|
35
|
+
:exception_classes => DISCONNECT_ERRORS,
|
36
|
+
:exception_class_overrides => [],
|
37
|
+
:retries => 0,
|
38
|
+
:server_retry_period => 1,
|
39
|
+
:server_max_requests => nil,
|
40
|
+
:retry_overrides => {},
|
41
|
+
:wrapped_exception_classes => DEFAULT_WRAPPED_ERRORS,
|
42
|
+
:connect_timeout => 0.1,
|
43
|
+
:timeout => 1,
|
44
|
+
:timeout_overrides => {}
|
45
|
+
}
|
11
46
|
|
12
47
|
attr_reader :client, :client_class, :current_server, :server_list, :options, :client_methods
|
13
48
|
|
14
49
|
def initialize(client_class, servers, options = {})
|
15
50
|
@options = DEFAULTS.merge(options)
|
51
|
+
@options[:server_retry_period] ||= 0
|
16
52
|
@client_class = client_class
|
17
|
-
@server_list = Array(servers)
|
53
|
+
@server_list = Array(servers).collect{|s| Server.new(s)}.sort_by { rand }
|
18
54
|
@current_server = @server_list.first
|
19
55
|
|
56
|
+
@callbacks = {}
|
20
57
|
@client_methods = []
|
21
58
|
@client_class.instance_methods.each do |method_name|
|
22
|
-
if method_name =~ /^
|
59
|
+
if method_name != 'send_message' && method_name =~ /^send_(.*)$/
|
23
60
|
instance_eval("def #{$1}(*args); handled_proxy(:'#{$1}', *args); end", __FILE__, __LINE__)
|
24
61
|
@client_methods << $1
|
25
62
|
end
|
26
63
|
end
|
64
|
+
@request_count = 0
|
65
|
+
@options[:wrapped_exception_classes].each do |exception_klass|
|
66
|
+
name = exception_klass.to_s.split('::').last
|
67
|
+
begin
|
68
|
+
@client_class.const_get(name)
|
69
|
+
rescue NameError
|
70
|
+
@client_class.const_set(name, Class.new(exception_klass))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Adds a callback that will be invoked at a certain time. The valid callback types are:
|
76
|
+
# :post_connect - should accept a single AbstractThriftClient argument, which is the client object to
|
77
|
+
# which the callback was added. Called after a connection to the remote thrift server
|
78
|
+
# is established.
|
79
|
+
# :before_method - should accept a single method name argument. Called before a method is invoked on the
|
80
|
+
# thrift server.
|
81
|
+
# :on_exception - should accept 2 args: an Exception instance and a method name. Called right before the
|
82
|
+
# exception is raised.
|
83
|
+
def add_callback(callback_type, &block)
|
84
|
+
case callback_type
|
85
|
+
when :post_connect, :before_method, :on_exception
|
86
|
+
@callbacks[callback_type] ||= []
|
87
|
+
@callbacks[callback_type].push(block)
|
88
|
+
# Allow chaining
|
89
|
+
return self
|
90
|
+
else
|
91
|
+
return nil
|
92
|
+
end
|
27
93
|
end
|
28
94
|
|
29
95
|
def inspect
|
@@ -34,184 +100,106 @@ class AbstractThriftClient
|
|
34
100
|
# called as the connection will be made on the first RPC method
|
35
101
|
# call.
|
36
102
|
def connect!
|
37
|
-
@
|
103
|
+
@current_server = next_live_server
|
104
|
+
@connection = Connection::Factory.create(@options[:transport], @options[:transport_wrapper], @current_server.connection_string, @options[:connect_timeout])
|
38
105
|
@connection.connect!
|
39
|
-
|
40
|
-
|
41
|
-
@transport
|
42
|
-
|
106
|
+
transport = @connection.transport
|
107
|
+
transport.timeout = @options[:timeout] if transport_can_timeout?
|
108
|
+
@client = @client_class.new(@options[:protocol].new(transport, *@options[:protocol_extra_params]))
|
109
|
+
do_callbacks(:post_connect, self)
|
43
110
|
end
|
44
111
|
|
45
112
|
def disconnect!
|
46
|
-
@connection.close rescue nil
|
113
|
+
@connection.close rescue nil #TODO
|
47
114
|
@client = nil
|
48
115
|
@current_server = nil
|
116
|
+
@request_count = 0
|
49
117
|
end
|
50
118
|
|
51
119
|
private
|
52
|
-
def handled_proxy(method_name, *args)
|
53
|
-
proxy(method_name, *args)
|
54
|
-
rescue Exception => e
|
55
|
-
handle_exception(e, method_name, args)
|
56
|
-
end
|
57
|
-
|
58
|
-
def proxy(method_name, *args)
|
59
|
-
connect! unless @client
|
60
|
-
send_rpc(method_name, *args)
|
61
|
-
end
|
62
120
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
@connection.close rescue nil
|
69
|
-
@client = nil
|
70
|
-
@current_server = nil
|
71
|
-
end
|
72
|
-
|
73
|
-
def handle_exception(e, method_name, args=nil)
|
74
|
-
raise e if @options[:raise]
|
75
|
-
@options[:defaults][method_name.to_sym]
|
76
|
-
end
|
77
|
-
|
78
|
-
module RetryingThriftClient
|
79
|
-
DISCONNECT_ERRORS = [
|
80
|
-
IOError,
|
81
|
-
Thrift::Exception,
|
82
|
-
Thrift::ProtocolException,
|
83
|
-
Thrift::ApplicationException,
|
84
|
-
Thrift::TransportException
|
85
|
-
].freeze
|
86
|
-
|
87
|
-
RETRYING_DEFAULTS = {
|
88
|
-
:exception_classes => DISCONNECT_ERRORS,
|
89
|
-
:randomize_server_list => true,
|
90
|
-
:retries => nil,
|
91
|
-
:server_retry_period => 1,
|
92
|
-
:server_max_requests => nil,
|
93
|
-
:retry_overrides => {}
|
94
|
-
}.freeze
|
95
|
-
|
96
|
-
def initialize(client_class, servers, options = {})
|
97
|
-
super
|
98
|
-
@options = RETRYING_DEFAULTS.merge(@options) # @options is set by super
|
99
|
-
@retries = options[:retries] || @server_list.size
|
100
|
-
@request_count = 0
|
101
|
-
@max_requests = @options[:server_max_requests]
|
102
|
-
@retry_period = @options[:server_retry_period]
|
103
|
-
rebuild_live_server_list!
|
121
|
+
# Calls all callbacks of the specified type with the given args
|
122
|
+
def do_callbacks(callback_type_sym, *args)
|
123
|
+
return unless @callbacks[callback_type_sym]
|
124
|
+
@callbacks[callback_type_sym].each do |callback|
|
125
|
+
callback.call(*args)
|
104
126
|
end
|
127
|
+
end
|
105
128
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
def disconnect!
|
114
|
-
# Keep live servers in the list if we have a retry period. Otherwise,
|
115
|
-
# always eject, because we will always re-add them.
|
116
|
-
if @retry_period && @current_server
|
117
|
-
@live_server_list.unshift(@current_server)
|
118
|
-
end
|
119
|
-
|
120
|
-
super()
|
121
|
-
@request_count = 0
|
122
|
-
end
|
123
|
-
|
124
|
-
private
|
125
|
-
|
126
|
-
def next_server
|
127
|
-
if @retry_period
|
128
|
-
rebuild_live_server_list! if Time.now > @last_rebuild + @retry_period
|
129
|
-
raise ThriftClient::NoServersAvailable, "No live servers in #{@server_list.inspect} since #{@last_rebuild.inspect}." if @live_server_list.empty?
|
130
|
-
elsif @live_server_list.empty?
|
131
|
-
rebuild_live_server_list!
|
129
|
+
def next_live_server
|
130
|
+
@server_index ||= 0
|
131
|
+
@server_list.length.times do |i|
|
132
|
+
cur = (1 + @server_index + i) % @server_list.length
|
133
|
+
if !@server_list[cur].marked_down_at || (@server_list[cur].marked_down_at + @options[:server_retry_period] <= Time.now)
|
134
|
+
@server_index = cur
|
135
|
+
return @server_list[cur]
|
132
136
|
end
|
133
|
-
@live_server_list.pop
|
134
137
|
end
|
138
|
+
raise ThriftClient::NoServersAvailable, "No live servers in #{@server_list.inspect} since #{@last_rebuild.inspect}."
|
139
|
+
end
|
135
140
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
@
|
141
|
+
def handled_proxy(method_name, *args)
|
142
|
+
disconnect_on_max! if @options[:server_max_requests] && @request_count >= @options[:server_max_requests]
|
143
|
+
begin
|
144
|
+
connect! unless @client
|
145
|
+
if has_timeouts?
|
146
|
+
@client.timeout = @options[:timeout_overrides][method_name.to_sym] || @options[:timeout]
|
142
147
|
end
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
end
|
149
|
-
|
150
|
-
def proxy(method_name, *args)
|
151
|
-
super
|
148
|
+
@request_count += 1
|
149
|
+
do_callbacks(:before_method, method_name)
|
150
|
+
@client.send(method_name, *args)
|
151
|
+
rescue *@options[:exception_class_overrides] => e
|
152
|
+
raise_or_default(e, method_name)
|
152
153
|
rescue *@options[:exception_classes] => e
|
153
154
|
disconnect_on_error!
|
154
|
-
tries ||= (@options[:retry_overrides][method_name.to_sym] || @retries)
|
155
|
+
tries ||= (@options[:retry_overrides][method_name.to_sym] || @options[:retries]) + 1
|
155
156
|
tries -= 1
|
156
|
-
tries
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
def disconnect_on_max!
|
165
|
-
@live_server_list.push(@current_server)
|
166
|
-
disconnect_on_error!
|
167
|
-
end
|
168
|
-
|
169
|
-
def disconnect_on_error!
|
170
|
-
super
|
171
|
-
@request_count = 0
|
157
|
+
if tries > 0
|
158
|
+
retry
|
159
|
+
else
|
160
|
+
raise_or_default(e, method_name)
|
161
|
+
end
|
162
|
+
rescue Exception => e
|
163
|
+
raise_or_default(e, method_name)
|
172
164
|
end
|
173
|
-
|
174
165
|
end
|
175
166
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
def initialize(client_class, servers, options = {})
|
183
|
-
super
|
184
|
-
@options = TIMINGOUT_DEFAULTS.merge(@options)
|
167
|
+
def raise_or_default(e, method_name)
|
168
|
+
if @options[:raise]
|
169
|
+
raise_wrapped_error(e, method_name)
|
170
|
+
else
|
171
|
+
@options[:defaults][method_name.to_sym]
|
185
172
|
end
|
173
|
+
end
|
186
174
|
|
187
|
-
|
188
|
-
|
189
|
-
|
175
|
+
def raise_wrapped_error(e, method_name)
|
176
|
+
do_callbacks(:on_exception, e, method_name)
|
177
|
+
if @options[:wrapped_exception_classes].include?(e.class)
|
178
|
+
raise @client_class.const_get(e.class.to_s.split('::').last), e.message, e.backtrace
|
179
|
+
else
|
180
|
+
raise e
|
190
181
|
end
|
182
|
+
end
|
191
183
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
@client_methods.each do |method_name|
|
196
|
-
@client.timeout = @options[:timeout_overrides][method_name.to_sym] || @options[:timeout]
|
197
|
-
end
|
198
|
-
end
|
184
|
+
def disconnect_on_max!
|
185
|
+
disconnect!
|
186
|
+
end
|
199
187
|
|
200
|
-
|
201
|
-
|
202
|
-
|
188
|
+
def disconnect_on_error!
|
189
|
+
@current_server.mark_down! if @current_server
|
190
|
+
disconnect!
|
191
|
+
end
|
203
192
|
|
204
|
-
|
205
|
-
|
206
|
-
|
193
|
+
def has_timeouts?
|
194
|
+
@has_timeouts ||= @options[:timeout_overrides].any? && transport_can_timeout?
|
195
|
+
end
|
207
196
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
end
|
197
|
+
def transport_can_timeout?
|
198
|
+
if (@options[:transport_wrapper] || @options[:transport]).method_defined?(:timeout=)
|
199
|
+
true
|
200
|
+
else
|
201
|
+
warn "ThriftClient: Timeout overrides have no effect with with transport type #{(@options[:transport_wrapper] || @options[:transport])}"
|
202
|
+
false
|
215
203
|
end
|
216
204
|
end
|
217
205
|
end
|
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
require "
|
3
|
-
require "
|
4
|
-
require "
|
5
|
-
require "factory"
|
1
|
+
require "thrift_client/connection/base"
|
2
|
+
require "thrift_client/connection/socket"
|
3
|
+
require "thrift_client/connection/http"
|
4
|
+
require "thrift_client/connection/factory"
|
@@ -26,6 +26,8 @@ module Thrift
|
|
26
26
|
fiber.resume
|
27
27
|
end
|
28
28
|
Fiber.yield
|
29
|
+
|
30
|
+
raise Thrift::TransportException, "Unable to connect to #{@host}:#{@port}" unless @connection.connected?
|
29
31
|
@connection
|
30
32
|
end
|
31
33
|
|
@@ -65,32 +67,30 @@ module Thrift
|
|
65
67
|
def initialize(host, port=9090)
|
66
68
|
@host, @port = host, port
|
67
69
|
@index = 0
|
68
|
-
@
|
70
|
+
@disconnected = 'not connected'
|
69
71
|
@buf = ''
|
70
72
|
end
|
71
73
|
|
72
74
|
def close
|
73
75
|
trap do
|
74
|
-
@
|
76
|
+
@disconnected = 'closed'
|
75
77
|
close_connection(true)
|
76
78
|
end
|
77
79
|
end
|
78
80
|
|
79
81
|
def blocking_read(size)
|
80
|
-
raise IOError, "lost connection to #{@host}:#{@port}"
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
Fiber.yield
|
93
|
-
end
|
82
|
+
raise IOError, "lost connection to #{@host}:#{@port}: #{@disconnected}" if @disconnected
|
83
|
+
if can_read?(size)
|
84
|
+
yank(size)
|
85
|
+
else
|
86
|
+
raise ArgumentError, "Unexpected state" if @size or @callback
|
87
|
+
|
88
|
+
fiber = Fiber.current
|
89
|
+
@size = size
|
90
|
+
@callback = proc { |data|
|
91
|
+
fiber.resume(data)
|
92
|
+
}
|
93
|
+
Fiber.yield
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
@@ -108,17 +108,17 @@ module Thrift
|
|
108
108
|
end
|
109
109
|
|
110
110
|
def connected?
|
111
|
-
|
111
|
+
!@disconnected
|
112
112
|
end
|
113
113
|
|
114
114
|
def connection_completed
|
115
|
-
@
|
115
|
+
@disconnected = nil
|
116
116
|
succeed
|
117
117
|
end
|
118
118
|
|
119
119
|
def unbind
|
120
|
-
if
|
121
|
-
@
|
120
|
+
if !@disconnected
|
121
|
+
@disconnected = 'unbound'
|
122
122
|
else
|
123
123
|
fail
|
124
124
|
end
|
data/lib/thrift_client/simple.rb
CHANGED
@@ -100,7 +100,8 @@ class ThriftClient
|
|
100
100
|
s.read(len)
|
101
101
|
when I64
|
102
102
|
hi, lo = s.read(8).unpack("NN")
|
103
|
-
(hi << 32) | lo
|
103
|
+
rv = (hi << 32) | lo
|
104
|
+
(rv >= (1 << 63)) ? (rv - (1 << 64)) : rv
|
104
105
|
when LIST
|
105
106
|
read_list(s)
|
106
107
|
when MAP
|
@@ -114,7 +115,15 @@ class ThriftClient
|
|
114
115
|
when StructType
|
115
116
|
read_struct(s, type.struct_class)
|
116
117
|
else
|
117
|
-
s.read(SIZES[type]).unpack(FORMATS[type]).first
|
118
|
+
rv = s.read(SIZES[type]).unpack(FORMATS[type]).first
|
119
|
+
case type
|
120
|
+
when I16
|
121
|
+
(rv >= (1 << 15)) ? (rv - (1 << 16)) : rv
|
122
|
+
when I32
|
123
|
+
(rv >= (1 << 31)) ? (rv - (1 << 32)) : rv
|
124
|
+
else
|
125
|
+
rv
|
126
|
+
end
|
118
127
|
end
|
119
128
|
end
|
120
129
|
|
data/lib/thrift_client/thrift.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
module Thrift
|
2
|
+
class BaseTransport
|
3
|
+
def timeout=(timeout)
|
4
|
+
end
|
5
|
+
|
6
|
+
def timeout
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
2
11
|
class BufferedTransport
|
3
12
|
def timeout=(timeout)
|
4
13
|
@transport.timeout = timeout
|
@@ -9,6 +18,16 @@ module Thrift
|
|
9
18
|
end
|
10
19
|
end
|
11
20
|
|
21
|
+
class FramedTransport
|
22
|
+
def timeout=(timeout)
|
23
|
+
@transport.timeout = timeout
|
24
|
+
end
|
25
|
+
|
26
|
+
def timeout
|
27
|
+
@transport.timeout
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
12
31
|
module Client
|
13
32
|
def timeout=(timeout)
|
14
33
|
@iprot.trans.timeout = timeout
|
data/test/greeter/greeter.rb
CHANGED
@@ -25,6 +25,13 @@ module Greeter
|
|
25
25
|
raise ::Thrift::ApplicationException.new(::Thrift::ApplicationException::MISSING_RESULT, 'greeting failed: unknown result')
|
26
26
|
end
|
27
27
|
|
28
|
+
def yo(name)
|
29
|
+
send_yo(name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def send_yo(name)
|
33
|
+
send_message('yo', Yo_args, :name => name)
|
34
|
+
end
|
28
35
|
end
|
29
36
|
|
30
37
|
class Processor
|
@@ -37,15 +44,20 @@ module Greeter
|
|
37
44
|
write_result(result, oprot, 'greeting', seqid)
|
38
45
|
end
|
39
46
|
|
47
|
+
def process_yo(seqid, iprot, oprot)
|
48
|
+
args = read_args(iprot, Yo_args)
|
49
|
+
@handler.yo(args.name)
|
50
|
+
return
|
51
|
+
end
|
52
|
+
|
40
53
|
end
|
41
54
|
|
42
55
|
# HELPER FUNCTIONS AND STRUCTURES
|
43
56
|
|
44
57
|
class Greeting_args
|
45
|
-
include ::Thrift::Struct
|
58
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
46
59
|
NAME = 1
|
47
60
|
|
48
|
-
::Thrift::Struct.field_accessor self, :name
|
49
61
|
FIELDS = {
|
50
62
|
NAME => {:type => ::Thrift::Types::STRING, :name => 'name'}
|
51
63
|
}
|
@@ -55,13 +67,13 @@ module Greeter
|
|
55
67
|
def validate
|
56
68
|
end
|
57
69
|
|
70
|
+
::Thrift::Struct.generate_accessors self
|
58
71
|
end
|
59
72
|
|
60
73
|
class Greeting_result
|
61
|
-
include ::Thrift::Struct
|
74
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
62
75
|
SUCCESS = 0
|
63
76
|
|
64
|
-
::Thrift::Struct.field_accessor self, :success
|
65
77
|
FIELDS = {
|
66
78
|
SUCCESS => {:type => ::Thrift::Types::STRING, :name => 'success'}
|
67
79
|
}
|
@@ -71,6 +83,38 @@ module Greeter
|
|
71
83
|
def validate
|
72
84
|
end
|
73
85
|
|
86
|
+
::Thrift::Struct.generate_accessors self
|
87
|
+
end
|
88
|
+
|
89
|
+
class Yo_args
|
90
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
91
|
+
NAME = 1
|
92
|
+
|
93
|
+
FIELDS = {
|
94
|
+
NAME => {:type => ::Thrift::Types::STRING, :name => 'name'}
|
95
|
+
}
|
96
|
+
|
97
|
+
def struct_fields; FIELDS; end
|
98
|
+
|
99
|
+
def validate
|
100
|
+
end
|
101
|
+
|
102
|
+
::Thrift::Struct.generate_accessors self
|
103
|
+
end
|
104
|
+
|
105
|
+
class Yo_result
|
106
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
107
|
+
|
108
|
+
FIELDS = {
|
109
|
+
|
110
|
+
}
|
111
|
+
|
112
|
+
def struct_fields; FIELDS; end
|
113
|
+
|
114
|
+
def validate
|
115
|
+
end
|
116
|
+
|
117
|
+
::Thrift::Struct.generate_accessors self
|
74
118
|
end
|
75
119
|
|
76
120
|
end
|
data/test/greeter/greeter.thrift
CHANGED
data/test/greeter/server.rb
CHANGED
data/test/simple_test.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require File.expand_path('test_helper.rb', File.dirname(__FILE__))
|
2
2
|
require "thrift/server/mongrel_http_server"
|
3
3
|
|
4
4
|
class ThriftClientHTTPTest < Test::Unit::TestCase
|
@@ -42,13 +42,5 @@ class ThriftClientHTTPTest < Test::Unit::TestCase
|
|
42
42
|
ThriftClient.new(Greeter::Client, "http://127.0.0.1:1463/greeter", @options).greeting("someone")
|
43
43
|
end
|
44
44
|
end
|
45
|
-
|
46
|
-
def test_non_random_fall_through
|
47
|
-
@servers = ["http://127.0.0.1:1463/greeter", "http://127.0.0.1:1461/greeter", "http://127.0.0.1:1462/greeter"]
|
48
|
-
assert_nothing_raised do
|
49
|
-
@options.merge!({ :protocol => Thrift::BinaryProtocol, :transport => Thrift::HTTPClientTransport })
|
50
|
-
ThriftClient.new(Greeter::Client, @servers, @options.merge(:randomize_server_list => false)).greeting("someone")
|
51
|
-
end
|
52
|
-
end
|
53
45
|
|
54
|
-
end
|
46
|
+
end
|
data/test/thrift_client_test.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require File.expand_path('test_helper.rb', File.dirname(__FILE__))
|
2
2
|
|
3
3
|
class ThriftClientTest < Test::Unit::TestCase
|
4
4
|
|
5
5
|
def setup
|
6
6
|
@servers = ["127.0.0.1:1461", "127.0.0.1:1462", "127.0.0.1:1463"]
|
7
|
-
@
|
7
|
+
@port = 1461
|
8
8
|
@timeout = 0.2
|
9
9
|
@options = {:protocol_extra_params => [false]}
|
10
10
|
@pid = Process.fork do
|
@@ -32,15 +32,28 @@ class ThriftClientTest < Test::Unit::TestCase
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
def
|
35
|
+
def test_dont_raise
|
36
36
|
assert_nothing_raised do
|
37
|
-
ThriftClient.new(Greeter::Client, @servers, @options.merge(:
|
37
|
+
ThriftClient.new(Greeter::Client, @servers.first, @options.merge(:raise => false)).greeting("someone")
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
41
|
+
def test_retries_correct_number_of_times
|
42
|
+
stub_server(@port) do |socket|
|
43
|
+
opts = @options.merge(:timeout => @timeout, :retries => 4, :server_retry_period => nil)
|
44
|
+
client = ThriftClient.new(Greeter::Client, "127.0.0.1:#{@port}", opts)
|
45
|
+
times_called = 0
|
46
|
+
|
47
|
+
singleton_class = (class << client; self end)
|
48
|
+
|
49
|
+
# disconnect_on_error! is called every time a server related
|
50
|
+
# connection error happens. it will be called every try (so, retries + 1)
|
51
|
+
singleton_class.send :define_method, :disconnect_on_error! do |*args|
|
52
|
+
times_called += 1; super *args
|
53
|
+
end
|
54
|
+
|
55
|
+
assert_raises(Greeter::Client::TransportException) { client.greeting("someone") }
|
56
|
+
assert_equal opts[:retries] + 1, times_called
|
44
57
|
end
|
45
58
|
end
|
46
59
|
|
@@ -57,7 +70,7 @@ class ThriftClientTest < Test::Unit::TestCase
|
|
57
70
|
def test_random_fall_through
|
58
71
|
assert_nothing_raised do
|
59
72
|
10.times do
|
60
|
-
client = ThriftClient.new(Greeter::Client, @servers, @options)
|
73
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:retries => 2))
|
61
74
|
client.greeting("someone")
|
62
75
|
client.disconnect!
|
63
76
|
end
|
@@ -70,20 +83,109 @@ class ThriftClientTest < Test::Unit::TestCase
|
|
70
83
|
end
|
71
84
|
end
|
72
85
|
|
86
|
+
def test_post_conn_cb
|
87
|
+
calledcnt = 0
|
88
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:retries => 2))
|
89
|
+
r = client.add_callback :post_connect do |cl|
|
90
|
+
calledcnt += 1
|
91
|
+
assert_equal(client, cl)
|
92
|
+
end
|
93
|
+
assert_equal(client, r)
|
94
|
+
assert_nothing_raised do
|
95
|
+
client.greeting("someone")
|
96
|
+
client.disconnect!
|
97
|
+
end
|
98
|
+
assert_equal(1, calledcnt)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_before_method_cb
|
102
|
+
before_method_counts = Hash.new { |hash, key| hash[key] = 0 }
|
103
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:retries => 2))
|
104
|
+
r = client.add_callback :before_method do |method_name|
|
105
|
+
before_method_counts[method_name.to_sym] += 1
|
106
|
+
end
|
107
|
+
assert_equal(client, r)
|
108
|
+
assert_nothing_raised do
|
109
|
+
client.greeting("someone")
|
110
|
+
client.yo("dude")
|
111
|
+
client.yo("dawg")
|
112
|
+
client.disconnect!
|
113
|
+
end
|
114
|
+
assert_equal({:greeting => 1, :yo => 2}, before_method_counts)
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_on_exception_cb
|
118
|
+
on_exception_counts = Hash.new { |h1, method_name| h1[method_name] = Hash.new { |h2, clazz| h2[clazz] = 0 }}
|
119
|
+
client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:retries => 2))
|
120
|
+
r = client.add_callback :on_exception do |error, method_name|
|
121
|
+
on_exception_counts[method_name.to_sym][error.class] += 1
|
122
|
+
end
|
123
|
+
assert_equal(client, r)
|
124
|
+
assert_raises(ThriftClient::NoServersAvailable) do
|
125
|
+
client.greeting("someone")
|
126
|
+
client.disconnect!
|
127
|
+
end
|
128
|
+
assert_equal({:greeting => {ThriftClient::NoServersAvailable => 1}}, on_exception_counts)
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_unknown_cb
|
132
|
+
calledcnt = 0
|
133
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:retries => 2))
|
134
|
+
r = client.add_callback :unknown do |cl|
|
135
|
+
assert(false)
|
136
|
+
end
|
137
|
+
assert_equal(nil, r)
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_multiple_cb
|
141
|
+
calledcnt = 0
|
142
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:retries => 2))
|
143
|
+
2.times do |i|
|
144
|
+
r = client.add_callback :post_connect do |cl|
|
145
|
+
calledcnt += 1
|
146
|
+
assert_equal(client, cl)
|
147
|
+
end
|
148
|
+
assert_equal(client, r)
|
149
|
+
end
|
150
|
+
assert_nothing_raised do
|
151
|
+
client.greeting("someone")
|
152
|
+
client.disconnect!
|
153
|
+
end
|
154
|
+
assert_equal(2, calledcnt)
|
155
|
+
end
|
156
|
+
|
73
157
|
def test_no_servers_eventually_raise
|
74
|
-
|
158
|
+
wascalled = false
|
159
|
+
client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:retries => 2))
|
160
|
+
client.add_callback :post_connect do
|
161
|
+
wascalled = true
|
162
|
+
end
|
75
163
|
assert_raises(ThriftClient::NoServersAvailable) do
|
76
164
|
client.greeting("someone")
|
77
165
|
client.disconnect!
|
78
166
|
end
|
167
|
+
assert(!wascalled)
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_socket_timeout
|
171
|
+
stub_server(@port) do |socket|
|
172
|
+
measurement = Benchmark.measure do
|
173
|
+
assert_raises(Greeter::Client::TransportException) do
|
174
|
+
ThriftClient.new(Greeter::Client, "127.0.0.1:#{@port}",
|
175
|
+
@options.merge(:timeout => 1, :connect_timeout => 0.5)
|
176
|
+
).greeting("someone")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
assert(measurement.real > 0.5 && measurement.real < 1.05)
|
180
|
+
end
|
79
181
|
end
|
80
182
|
|
81
183
|
def test_framed_transport_timeout
|
82
|
-
stub_server(@
|
184
|
+
stub_server(@port) do |socket|
|
83
185
|
measurement = Benchmark.measure do
|
84
|
-
assert_raises(
|
85
|
-
ThriftClient.new(Greeter::Client, "127.0.0.1:#{@
|
86
|
-
@options.merge(:timeout => @timeout)
|
186
|
+
assert_raises(Greeter::Client::TransportException) do
|
187
|
+
ThriftClient.new(Greeter::Client, "127.0.0.1:#{@port}",
|
188
|
+
@options.merge(:timeout => @timeout, :connect_timeout => @timeout)
|
87
189
|
).greeting("someone")
|
88
190
|
end
|
89
191
|
end
|
@@ -92,12 +194,13 @@ class ThriftClientTest < Test::Unit::TestCase
|
|
92
194
|
end
|
93
195
|
|
94
196
|
def test_buffered_transport_timeout
|
95
|
-
stub_server(@
|
197
|
+
stub_server(@port) do |socket|
|
96
198
|
measurement = Benchmark.measure do
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
199
|
+
client = ThriftClient.new(Greeter::Client, "127.0.0.1:#{@port}",
|
200
|
+
@options.merge(:timeout => @timeout, :transport_wrapper => Thrift::BufferedTransport, :connect_timeout => @timeout)
|
201
|
+
)
|
202
|
+
assert_raises(Greeter::Client::TransportException) do
|
203
|
+
client.greeting("someone")
|
101
204
|
end
|
102
205
|
end
|
103
206
|
assert((measurement.real > @timeout), "#{measurement.real} < #{@timeout}")
|
@@ -107,12 +210,13 @@ class ThriftClientTest < Test::Unit::TestCase
|
|
107
210
|
def test_buffered_transport_timeout_override
|
108
211
|
# FIXME Large timeout values always are applied twice for some bizarre reason
|
109
212
|
log_timeout = @timeout * 4
|
110
|
-
stub_server(@
|
213
|
+
stub_server(@port) do |socket|
|
111
214
|
measurement = Benchmark.measure do
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
215
|
+
client = ThriftClient.new(Greeter::Client, "127.0.0.1:#{@port}",
|
216
|
+
@options.merge(:timeout => @timeout, :timeout_overrides => {:greeting => log_timeout}, :transport_wrapper => Thrift::BufferedTransport)
|
217
|
+
)
|
218
|
+
assert_raises(Greeter::Client::TransportException) do
|
219
|
+
client.greeting("someone")
|
116
220
|
end
|
117
221
|
end
|
118
222
|
assert((measurement.real > log_timeout), "#{measurement.real} < #{log_timeout}")
|
@@ -120,21 +224,28 @@ class ThriftClientTest < Test::Unit::TestCase
|
|
120
224
|
end
|
121
225
|
|
122
226
|
def test_retry_period
|
123
|
-
client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:server_retry_period => 1))
|
227
|
+
client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:server_retry_period => 1, :retries => 2))
|
124
228
|
assert_raises(ThriftClient::NoServersAvailable) { client.greeting("someone") }
|
125
229
|
sleep 1.1
|
126
230
|
assert_raises(ThriftClient::NoServersAvailable) { client.greeting("someone") }
|
127
231
|
end
|
128
232
|
|
129
233
|
def test_client_with_retry_period_drops_servers
|
130
|
-
client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:server_retry_period => 1))
|
234
|
+
client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:server_retry_period => 1, :retries => 2))
|
131
235
|
assert_raises(ThriftClient::NoServersAvailable) { client.greeting("someone") }
|
132
236
|
sleep 1.1
|
133
237
|
assert_raises(ThriftClient::NoServersAvailable) { client.greeting("someone") }
|
134
238
|
end
|
135
239
|
|
240
|
+
def test_oneway_method
|
241
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:server_max_requests => 2, :retries => 2))
|
242
|
+
assert_nothing_raised do
|
243
|
+
response = client.yo("dude")
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
136
247
|
def test_server_max_requests_with_downed_servers
|
137
|
-
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:server_max_requests => 2))
|
248
|
+
client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:server_max_requests => 2, :retries => 2))
|
138
249
|
client.greeting("someone")
|
139
250
|
internal_client = client.client
|
140
251
|
client.greeting("someone")
|
data/thrift_client.gemspec
CHANGED