thrift_client 0.4.7 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ v0.5.0 Add support for wrapping exception, so that Thrift::Foo can become Greeter::Foo.
2
+ Make server_retry_period work the way you expect.
3
+ Better bookkeeping around marking servers as dead.
4
+
1
5
  v0.4.7 fix thrift gem dependency
2
6
 
3
7
  v0.4.6 Add support for oneway methods.
@@ -1,20 +1,54 @@
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
- }.freeze
34
+ :defaults => {},
35
+ :exception_classes => DISCONNECT_ERRORS,
36
+ :retries => 0,
37
+ :server_retry_period => 1,
38
+ :server_max_requests => nil,
39
+ :retry_overrides => {},
40
+ :wrapped_exception_classes => DEFAULT_WRAPPED_ERRORS,
41
+ :timeout => 1,
42
+ :timeout_overrides => {}
43
+ }
11
44
 
12
45
  attr_reader :client, :client_class, :current_server, :server_list, :options, :client_methods
13
46
 
14
47
  def initialize(client_class, servers, options = {})
15
48
  @options = DEFAULTS.merge(options)
49
+ @options[:server_retry_period] ||= 0
16
50
  @client_class = client_class
17
- @server_list = Array(servers)
51
+ @server_list = Array(servers).collect{|s| Server.new(s)}.sort_by { rand }
18
52
  @current_server = @server_list.first
19
53
 
20
54
  @client_methods = []
@@ -24,6 +58,15 @@ class AbstractThriftClient
24
58
  @client_methods << $1
25
59
  end
26
60
  end
61
+ @request_count = 0
62
+ @options[:wrapped_exception_classes].each do |exception_klass|
63
+ name = exception_klass.to_s.split('::').last
64
+ begin
65
+ @client_class.const_get(name)
66
+ rescue NameError
67
+ @client_class.const_set(name, Class.new(exception_klass))
68
+ end
69
+ end
27
70
  end
28
71
 
29
72
  def inspect
@@ -34,180 +77,91 @@ class AbstractThriftClient
34
77
  # called as the connection will be made on the first RPC method
35
78
  # call.
36
79
  def connect!
37
- @connection = Connection::Factory.create(@options[:transport], @options[:transport_wrapper], @current_server, @options[:timeout])
80
+ @current_server = next_live_server
81
+ @connection = Connection::Factory.create(@options[:transport], @options[:transport_wrapper], @current_server.connection_string, @options[:timeout])
38
82
  @connection.connect!
39
83
  @client = @client_class.new(@options[:protocol].new(@connection.transport, *@options[:protocol_extra_params]))
40
- rescue Thrift::TransportException, Errno::ECONNREFUSED => e
41
- @transport.close rescue nil
42
- raise e
43
84
  end
44
85
 
45
86
  def disconnect!
46
- @connection.close rescue nil
87
+ @connection.close rescue nil #TODO
47
88
  @client = nil
48
89
  @current_server = nil
90
+ @request_count = 0
49
91
  end
50
92
 
51
93
  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 post_connect(method_name); end
59
-
60
- def proxy(method_name, *args)
61
- connect! unless @client
62
- post_connect(method_name)
63
- send_rpc(method_name, *args)
64
- end
65
-
66
- def send_rpc(method_name, *args)
67
- @client.send(method_name, *args)
68
- end
69
-
70
- def disconnect_on_error!
71
- @connection.close rescue nil
72
- @client = nil
73
- @current_server = nil
74
- end
75
-
76
- def handle_exception(e, method_name, args=nil)
77
- raise e if @options[:raise]
78
- @options[:defaults][method_name.to_sym]
79
- end
80
-
81
- module RetryingThriftClient
82
- DISCONNECT_ERRORS = [
83
- IOError,
84
- Thrift::Exception,
85
- Thrift::ProtocolException,
86
- Thrift::ApplicationException,
87
- Thrift::TransportException
88
- ].freeze
89
-
90
- RETRYING_DEFAULTS = {
91
- :exception_classes => DISCONNECT_ERRORS,
92
- :randomize_server_list => true,
93
- :retries => 0,
94
- :server_retry_period => 1,
95
- :server_max_requests => nil,
96
- :retry_overrides => {}
97
- }.freeze
98
-
99
- def initialize(client_class, servers, options = {})
100
- super
101
- @options = RETRYING_DEFAULTS.merge(@options) # @options is set by super
102
- @retries = @options[:retries]
103
- @request_count = 0
104
- @max_requests = @options[:server_max_requests]
105
- @retry_period = @options[:server_retry_period]
106
- rebuild_live_server_list!
107
- end
108
-
109
- def connect!
110
- @current_server = next_server
111
- super
112
- rescue Thrift::TransportException, Errno::ECONNREFUSED
113
- retry
114
- end
115
-
116
- def disconnect!
117
- # Keep live servers in the list if we have a retry period. Otherwise,
118
- # always eject, because we will always re-add them.
119
- if @retry_period && @current_server
120
- @live_server_list.unshift(@current_server)
121
- end
122
-
123
- super()
124
- @request_count = 0
125
- end
126
-
127
- private
128
94
 
