protobuf 1.0.1 → 1.1.0.beta0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +3 -0
  2. data/.yardopts +5 -0
  3. data/Gemfile.lock +25 -10
  4. data/bin/rpc_server +38 -33
  5. data/lib/protobuf.rb +22 -3
  6. data/lib/protobuf/common/logger.rb +6 -8
  7. data/lib/protobuf/compiler/visitors.rb +8 -9
  8. data/lib/protobuf/descriptor/descriptor_builder.rb +6 -6
  9. data/lib/protobuf/ext/eventmachine.rb +2 -4
  10. data/lib/protobuf/message/message.rb +1 -3
  11. data/lib/protobuf/rpc/buffer.rb +6 -6
  12. data/lib/protobuf/rpc/client.rb +59 -21
  13. data/lib/protobuf/rpc/connector.rb +10 -9
  14. data/lib/protobuf/rpc/connectors/base.rb +23 -8
  15. data/lib/protobuf/rpc/connectors/common.rb +155 -0
  16. data/lib/protobuf/rpc/connectors/em_client.rb +23 -192
  17. data/lib/protobuf/rpc/connectors/eventmachine.rb +36 -44
  18. data/lib/protobuf/rpc/connectors/socket.rb +58 -1
  19. data/lib/protobuf/rpc/error.rb +6 -14
  20. data/lib/protobuf/rpc/server.rb +72 -99
  21. data/lib/protobuf/rpc/servers/evented_runner.rb +32 -0
  22. data/lib/protobuf/rpc/servers/evented_server.rb +29 -0
  23. data/lib/protobuf/rpc/servers/socket_runner.rb +17 -0
  24. data/lib/protobuf/rpc/servers/socket_server.rb +145 -0
  25. data/lib/protobuf/rpc/service.rb +50 -51
  26. data/lib/protobuf/rpc/stat.rb +2 -2
  27. data/lib/protobuf/version.rb +1 -1
  28. data/protobuf.gemspec +9 -4
  29. data/spec/helper/all.rb +1 -7
  30. data/spec/helper/server.rb +45 -5
  31. data/spec/helper/silent_constants.rb +40 -0
  32. data/spec/proto/test_service.rb +0 -1
  33. data/spec/proto/test_service_impl.rb +4 -3
  34. data/spec/spec_helper.rb +19 -6
  35. data/spec/unit/enum_spec.rb +4 -4
  36. data/spec/unit/rpc/client_spec.rb +32 -42
  37. data/spec/unit/rpc/connector_spec.rb +11 -16
  38. data/spec/unit/rpc/connectors/base_spec.rb +14 -3
  39. data/spec/unit/rpc/connectors/common_spec.rb +132 -0
  40. data/spec/unit/rpc/connectors/{eventmachine/client_spec.rb → eventmachine_client_spec.rb} +0 -0
  41. data/spec/unit/rpc/connectors/socket_spec.rb +49 -0
  42. data/spec/unit/rpc/servers/evented_server_spec.rb +18 -0
  43. data/spec/unit/rpc/servers/socket_server_spec.rb +57 -0
  44. metadata +86 -16
  45. data/spec/unit/rpc/server_spec.rb +0 -27
