thrift_client 0.4.7 → 0.5.0

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.
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