129
- def next_server
130
- if @retry_period
131
- rebuild_live_server_list! if Time.now > @last_rebuild + @retry_period
132
- raise ThriftClient::NoServersAvailable, "No live servers in #{@server_list.inspect} since #{@last_rebuild.inspect}." if @live_server_list.empty?
133
- elsif @live_server_list.empty?
134
- rebuild_live_server_list!
95
+ def next_live_server
96
+ @server_index ||= 0
97
+ @server_list.length.times do |i|
98
+ cur = (1 + @server_index + i) % @server_list.length
99
+ if !@server_list[cur].marked_down_at || (@server_list[cur].marked_down_at + @options[:server_retry_period] <= Time.now)
100
+ @server_index = cur
101
+ return @server_list[cur]
135
102
  end
136
- @live_server_list.pop
137
103
  end
104
+ raise ThriftClient::NoServersAvailable, "No live servers in #{@server_list.inspect} since #{@last_rebuild.inspect}."
105
+ end
138
106
 
139
- def rebuild_live_server_list!
140
- @last_rebuild = Time.now
141
- if @options[:randomize_server_list]
142
- @live_server_list = @server_list.sort_by { rand }
143
- else
144
- @live_server_list = @server_list.dup
107
+ def handled_proxy(method_name, *args)
108
+ disconnect_on_max! if @options[:server_max_requests] && @request_count >= @options[:server_max_requests]
109
+ begin
110
+ connect! unless @client
111
+ if has_timeouts?
112
+ @client.timeout = @options[:timeout_overrides][method_name.to_sym] || @options[:timeout]
145
113
  end
146
- end
147
-
148
- def handled_proxy(method_name, *args)
149
- disconnect_on_max! if @max_requests and @request_count >= @max_requests
150
- super
151
- end
152
-
153
- def proxy(method_name, *args)
154
- super
114
+ @request_count += 1
115
+ @client.send(method_name, *args)
155
116
  rescue *@options[:exception_classes] => e
156
117
  disconnect_on_error!
157
- tries ||= (@options[:retry_overrides][method_name.to_sym] || @retries) + 1
118
+ tries ||= (@options[:retry_overrides][method_name.to_sym] || @options[:retries]) + 1
158
119
  tries -= 1
159
- tries > 0 ? retry : raise
160
- end
161
-
162
- def send_rpc(method_name, *args)
163
- @request_count += 1
164
- super
165
- end
166
-
167
- def disconnect_on_max!
168
- @live_server_list.push(@current_server)
169
- disconnect_on_error!
120
+ if tries > 0
121
+ retry
122
+ else
123
+ raise_or_default(e, method_name)
124
+ end
125
+ rescue Exception => e
126
+ raise_or_default(e, method_name)
170
127
  end
128
+ end
171
129
 
172
- def disconnect_on_error!
173
- super
174
- @request_count = 0
130
+ def raise_or_default(e, method_name)
131
+ if @options[:raise]
132
+ raise_wrapped_error(e)
133
+ else
134
+ @options[:defaults][method_name.to_sym]
175
135
  end
176
-
177
136
  end
178
137
 