@@ -0,0 +1,29 @@
1
+ require 'protobuf/rpc/server'
2
+
3
+ module Protobuf
4
+ module Rpc
5
+ class EventedServer < EventMachine::Connection
6
+ include Protobuf::Rpc::Server
7
+ include Protobuf::Logger::LogMethods
8
+
9
+ # Initialize a new read buffer for storing client request info
10
+ def post_init
11
+ log_debug '[server] Post init, new read buffer created'
12
+
13
+ @stats = Protobuf::Rpc::Stat.new(:SERVER, true)
14
+ @stats.client = Socket.unpack_sockaddr_in(get_peername)
15
+
16
+ @buffer = Protobuf::Rpc::Buffer.new(:read)
17
+ @did_respond = false
18
+ end
19
+
20
+ # Receive a chunk of data, potentially flushed to handle_client
21
+ def receive_data(data)
22
+ log_debug '[server] receive_data: %s' % data
23
+ @buffer << data
24
+ handle_client if @buffer.flushed?
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module Protobuf
2
+ module Rpc
3
+ class SocketRunner
4
+
5
+ def self.stop
6
+ Protobuf::Rpc::SocketServer.stop
7
+ Protobuf::Logger.info 'Shutdown complete'
8
+ end
9
+
10
+ def self.run(server)
11
+ Protobuf::Logger.info "SocketServer Running"
12
+ Protobuf::Rpc::SocketServer.run(server.host, server.port, server.backlog, server.threshold) if !Protobuf::Rpc::SocketServer.running?
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,145 @@
1
+ require 'protobuf/rpc/server'
2
+
3
+ module Protobuf
4
+ module Rpc
5
+ class SocketServer
6
+ include Protobuf::Rpc::Server
7
+ include Protobuf::Logger::LogMethods
8
+
9
+ class << self
10
+
11
+ def cleanup?
12
+ # every 10 connections run a cleanup routine after closing the response
13
+ @threads.size > (@thread_threshold - 1) && (@threads.size % @thread_threshold) == 0
14
+ end
15
+
16
+ def cleanup_threads
17
+ log_debug "[#{log_signature}] Thread cleanup - #{@threads.size} - start"
18
+
19
+ @threads = @threads.select do |t|
20
+ if t[:thread].alive?
21
+ true
22
+ else
23
+ t[:thread].join
24
+ @working.delete(t[:socket])
25
+ false
26
+ end
27
+ end
28
+
29
+ log_debug "[#{log_signature}] Thread cleanup - #{@threads.size} - complete"
30
+ end
31
+
32
+ def log_signature
33
+ @log_signature ||= "server-#{self}"
34
+ end
35
+
36
+ def new_worker(socket)
37
+ Thread.new(socket) do |sock|
38
+ Protobuf::Rpc::SocketServer::Worker.new(sock) do |s|
39
+ s.close
40
+ end
41
+ end
42
+ end
43
+
44
+ # TODO make listen/backlog part of config
45
+ def run(host = "127.0.0.1", port = 9399, backlog = 100, thread_threshold = 100)
46
+ log_debug "[#{log_signature}] Run"
47
+ @running = true
48
+ @threads = []
49
+ @thread_threshold = thread_threshold
50
+ @server = TCPServer.new(host, port)
51
+ @server.listen(backlog)
52
+ @working = []
53
+ @listen_fds = [@server]
54
+
55
+ while running?
56
+ log_debug "[#{log_signature}] Waiting for connections"
57
+
58
+ if ready_cnxns = IO.select(@listen_fds, [], [], 20)
59
+ cnxns = ready_cnxns.first
60
+ cnxns.each do |client|
61
+ case
62
+ when !running? then
63
+ # no-op
64
+ when client == @server then
65
+ log_debug "[#{log_signature}] Accepted new connection"
66
+ client, sockaddr = @server.accept
67
+ @listen_fds << client
68
+ else
69
+ if !@working.include?(client)
70
+ @working << @listen_fds.delete(client)
71
+ log_debug "[#{log_signature}] Working"
72
+ @threads << { :thread => new_worker(client),
73
+ :socket => client }
74
+
75
+ cleanup_threads if cleanup?
76
+ end
77
+ end
78
+ end
79
+ else
80
+ # Run a cleanup if select times out while waiting
81
+ cleanup_threads if @threads.size > 1
82
+ end
83
+ end
84
+
85
+ rescue
86
+ # Closing the server causes the loop to raise an exception here
87
+ raise if running?
88
+ end
89
+
90
+ def running?
91
+ @running
92
+ end
93
+
94
+ def stop
95
+ @running = false
96
+ @server.close
97
+ end
98
+
99
+ end
100
+
101
+ class Worker
102
+ include Protobuf::Rpc::Server
103
+ include Protobuf::Logger::LogMethods
104
+
105
+ def initialize(sock, &complete_cb)
106
+ @did_response = false
107
+ @socket = sock
108
+ @request = Protobuf::Socketrpc::Request.new
109
+ @response = Protobuf::Socketrpc::Response.new
110
+ @buffer = Protobuf::Rpc::Buffer.new(:read)
111
+ @stats = Protobuf::Rpc::Stat.new(:SERVER, true)
112
+ @complete_cb = complete_cb
113
+ log_debug "[#{log_signature}] Post init, new read buffer created"
114
+
115
+ @stats.client = Socket.unpack_sockaddr_in(@socket.getpeername)
116
+ @buffer << read_data
117
+ log_debug "[#{log_signature}] handling request"
118
+ handle_client if @buffer.flushed?
119
+ end
120
+
121
+ def log_signature
122
+ @log_signature ||= "server-#{self.class}-#{object_id}"
123
+ end
124
+
125
+ def read_data
126
+ size_io = StringIO.new
127
+
128
+ while((size_reader = @socket.getc) != "-")
129
+ size_io << size_reader
130
+ end
131
+ str_size_io = size_io.string
132
+
133
+ "#{str_size_io}-#{@socket.read(str_size_io.to_i)}"
134
+ end
135
+
136
+ def send_data(data)
137
+ log_debug "[#{log_signature}] sending data : %s" % data
138
+ @socket.write(data)
139
+ @socket.flush
140
+ @complete_cb.call(@socket)
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -4,10 +4,9 @@ require 'protobuf/rpc/error'
4
4
 
