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 CHANGED
@@ -1,9 +1,43 @@
1
- v0.4.2 Allow per-method overrides of retries. Fix several bugs with EventMachine.
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.1 Making ThriftClient decoratable. Able to add new functionality a class definition.
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
@@ -21,3 +21,4 @@ test/simple_test.rb
21
21
  test/test_helper.rb
22
22
  test/thrift_client_http_test.rb
23
23
  test/thrift_client_test.rb
24
+ thrift_client.gemspec
@@ -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
@@ -1,23 +1,10 @@
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
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::FramedTransport</tt>.
30
- <tt>:randomize_server_list</tt>:: Whether to connect to the servers randomly, instead of in order. Defaults to <tt>true</tt>.
31
- <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>
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 the number of servers defined.
34
- <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.
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
- }.freeze
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 =~ /^recv_(.*)$/
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
- @connection = Connection::Factory.create(@options[:transport], @options[:transport_wrapper], @current_server, @options[:timeout])
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
- @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
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
- def send_rpc(method_name, *args)
64
- @client.send(method_name, *args)
65
- end
66
-
67
- def disconnect_on_error!
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
- def connect!
107
- @current_server = next_server
108
- super
109
- rescue Thrift::TransportException, Errno::ECONNREFUSED
110
- retry
111
- end
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
- def rebuild_live_server_list!
137
- @last_rebuild = Time.now
138
- if @options[:randomize_server_list]
139
- @live_server_list = @server_list.sort_by { rand }
140
- else
141
- @live_server_list = @server_list.dup
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
- end
144
-
145
- def handled_proxy(method_name, *args)
146
- disconnect_on_max! if @max_requests and @request_count >= @max_requests
147
- super
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 == 0 ? handle_exception(e, method_name, args) : retry
157
- end
158
-
159
- def send_rpc(method_name, *args)
160
- @request_count += 1
161
- super
162
- end
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
- module TimingOutThriftClient
177
- TIMINGOUT_DEFAULTS = {
178
- :timeout => 1,
179
- :timeout_overrides => {}
180
- }.freeze
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
- def connect!
188
- super
189
- set_method_timeouts!
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
- private
193
- def set_method_timeouts!
194
- return unless has_timeouts?
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
- def has_timeouts?
201
- @has_timeouts ||= has_timeouts!
202
- end
188
+ def disconnect_on_error!
189
+ @current_server.mark_down! if @current_server
190
+ disconnect!
191
+ end
203
192
 
204
- def has_timeouts!
205
- @options[:timeout_overrides].any? && transport_can_timeout?
206
- end
193
+ def has_timeouts?
194
+ @has_timeouts ||= @options[:timeout_overrides].any? && transport_can_timeout?
195
+ end
207
196
 
208
- def transport_can_timeout?
209
- if (@options[:transport_wrapper] || @options[:transport]).method_defined?(:timeout=)
210
- true
211
- else
212
- warn "ThriftClient: Timeout overrides have no effect with with transport type #{(@options[:transport_wrapper] || @options[:transport])}"
213
- false
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
- $LOAD_PATH.unshift(File.dirname(__FILE__) + "/connection")
2
- require "base"
3
- require "socket"
4
- require "http"
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
- @connected = false
70
+ @disconnected = 'not connected'
69
71
  @buf = ''
70
72
  end
71
73
 
72
74
  def close
73
75
  trap do
74
- @connected = false
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}" unless @connected
81
- trap do
82
- if can_read?(size)
83
- yank(size)
84
- else
85
- raise ArgumentError, "Unexpected state" if @size or @callback
86
-
87
- fiber = Fiber.current
88
- @size = size
89
- @callback = proc { |data|
90
- fiber.resume(data)
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
- @connected
111
+ !@disconnected
112
112
  end
113
113
 
114
114
  def connection_completed
115
- @connected = true
115
+ @disconnected = nil
116
116
  succeed
117
117
  end
118
118
 
119
119
  def unbind
120
- if @connected
121
- @connected = false
120
+ if !@disconnected
121
+ @disconnected = 'unbound'
122
122
  else
123
123
  fail
124
124
  end
@@ -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
 
@@ -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
@@ -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
@@ -1,3 +1,4 @@
1
1
  service Greeter {
2
2
  string greeting(1:string name)
3
+ oneway void yo(1:string name)
3
4
  }
@@ -3,6 +3,10 @@ module Greeter
3
3
  def greeting(name)
4
4
  "hello there #{name}!"
5
5
  end
6
+
7
+ def yo(name)
8
+ #whee
9
+ end
6
10
  end
7
11
 
8
12
  class Server
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/test_helper"
1
+ require File.expand_path('test_helper.rb', File.dirname(__FILE__))
2
2
 
3
3
  class MultipleWorkingServersTest < Test::Unit::TestCase
4
4
  def setup
@@ -1,5 +1,4 @@
1
-
2
- require "#{File.dirname(__FILE__)}/test_helper"
1
+ require File.expand_path('test_helper.rb', File.dirname(__FILE__))
3
2
 
4
3
  class SimpleTest < Test::Unit::TestCase
5
4
 
@@ -1,4 +1,4 @@
1
-
1
+ require 'rubygems'
2
2
  require 'test/unit'
3
3
  require 'benchmark'
4
4
  $LOAD_PATH << "#{File.expand_path(File.dirname(__FILE__))}/../lib"
@@ -1,4 +1,4 @@
1
- require "#{File.dirname(__FILE__)}/test_helper"
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
@@ -1,10 +1,10 @@
1
- require "#{File.dirname(__FILE__)}/test_helper"
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
- @socket = 1461
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 test_non_random_fall_through
35
+ def test_dont_raise
36
36
  assert_nothing_raised do
37
- ThriftClient.new(Greeter::Client, @servers, @options.merge(:randomize_server_list => false)).greeting("someone")
37
+ ThriftClient.new(Greeter::Client, @servers.first, @options.merge(:raise => false)).greeting("someone")
38
38
  end
39
39
  end
40
40
 
41
- def test_dont_raise
42
- assert_nothing_raised do
43
- ThriftClient.new(Greeter::Client, @servers.first, @options.merge(:raise => false)).greeting("someone")
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
- client = ThriftClient.new(Greeter::Client, @servers[0,2], @options)
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(@socket) do |socket|
184
+ stub_server(@port) do |socket|
83
185
  measurement = Benchmark.measure do
84
- assert_raises(Thrift::TransportException) do
85
- ThriftClient.new(Greeter::Client, "127.0.0.1:#{@socket}",
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(@socket) do |socket|
197
+ stub_server(@port) do |socket|
96
198
  measurement = Benchmark.measure do
97
- assert_raises(Thrift::TransportException) do
98
- ThriftClient.new(Greeter::Client, "127.0.0.1:#{@socket}",
99
- @options.merge(:timeout => @timeout, :transport_wrapper => Thrift::BufferedTransport)
100
- ).greeting("someone")
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(@socket) do |socket|
213
+ stub_server(@port) do |socket|
111
214
  measurement = Benchmark.measure do
112
- assert_raises(Thrift::TransportException) do
113
- ThriftClient.new(Greeter::Client, "127.0.0.1:#{@socket}",
114
- @options.merge(:timeout => @timeout, :timeout_overrides => {:greeting => log_timeout}, :transport_wrapper => Thrift::BufferedTransport)
115
- ).greeting("someone")
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")
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{fl-thrift_client}
5
- s.version = "0.4.2"
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"]
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 4
9
- - 2
10
- version: 0.4.2
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Evan Weaver