protobuf 1.0.1 → 1.1.0.beta0

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