179
- module TimingOutThriftClient
180
- TIMINGOUT_DEFAULTS = {
181
- :timeout => 1,
182
- :timeout_overrides => {}
183
- }.freeze
184
-
185
- def initialize(client_class, servers, options = {})
186
- super
187
- @options = TIMINGOUT_DEFAULTS.merge(@options)
138
+ def raise_wrapped_error(e)
139
+ if @options[:wrapped_exception_classes].include?(e.class)
140
+ raise @client_class.const_get(e.class.to_s.split('::').last), e.message, e.backtrace
141
+ else
142
+ raise e
188
143
  end
144
+ end
189
145
 
190
- private
191
- def post_connect(method_name)
192
- return unless has_timeouts?
193
- @client.timeout = @options[:timeout_overrides][method_name.to_sym] || @options[:timeout]
194
- end
146
+ def disconnect_on_max!
147
+ disconnect!
148
+ end
195
149
 
196
- def has_timeouts?
197
- @has_timeouts ||= has_timeouts!
198
- end
150
+ def disconnect_on_error!
151
+ @current_server.mark_down!
152
+ disconnect!
153
+ end
199
154
 
200
- def has_timeouts!
201
- @options[:timeout_overrides].any? && transport_can_timeout?
202
- end
155
+ def has_timeouts?
156
+ @has_timeouts ||= @options[:timeout_overrides].any? && transport_can_timeout?
157
+ end
203
158
 
204
- def transport_can_timeout?
205
- if (@options[:transport_wrapper] || @options[:transport]).method_defined?(:timeout=)
206
- true
207
- else
208
- warn "ThriftClient: Timeout overrides have no effect with with transport type #{(@options[:transport_wrapper] || @options[:transport])}"
209
- false
210
- end
159
+ def transport_can_timeout?
160
+ if (@options[:transport_wrapper] || @options[:transport]).method_defined?(:timeout=)
161
+ true
162
+ else
163
+ warn "ThriftClient: Timeout overrides have no effect with with transport type #{(@options[:transport_wrapper] || @options[:transport])}"
164
+ false
211
165
  end
212
166
  end
213
167
  end
@@ -27,8 +27,6 @@ module Thrift
27
27
  end
28
28
  Fiber.yield
29
29
 
30
- # Use Thrift::TransportException so the RetryingThriftClient knows to try the next
31
- # server instead of raising the error.
32
30
  raise Thrift::TransportException, "Unable to connect to #{@host}:#{@port}" unless @connection.connected?
33
31
  @connection
34
32
  end
data/lib/thrift_client.rb CHANGED
@@ -1,11 +1,6 @@
1
- if ENV["ANCIENT_THRIFT"]
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
1
+ require 'rubygems'
2
+ gem 'thrift', '~>0.2.0'
3
+ require 'thrift'
9
4
 
10
5
  require 'rubygems'
11
6
  require 'thrift_client/thrift'
@@ -13,11 +8,7 @@ require 'thrift_client/connection'
13
8
  require 'thrift_client/abstract_thrift_client'
14
9
 
15
10
  class ThriftClient < AbstractThriftClient
16
- # This error is for backwards compatibility only. If defined in
17
- # RetryingThriftClient instead, causes the test suite will break.
18
11
  class NoServersAvailable < StandardError; end
19
- include RetryingThriftClient
20
- include TimingOutThriftClient
21
12
 
22
13
  =begin rdoc
23
14
  Create a new ThriftClient instance. Accepts an internal Thrift client class (such as CassandraRb::Client), a list of servers with ports, and optional parameters.
@@ -28,11 +19,10 @@ Valid optional parameters are:
28
19
  <tt>:protocol_extra_params</tt>:: An array of additional parameters to pass to the protocol initialization call. Defaults to <tt>[]</tt>.
29
20
  <tt>:transport</tt>:: Which Thrift transport to use. Defaults to <tt>Thrift::Socket</tt>.
30
21
  <tt>:transport_wrapper</tt>:: Which Thrift transport wrapper to use. Defaults to <tt>Thrift::FramedTransport</tt>.