5
5
  module Protobuf
6
6
  module Rpc
7
-
8
7
  # Object to encapsulate the request/response types for a given service method
9
8
  #
10
- RpcMethod = Struct.new "RpcMethod", :service, :method, :request_type, :response_type
9
+ RpcMethod = Struct.new("RpcMethod", :service, :method, :request_type, :response_type)
11
10
 
12
11
  class Service
13
12
  include Protobuf::Logger::LogMethods
@@ -27,34 +26,32 @@ module Protobuf
27
26
 
28
27
  # You MUST add the method name to this list if you are adding
29
28
  # instance methods below, otherwise stuff will definitely break
30
- NON_RPC_METHODS = %w( rpcs call_rpc on_rpc_failed rpc_failed request response method_missing async_responder on_send_response send_response )
29
+ NON_RPC_METHODS = %w( rpcs call_rpc on_rpc_failed rpc_failed request response method_missing async_responder on_send_response send_response log_signature )
31
30
 
32
31
  # Override methods being added to the class
33
32
  # If the method isn't already a private instance method, or it doesn't start with rpc_,
34
33
  # or it isn't in the reserved method list (NON_RPC_METHODS),
35
34
  # We want to remap the method such that we can wrap it in before and after behavior,
36
35
  # most notably calling call_rpc against the method. See call_rpc for more info.
37
- def method_added old
36
+ def method_added(old)
38
37
  new_method = :"rpc_#{old}"
39
38
  return if private_instance_methods.include?(new_method) or old =~ /^rpc_/ or NON_RPC_METHODS.include?(old.to_s)
40
39
 
41
40
  alias_method new_method, old
42
41
  private new_method
43
42
 
44
- begin
45
- define_method(old) do |pb_request|
46
- call_rpc old.to_sym, pb_request
47
- end
48
- rescue ArgumentError => e
49
- # Wrap a known issue where an instance method was defined in the class without
50
- # it being ignored with NON_RPC_METHODS.
51
- raise ArgumentError, "#{e.message} (Note: This could mean that you need to add the method #{old} to the NON_RPC_METHODS list)"
43
+ define_method(old) do |pb_request|
44
+ call_rpc(old.to_sym, pb_request)
52
45
  end
46
+ rescue ArgumentError => e
47
+ # Wrap a known issue where an instance method was defined in the class without
48
+ # it being ignored with NON_RPC_METHODS.
49
+ raise ArgumentError, "#{e.message} (Note: This could mean that you need to add the method #{old} to the NON_RPC_METHODS list)"
53
50
  end
54
51
 
55
52
  # Generated service classes should call this method on themselves to add rpc methods
56
53
  # to the stack with a given request and response type
57
- def rpc method, request_type, response_type
54
+ def rpc(method, request_type, response_type)
58
55
  rpcs[self] ||= {}
59
56
  rpcs[self][method] = RpcMethod.new self, method, request_type, response_type
60
57
  end
@@ -68,7 +65,7 @@ module Protobuf
68
65
  # See Client#initialize and ClientConnection::DEFAULT_OPTIONS
69
66
  # for all available options.
70
67
  #
71
- def client options={}
68
+ def client(options={})
72
69
  configure
