thrift_client-mavericks 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDRjMjcxZmEyY2ZmYmMyYWNiYTFlZTI3NjcxMDg4MGE4NzUxM2FlNg==
5
+ data.tar.gz: !binary |-
6
+ ZmM3ZTQ2ZjQ3OGI5ZDkwNDFlODkxZTZlNzJkOWM2YmQyNTQ0YWFiZQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MzA4OTk3ODdiYWM3MzFlNWQwYTMxYzE2NjEyOTk1OWIwMTc2NGEzN2U4MzBh
10
+ YTlmNDJhOTY2YWU3NmQ1NTQ3Y2U3ZWU0ZDA0ZTE3NWRjNGZlMGRiNTU0YmVj
11
+ Y2MyN2UwYTFhZGVkZTdlZTg0Njg1NTMyMzI5OTVjMDM5ZjI4ZmM=
12
+ data.tar.gz: !binary |-
13
+ YTEyNzk2OTk5NmRjZTRhMWRiNzdkZmQ4NzQ3ZTUzNDgwNjNiMDM2MTBkN2Nl
14
+ ZGFjNjA0ZWE0ODRiNjY0NzMzYWJmMzY4YzgzZDM3NWI3ZTIzODZkMzU0ZDk2
15
+ NGNlMGY1ZmJlN2M2YTJmZmVlZmIxYWI2YTBhMmVjM2UwM2FiMGU=
@@ -0,0 +1,34 @@
1
+ require 'thrift'
2
+ require 'thrift_client/thrift'
3
+ require 'thrift_client/server'
4
+ require 'thrift_client/abstract_thrift_client'
5
+
6
+ class ThriftClient < AbstractThriftClient
7
+ class NoServersAvailable < StandardError; end
8
+
9
+ =begin rdoc
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.
11
+
12
+ Valid optional parameters are:
13
+
14
+ <tt>:protocol</tt>:: Which Thrift protocol to use. Defaults to <tt>Thrift::BinaryProtocol</tt>.
15
+ <tt>:protocol_extra_params</tt>:: An array of additional parameters to pass to the protocol initialization call. Defaults to <tt>[]</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.
20
+ <tt>:raise</tt>:: Whether to reraise errors if no responsive servers are found. Defaults to <tt>true</tt>.
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.
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).
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>.
26
+ <tt>:timeout_overrides</tt>:: Specify additional timeouts on a per-method basis, in seconds. Only works with <tt>Thrift::BufferedTransport</tt>.
27
+ <tt>:cached_connections</tt>:: Cache connections between requests. Trades connect() costs for open sockets. Defaults to <tt>false</tt>.
28
+ <tt>:defaults</tt>:: Specify default values to return on a per-method basis, if <tt>:raise</tt> is set to false.
29
+ =end rdoc
30
+
31
+ def initialize(client_class, servers, options = {})
32
+ super
33
+ end
34
+ end
@@ -0,0 +1,221 @@
1
+ class AbstractThriftClient
2
+ include ThriftHelpers
3
+
4
+ DISCONNECT_ERRORS = [
5
+ IOError,
6
+ Thrift::Exception,
7
+ Thrift::ApplicationException,
8
+ Thrift::TransportException
9
+ ]
10
+
11
+ DEFAULT_WRAPPED_ERRORS = [
12
+ Thrift::ApplicationException,
13
+ Thrift::TransportException,
14
+ ]
15
+
16
+ DEFAULTS = {
17
+ :protocol => Thrift::BinaryProtocol,
18
+ :protocol_extra_params => [],
19
+ :transport => Thrift::Socket,
20
+ :transport_wrapper => Thrift::FramedTransport,
21
+ :raise => true,
22
+ :defaults => {},
23
+ :exception_classes => DISCONNECT_ERRORS,
24
+ :exception_class_overrides => [],
25
+ :retries => 0,
26
+ :server_retry_period => 1,
27
+ :server_max_requests => nil,
28
+ :retry_overrides => {},
29
+ :wrapped_exception_classes => DEFAULT_WRAPPED_ERRORS,
30
+ :connect_timeout => 0.1,
31
+ :timeout => 1,
32
+ :timeout_overrides => {},
33
+ :cached_connections => false
34
+ }
35
+
36
+ attr_reader :last_client, :client, :client_class, :current_server, :server_list, :options, :client_methods
37
+
38
+ def initialize(client_class, servers, options = {})
39
+ @options = DEFAULTS.merge(options)
40
+ @options[:server_retry_period] ||= 0
41
+
42
+ @client_class = client_class
43
+ @server_list = Array(servers).collect do |s|
44
+ Server.new(s, @client_class, @options)
45
+ end.sort_by { rand }
46
+
47
+ @current_server = @server_list.first
48
+
49
+ @callbacks = {}
50
+ @client_methods = []
51
+ @client_class.instance_methods.each do |method_name|
52
+ if method_name != 'send_message' && method_name =~ /^send_(.*)$/
53
+ instance_eval("def #{$1}(*args); handled_proxy(:'#{$1}', *args); end", __FILE__, __LINE__)
54
+ @client_methods << $1
55
+ end
56
+ end
57
+ @request_count = 0
58
+ @options[:wrapped_exception_classes].each do |exception_klass|
59
+ name = exception_klass.to_s.split('::').last
60
+ begin
61
+ @client_class.const_get(name)
62
+ rescue NameError
63
+ @client_class.const_set(name, Class.new(exception_klass))
64
+ end
65
+ end
66
+ end
67
+
68
+ # Adds a callback that will be invoked at a certain time. The valid callback types are:
69
+ # :post_connect - should accept a single AbstractThriftClient argument, which is the client object to
70
+ # which the callback was added. Called after a connection to the remote thrift server
71
+ # is established.
72
+ # :before_method - should accept a single method name argument. Called before a method is invoked on the
73
+ # thrift server.
74
+ # :on_exception - should accept 2 args: an Exception instance and a method name. Called right before the
75
+ # exception is raised.
76
+ def add_callback(callback_type, &block)
77
+ case callback_type
78
+ when :post_connect, :before_method, :on_exception
79
+ @callbacks[callback_type] ||= []
80
+ @callbacks[callback_type].push(block)
81
+ # Allow chaining
82
+ return self
83
+ else
84
+ return nil
85
+ end
86
+ end
87
+
88
+ def inspect
89
+ "<#{self.class}(#{client_class}) @current_server=#{@current_server}>"
90
+ end
91
+
92
+ # Force the client to connect to the server. Not necessary to be
93
+ # called as the connection will be made on the first RPC method
94
+ # call.
95
+ def connect!(method = nil)
96
+ start_time ||= Time.now
97
+ @current_server = next_live_server
98
+ @client = @current_server.client
99
+ @last_client = @client
100
+ do_callbacks(:post_connect, self)
101
+ rescue IOError, Thrift::TransportException
102
+ disconnect!(true)
103
+ timeout = timeout(method)
104
+ if timeout && Time.now - start_time > timeout
105
+ no_servers_available!
106
+ else
107
+ retry
108
+ end
109
+ end
110
+
111
+ def disconnect!(error = false)
112
+ if @current_server
113
+ @current_server.mark_down!(@options[:server_retry_period]) if error
114
+ @current_server.close
115
+ end
116
+
117
+ @client = nil
118
+ @current_server = nil
119
+ @request_count = 0
120
+ end
121
+
122
+ private
123
+
124
+ # Calls all callbacks of the specified type with the given args
125
+ def do_callbacks(callback_type_sym, *args)
126
+ return unless @callbacks[callback_type_sym]
127
+ @callbacks[callback_type_sym].each do |callback|
128
+ callback.call(*args)
129
+ end
130
+ end
131
+
132
+ def next_live_server
133
+ @server_index ||= 0
134
+ @server_list.length.times do |i|
135
+ cur = (1 + @server_index + i) % @server_list.length
136
+ if @server_list[cur].up?
137
+ @server_index = cur
138
+ return @server_list[cur]
139
+ end
140
+ end
141
+ no_servers_available!
142
+ end
143
+
144
+ def ensure_socket_alignment
145
+ incomplete = true
146
+ result = yield
147
+ incomplete = false
148
+ result
149
+ # Thrift exceptions get read off the wire. We can consider them complete requests
150
+ rescue Thrift::Exception => e
151
+ incomplete = false
152
+ raise e
153
+ ensure
154
+ disconnect! if incomplete
155
+ end
156
+
157
+ def handled_proxy(method_name, *args)
158
+ begin
159
+ connect!(method_name.to_sym) unless @client
160
+ if has_timeouts?
161
+ @client.timeout = timeout(method_name.to_sym)
162
+ end
163
+ @request_count += 1
164
+ do_callbacks(:before_method, method_name)
165
+ ensure_socket_alignment { @client.send(method_name, *args) }
166
+ rescue *@options[:exception_class_overrides] => e
167
+ raise_or_default(e, method_name)
168
+ rescue *@options[:exception_classes] => e
169
+ disconnect!(true)
170
+ tries ||= (@options[:retry_overrides][method_name.to_sym] || @options[:retries]) + 1
171
+ tries -= 1
172
+ if tries > 0
173
+ retry
174
+ else
175
+ raise_or_default(e, method_name)
176
+ end
177
+ rescue Exception => e
178
+ raise_or_default(e, method_name)
179
+ ensure
180
+ disconnect! if @options[:server_max_requests] && @request_count >= @options[:server_max_requests]
181
+ end
182
+ end
183
+
184
+ def raise_or_default(e, method_name)
185
+ if @options[:raise]
186
+ raise_wrapped_error(e, method_name)
187
+ else
188
+ @options[:defaults][method_name.to_sym]
189
+ end
190
+ end
191
+
192
+ def raise_wrapped_error(e, method_name)
193
+ do_callbacks(:on_exception, e, method_name)
194
+ if @options[:wrapped_exception_classes].include?(e.class)
195
+ raise @client_class.const_get(e.class.to_s.split('::').last), e.message, e.backtrace
196
+ else
197
+ raise e
198
+ end
199
+ end
200
+
201
+ def has_timeouts?
202
+ @has_timeouts ||= @options[:timeout_overrides].any? && transport_can_timeout?
203
+ end
204
+
205
+ def timeout(method = nil)
206
+ @options[:timeout_overrides][method] || @options[:timeout]
207
+ end
208
+
209
+ def transport_can_timeout?
210
+ if (@options[:transport_wrapper] || @options[:transport]).method_defined?(:timeout=)
211
+ true
212
+ else
213
+ warn "ThriftClient: Timeout overrides have no effect with with transport type #{(@options[:transport_wrapper] || @options[:transport])}"
214
+ false
215
+ end
216
+ end
217
+
218
+ def no_servers_available!
219
+ raise ThriftClient::NoServersAvailable, "No live servers in #{@server_list.inspect}."
220
+ end
221
+ end
@@ -0,0 +1,4 @@
1
+ require "thrift_client/connection/base"
2
+ require "thrift_client/connection/socket"
3
+ require "thrift_client/connection/http"
4
+ require "thrift_client/connection/factory"
@@ -0,0 +1,25 @@
1
+ module ThriftHelpers
2
+ module Connection
3
+ class Base
4
+ attr_accessor :transport, :server
5
+
6
+ def initialize(transport, transport_wrapper, server, timeout)
7
+ @transport = transport
8
+ @transport_wrapper = transport_wrapper
9
+ @server = server
10
+ @timeout = timeout
11
+ end
12
+
13
+ def connect!
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def open?
18
+ @transport.open?
19
+ end
20
+
21
+ def close
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ module ThriftHelpers
2
+ module Connection
3
+ class Factory
4
+ def self.create(transport, transport_wrapper, server, timeout)
5
+ if transport == Thrift::HTTPClientTransport
6
+ Connection::HTTP.new(transport, transport_wrapper, server, timeout)
7
+ else
8
+ Connection::Socket.new(transport, transport_wrapper, server, timeout)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module ThriftHelpers
2
+ module Connection
3
+ class HTTP < Base
4
+ def initialize(*args)
5
+ super *args
6
+
7
+ uri = parse_server(@server)
8
+ @transport = Thrift::HTTPClientTransport.new(@server)
9
+ end
10
+
11
+ def connect!
12
+ http = Net::HTTP.new(uri.host, uri.port)
13
+ http.use_ssl = uri.scheme == "https"
14
+ http.get(uri.path)
15
+ end
16
+
17
+ def open?
18
+ true
19
+ end
20
+
21
+ private
22
+ def parse_server(server)
23
+ uri = URI.parse(server)
24
+ raise ArgumentError, 'Servers must start with http' unless uri.scheme =~ /^http/
25
+ uri
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module ThriftHelpers
2
+ module Connection
3
+ class Socket < Base
4
+ def initialize(*args)
5
+ super *args
6
+
7
+ host, port = parse_server(@server)
8
+ @transport = @transport.new(host, port.to_i, @timeout)
9
+ @transport = @transport_wrapper.new(@transport) if @transport_wrapper
10
+ end
11
+
12
+ def close
13
+ @transport.close
14
+ end
15
+
16
+ def connect!
17
+ @transport.open
18
+ end
19
+
20
+ private
21
+
22
+ def parse_server(server)
23
+ host, port = server.to_s.split(":")
24
+ raise ArgumentError, 'Servers must be in the form "host:port"' unless host and port
25
+ [host, port]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,145 @@
1
+ raise RuntimeError, "The eventmachine transport requires Ruby 1.9.x" if RUBY_VERSION < '1.9.0'
2
+
3
+ require 'eventmachine'
4
+ require 'fiber'
5
+
6
+ # EventMachine-ready Thrift connection
7
+ # Should not be used with a transport wrapper since it already performs buffering in Ruby.
8
+ module Thrift
9
+ class EventMachineTransport < BaseTransport
10
+ def initialize(host, port=9090, timeout=5)
11
+ @host, @port, @timeout = host, port, timeout
12
+ @connection = nil
13
+ end
14
+
15
+ def open?
16
+ @connection && @connection.connected?
17
+ end
18
+
19
+ def open
20
+ fiber = Fiber.current
21
+ @connection = EventMachineConnection.connect(@host, @port, @timeout)
22
+ @connection.callback do
23
+ fiber.resume
24
+ end
25
+ @connection.errback do
26
+ fiber.resume
27
+ end
28
+ Fiber.yield
29
+
30
+ raise Thrift::TransportException, "Unable to connect to #{@host}:#{@port}" unless @connection.connected?
31
+ @connection
32
+ end
33
+
34
+ def close
35
+ @connection.close
36
+ end
37
+
38
+ def read(sz)
39
+ @connection.blocking_read(sz)
40
+ end
41
+
42
+ def write(buf)
43
+ @connection.send_data(buf)
44
+ end
45
+ end
46
+
47
+ module EventMachineConnection
48
+ GARBAGE_BUFFER_SIZE = 4096 # 4kB
49
+
50
+ include EM::Deferrable
51
+
52
+ def self.connect(host='localhost', port=9090, timeout=5, &block)
53
+ EM.connect(host, port, self, host, port) do |conn|
54
+ conn.pending_connect_timeout = timeout
55
+ end
56
+ end
57
+
58
+ def trap
59
+ begin
60
+ yield
61
+ rescue Exception => ex
62
+ puts ex.message
63
+ puts ex.backtrace.join("\n")
64
+ end
65
+ end
66
+
67
+ def initialize(host, port=9090)
68
+ @host, @port = host, port
69
+ @index = 0
70
+ @disconnected = 'not connected'
71
+ @buf = ''
72
+ end
73
+
74
+ def close
75
+ trap do
76
+ @disconnected = 'closed'
77
+ close_connection(true)
78
+ end
79
+ end
80
+
81
+ def blocking_read(size)
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
+ end
95
+ end
96
+
97
+ def receive_data(data)
98
+ trap do
99
+ (@buf) << data
100
+
101
+ if @callback and can_read?(@size)
102
+ callback = @callback
103
+ data = yank(@size)
104
+ @callback = @size = nil
105
+ callback.call(data)
106
+ end
107
+ end
108
+ end
109
+
110
+ def connected?
111
+ !@disconnected
112
+ end
113
+
114
+ def connection_completed
115
+ @disconnected = nil
116
+ succeed
117
+ end
118
+
119
+ def unbind
120
+ if !@disconnected
121
+ @disconnected = 'unbound'
122
+ else
123
+ fail
124
+ end
125
+ end
126
+
127
+ def can_read?(size)
128
+ @buf.size >= @index + size
129
+ end
130
+
131
+ private
132
+
133
+ def yank(len)
134
+ data = @buf.slice(@index, len)
135
+ @index += len
136
+ @index = @buf.size if @index > @buf.size
137
+ if @index >= GARBAGE_BUFFER_SIZE
138
+ @buf = @buf.slice(@index..-1)
139
+ @index = 0
140
+ end
141
+ data
142
+ end
143
+
144
+ end
145
+ end