31
- <tt>:randomize_server_list</tt>:: Whether to connect to the servers randomly, instead of in order. Defaults to <tt>true</tt>.
32
- <tt>:exception_classes</tt>:: Which exceptions to catch and retry when sending a request. Defaults to <tt>[IOError, Thrift::Exception, Thrift::ProtocolException, Thrift::ApplicationException, Thrift::TransportException, NoServersAvailable]</tt>
22
+ <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>
33
23
  <tt>:raise</tt>:: Whether to reraise errors if no responsive servers are found. Defaults to <tt>true</tt>.
34
- <tt>:retries</tt>:: How many times to retry a request. Defaults to the number of servers defined.
35
- <tt>:server_retry_period</tt>:: How many seconds to wait before trying to reconnect after marking all servers as down. Defaults to <tt>1</tt>. Set to <tt>nil</tt> to retry endlessly.
24
+ <tt>:retries</tt>:: How many times to retry a request. Defaults to 0.
25
+ <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.
36
26
  <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).
37
27
  <tt>:timeout</tt>:: Specify the default timeout in seconds. Defaults to <tt>1</tt>.
38
28
  <tt>:timeout_overrides</tt>:: Specify additional timeouts on a per-method basis, in seconds. Only works with <tt>Thrift::BufferedTransport</tt>.
@@ -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
46
  end
@@ -32,33 +32,12 @@ class ThriftClientTest < Test::Unit::TestCase
32
32
  end
33
33
  end
34
34
 
35
- def test_non_random_fall_through
36
- assert_nothing_raised do
37
- ThriftClient.new(Greeter::Client, @servers, @options.merge(:randomize_server_list => false)).greeting("someone")
38
- end
39
- end
40
-
41
35
  def test_dont_raise
42
36
  assert_nothing_raised do
43
37
  ThriftClient.new(Greeter::Client, @servers.first, @options.merge(:raise => false)).greeting("someone")
44
38
  end
45
39
  end
46
40
 
47
- def test_handle_exception_is_called_once_when_retrying
48
- client = ThriftClient.new(Greeter::Client, @servers.first, @options.merge(:raise => false, :retries => 1))
49
- client.options[:exception_classes] += [ThriftClient::NoServersAvailable]
50
- singleton_class = (class << client; self end)
51
-
52
- times_called = 0
53
- singleton_class.send :define_method, :handle_exception do |*args|
54
- times_called += 1
55
- raise IOError
56
- end
57
-
58
- assert_raises(IOError) { client.greeting('someone') }
59
- assert_equal 1, times_called
60
- end
61
-
62
41
  def test_retries_correct_number_of_times
63
42
  stub_server(@port) do |socket|
64
43
  opts = @options.merge(:timeout => @timeout, :retries => 4, :server_retry_period => nil)
@@ -73,7 +52,7 @@ class ThriftClientTest < Test::Unit::TestCase
73
52
  times_called += 1; super
74
53
  end
75
54
 
76
- assert_raises(Thrift::TransportException) { client.greeting("someone") }
55
+ assert_raises(Greeter::Client::TransportException) { client.greeting("someone") }
77
56
  assert_equal opts[:retries] + 1, times_called
78
57
  end
79
58
  end
@@ -91,7 +70,7 @@ class ThriftClientTest < Test::Unit::TestCase
91
70
  def test_random_fall_through
92
71
  assert_nothing_raised do
93
72
  10.times do
94
- client = ThriftClient.new(Greeter::Client, @servers, @options)
73
+ client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:retries => 2))
95
74
  client.greeting("someone")
96
75
  client.disconnect!
97
76
  end
@@ -105,7 +84,7 @@ class ThriftClientTest < Test::Unit::TestCase
105
84
  end
106
85
 
107
86
  def test_no_servers_eventually_raise
108
- client = ThriftClient.new(Greeter::Client, @servers[0,2], @options)
87
+ client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:retries => 2))
109
88
  assert_raises(ThriftClient::NoServersAvailable) do
110
89
  client.greeting("someone")
111
90
  client.disconnect!
@@ -115,7 +94,7 @@ class ThriftClientTest < Test::Unit::TestCase
115
94
  def test_framed_transport_timeout
116
95
  stub_server(@port) do |socket|
117
96
  measurement = Benchmark.measure do
118
- assert_raises(Thrift::TransportException) do
97
+ assert_raises(Greeter::Client::TransportException) do
119
98
  ThriftClient.new(Greeter::Client, "127.0.0.1:#{@port}",
120
99
  @options.merge(:timeout => @timeout)
121
100
  ).greeting("someone")
