thrift_client-mavericks 0.8.4

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