73
70
  Client.new({
74
71
  :service => self,
@@ -83,17 +80,17 @@ module Protobuf
83
80
  # so that any Clients using the Service.client sugar
84
81
  # will not have to configure the location each time.
85
82
  #
86
- def configure config={}
83
+ def configure(config={})
87
84
  locations[self] ||= {}
88
- locations[self][:host] = config[:host] if config.key? :host
89
- locations[self][:port] = config[:port] if config.key? :port
85
+ locations[self][:host] = config[:host] if config.key?(:host)
86
+ locations[self][:port] = config[:port] if config.key?(:port)
90
87
  end
91
88
 
92
89
  # Shorthand call to configure, passing a string formatted as hostname:port
93
90
  # e.g. 127.0.0.1:9933
94
91
  # e.g. localhost:0
95
92
  #
96
- def located_at location
93
+ def located_at(location)
97
94
  return if location.nil? or location.downcase.strip !~ /[a-z0-9.]+:\d+/
98
95
  host, port = location.downcase.strip.split ':'
99
96
  configure :host => host, :port => port.to_i
@@ -117,6 +114,10 @@ module Protobuf
117
114
  end
118
115
 
119
116
  end
117
+
118
+ def log_signature
119
+ @log_signature ||= "service-#{self.class}"
120
+ end
120
121
 
121
122
  # If a method comes through that hasn't been found, and it
122
123
  # is defined in the rpcs method list, we know that the rpc
@@ -129,7 +130,7 @@ module Protobuf
129
130
  log_error exc.message
130
131
  raise exc
131
132
  else
132
- log_error '-------------- [service] %s#%s not rpc method, passing to super' % [self.class.name, m.to_s]
133
+ log_error "-------------- [#{log_signature}] %s#%s not rpc method, passing to super" % [self.class.name, m.to_s]
133
134
  super m, params
134
135
  end
135
136
  end
@@ -141,7 +142,7 @@ module Protobuf
141
142
 
142
143
  # Callback register for the server when a service
143
144
  # method calls rpc_failed. Called by Service#rpc_failed.
144
- def on_rpc_failed &rpc_failure_cb
145
+ def on_rpc_failed(&rpc_failure_cb)
145
146
  @rpc_failure_cb = rpc_failure_cb
146
147
  end
147
148
 
@@ -149,14 +150,11 @@ module Protobuf
149
150
  # NOTE: This shortcuts the @async_responder paradigm. There is
150
151
  # not any way to get around this currently (and I'm not sure you should want to).
151
152
  #
152
- def rpc_failed message="RPC Failed while executing service method #{@current_method}"
153
- if @rpc_failure_cb.nil?
154
- exc = RuntimeError.new 'Unable to invoke rpc_failed, no failure callback is setup.'
155
- log_error exc.message
156
- raise exc
157
- end
153
+ def rpc_failed(message="RPC Failed while executing service method #{@current_method}")
154
+ error_message = 'Unable to invoke rpc_failed, no failure callback is setup.'
155
+ log_and_raise_error(error_message) if @rpc_failure_cb.nil?
158
156
  error = message.is_a?(String) ? RpcFailed.new(message) : message
159
- log_warn '[service] RPC Failed: %s' % error.message
157
+ log_warn "[#{log_signature}] RPC Failed: %s" % error.message
160
158
  @rpc_failure_cb.call(error)
161
159
  end
162
160
 
@@ -164,7 +162,7 @@ module Protobuf
164
162
  # when it is appropriate to generate a response to the client.
165
163
  # Used in conjunciton with Service#send_response.
166
164
  #
167
- def on_send_response &responder
165
+ def on_send_response(&responder)
168
166
  @responder = responder
169
167
  end
170
168
 
@@ -175,15 +173,17 @@ module Protobuf
175
173
  # will timeout since no data will be sent.
176
174
  #
177
175
  def send_response
178
- if @responder.nil?
179
- exc = RuntimeError.new "Unable to send response, responder is nil. It appears you aren't inside of an RPC request/response cycle."
180
- log_error exc.message
181
- raise exc
182
- end
183
- @responder.call @response
176
+ error_message = "Unable to send response, responder is nil. It appears you aren't inside of an RPC request/response cycle."
177
+ log_and_raise_error(error_message) if @responder.nil?
178
+ @responder.call(@response)
184
179
  end
185
180
 
186
181
  private
182
+
183
+ def log_and_raise_error(error_message)
184
+ log_error(error_message)
185
+ raise error_message
186
+ end
187
187
 
188
188
  # Call the rpc method that was previously privatized.
189
189
  # call_rpc allows us to wrap the normal method call with
@@ -202,38 +202,36 @@ module Protobuf
202
202
  # by calling self.send_response without any arguments. The rpc
203
203
  # server is setup to handle synchronous and asynchronous responses.
204
204
  #
205
- def call_rpc method, pb_request
205
+ def call_rpc(method, pb_request)
206
206
  @current_method = method
207
207
 
208
208
  # Allows the service to set whether or not
209
209
  # it would like to asynchronously respond to the connected client(s)
210
210
  @async_responder = false
211
211
 
212
- begin
213
- # Setup the request
214
- @request = rpcs[method].request_type.new
215
- @request.parse_from_string pb_request.request_proto
216
- rescue
217
- exc = BadRequestProto.new 'Unable to parse request: %s' % $!.message
218
- log_error exc.message
219
- log_error $!.backtrace.join("\n")
220
- raise exc
221
- end
222
-
212
+ # Setup the request
213
+ @request = rpcs[method].request_type.new
214
+ @request.parse_from_string(pb_request.request_proto)
215
+ rescue
216
+ exc = BadRequestProto.new 'Unable to parse request: %s' % $!.message
217
+ log_error exc.message
218
+ log_error $!.backtrace.join("\n")
219
+ raise exc
220
+ else # when no Exception was thrown
223
221
  # Setup the response
224
222
  @response = rpcs[method].response_type.new
225
223
 
226
- log_debug '[service] calling service method %s#%s' % [self.class.name, method]
224
+ log_debug "[#{log_signature}] calling service method %s#%s" % [self.class, method]
227
225
  # Call the aliased rpc method (e.g. :rpc_find for :find)
228
226
  __send__("rpc_#{method}".to_sym)
229
- log_debug '[service] completed service method %s#%s' % [self.class.name, method]
227
+ log_debug "[#{log_signature}] completed service method %s#%s" % [self.class, method]
230
228
 
231
229
  # Pass the populated response back to the server
232
230
  # Note this will only get called if the rpc method didn't explode (by design)
233
231
  if @async_responder
234
- log_debug '[service] async request, not sending response (yet)'
232
+ log_debug "[#{log_signature}] async request, not sending response (yet)"
235
233
  else
236
- log_debug '[service] trigger server send_response'
234
+ log_debug "[#{log_signature}] trigger server send_response"
237
235
  send_response
238
236
  end
239
237
  end
@@ -241,4 +239,5 @@ module Protobuf
241
239
  end
242
240
 
243
241
  end
244
- end
242
+
243
+ end
@@ -51,12 +51,12 @@ module Protobuf
51
51
  end
52
52
 
53
53
  def log_stats
54
- Protobuf::Logger.info to_s
54
+ Protobuf::Logger.info(self.to_s)
55
55
  end
56
56
 
57
57
  def to_s
58
58
  [
59
- @type == :SERVER ? '[SRV]' : '[CLT]',
59
+ @type == :SERVER ? "[SRV-#{self.class}]" : "[CLT-#{self.class}]",
60
60
  rpc,
61
61
  elapsed_time,
62
62
  sizes,
@@ -1,3 +1,3 @@
1
1
  module Protobuf
2
- VERSION = '1.0.1'
2
+ VERSION = '1.1.0.beta0'
3
3
  end
data/protobuf.gemspec CHANGED
@@ -1,4 +1,4 @@
1
- # -*- encoding: utf-8 -*-
1
+ # encoding: UTF-8
2
2
  $:.push File.expand_path("./lib", File.dirname(__FILE__))
3
3
  require "protobuf/version"
4
4
 
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.date = %q{2011-12-07}
9
9
 
10
10
  s.authors = ['BJ Neilsen', 'Brandon Dewitt']
11
- s.email = ["bj.neilsen@gmail.com", "brandonsdewitt@gmail.com"]
11
+ s.email = ["bj.neilsen@gmail.com", "brandonsdewitt+protobuf@gmail.com"]
12
12
  s.homepage = %q{https://github.com/localshred/protobuf}
13
13
  s.summary = 'Ruby implementation for Protocol Buffers. Works with other protobuf rpc implementations (e.g. Java, Python, C++).'
14
14
  s.description = s.summary + "\n\nThis gem has diverged from https://github.com/macks/ruby-protobuf. All credit for serialization and rprotoc work most certainly goes to the original authors. All RPC implementation code (client/server/service) was written and is maintained by this author. Attempts to reconcile the original codebase with the current RPC implementation went unsuccessful."
@@ -19,7 +19,12 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_dependency 'eventmachine', '~> 0.12.10'
22
-
22
+ s.add_dependency 'eventually', '~> 0.1.0'
23
+ s.add_dependency 'json_pure', '~> 1.6.4'
24
+
23
25
  s.add_development_dependency 'rake', '~> 0.8.7'
24
- s.add_development_dependency 'rspec', '~> 2.7.0'
26
+ s.add_development_dependency 'rspec', '~> 2.8.0'
27
+ s.add_development_dependency 'yard', '~> 0.7.4'
28
+ s.add_development_dependency 'redcarpet', '~> 1.17.2'
29
+ s.add_development_dependency 'simplecov', '~> 0.5.4'
25
30
  end