@@ -128,10 +107,11 @@ class ThriftClientTest < Test::Unit::TestCase
128
107
  def test_buffered_transport_timeout
129
108
  stub_server(@port) do |socket|
130
109
  measurement = Benchmark.measure do
131
- assert_raises(Thrift::TransportException) do
132
- ThriftClient.new(Greeter::Client, "127.0.0.1:#{@port}",
133
- @options.merge(:timeout => @timeout, :transport_wrapper => Thrift::BufferedTransport)
134
- ).greeting("someone")
110
+ client = ThriftClient.new(Greeter::Client, "127.0.0.1:#{@port}",
111
+ @options.merge(:timeout => @timeout, :transport_wrapper => Thrift::BufferedTransport)
112
+ )
113
+ assert_raises(Greeter::Client::TransportException) do
114
+ client.greeting("someone")
135
115
  end
136
116
  end
137
117
  assert((measurement.real > @timeout), "#{measurement.real} < #{@timeout}")
@@ -143,10 +123,11 @@ class ThriftClientTest < Test::Unit::TestCase
143
123
  log_timeout = @timeout * 4
144
124
  stub_server(@port) do |socket|
145
125
  measurement = Benchmark.measure do
146
- assert_raises(Thrift::TransportException) do
147
- ThriftClient.new(Greeter::Client, "127.0.0.1:#{@port}",
148
- @options.merge(:timeout => @timeout, :timeout_overrides => {:greeting => log_timeout}, :transport_wrapper => Thrift::BufferedTransport)
149
- ).greeting("someone")
126
+ client = ThriftClient.new(Greeter::Client, "127.0.0.1:#{@port}",
127
+ @options.merge(:timeout => @timeout, :timeout_overrides => {:greeting => log_timeout}, :transport_wrapper => Thrift::BufferedTransport)
128
+ )
129
+ assert_raises(Greeter::Client::TransportException) do
130
+ client.greeting("someone")
150
131
  end
151
132
  end
152
133
  assert((measurement.real > log_timeout), "#{measurement.real} < #{log_timeout}")
@@ -154,28 +135,28 @@ class ThriftClientTest < Test::Unit::TestCase
154
135
  end
155
136
 
156
137
  def test_retry_period
157
- client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:server_retry_period => 1))
138
+ client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:server_retry_period => 1, :retries => 2))
158
139
  assert_raises(ThriftClient::NoServersAvailable) { client.greeting("someone") }
159
140
  sleep 1.1
160
141
  assert_raises(ThriftClient::NoServersAvailable) { client.greeting("someone") }
161
142
  end
162
143
 
163
144
  def test_client_with_retry_period_drops_servers
164
- client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:server_retry_period => 1))
145
+ client = ThriftClient.new(Greeter::Client, @servers[0,2], @options.merge(:server_retry_period => 1, :retries => 2))
165
146
  assert_raises(ThriftClient::NoServersAvailable) { client.greeting("someone") }
166
147
  sleep 1.1
167
148
  assert_raises(ThriftClient::NoServersAvailable) { client.greeting("someone") }
168
149
  end
169
150
 
170
151
  def test_oneway_method
171
- client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:server_max_requests => 2))
152
+ client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:server_max_requests => 2, :retries => 2))
172
153
  assert_nothing_raised do
173
154
  response = client.yo("dude")
174
155
  end
175
156
  end
176
157
 
177
158
  def test_server_max_requests_with_downed_servers
178
- client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:server_max_requests => 2))
159
+ client = ThriftClient.new(Greeter::Client, @servers, @options.merge(:server_max_requests => 2, :retries => 2))
179
160
  client.greeting("someone")
180
161
  internal_client = client.client
181
162
  client.greeting("someone")
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{thrift_client}
5
- s.version = "0.4.7"
5
+ s.version = "0.5.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0.8") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Evan Weaver, Ryan King, Jeff Hodges"]
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thrift_client
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1
4
+ hash: 11
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 4
9
- - 7
10
- version: 0.4.7
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Evan Weaver, Ryan King, Jeff Hodges