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.
- data/.gitignore +3 -0
- data/.yardopts +5 -0
- data/Gemfile.lock +25 -10
- data/bin/rpc_server +38 -33
- data/lib/protobuf.rb +22 -3
- data/lib/protobuf/common/logger.rb +6 -8
- data/lib/protobuf/compiler/visitors.rb +8 -9
- data/lib/protobuf/descriptor/descriptor_builder.rb +6 -6
- data/lib/protobuf/ext/eventmachine.rb +2 -4
- data/lib/protobuf/message/message.rb +1 -3
- data/lib/protobuf/rpc/buffer.rb +6 -6
- data/lib/protobuf/rpc/client.rb +59 -21
- data/lib/protobuf/rpc/connector.rb +10 -9
- data/lib/protobuf/rpc/connectors/base.rb +23 -8
- data/lib/protobuf/rpc/connectors/common.rb +155 -0
- data/lib/protobuf/rpc/connectors/em_client.rb +23 -192
- data/lib/protobuf/rpc/connectors/eventmachine.rb +36 -44
- data/lib/protobuf/rpc/connectors/socket.rb +58 -1
- data/lib/protobuf/rpc/error.rb +6 -14
- data/lib/protobuf/rpc/server.rb +72 -99
- data/lib/protobuf/rpc/servers/evented_runner.rb +32 -0
- data/lib/protobuf/rpc/servers/evented_server.rb +29 -0
- data/lib/protobuf/rpc/servers/socket_runner.rb +17 -0
- data/lib/protobuf/rpc/servers/socket_server.rb +145 -0
- data/lib/protobuf/rpc/service.rb +50 -51
- data/lib/protobuf/rpc/stat.rb +2 -2
- data/lib/protobuf/version.rb +1 -1
- data/protobuf.gemspec +9 -4
- data/spec/helper/all.rb +1 -7
- data/spec/helper/server.rb +45 -5
- data/spec/helper/silent_constants.rb +40 -0
- data/spec/proto/test_service.rb +0 -1
- data/spec/proto/test_service_impl.rb +4 -3
- data/spec/spec_helper.rb +19 -6
- data/spec/unit/enum_spec.rb +4 -4
- data/spec/unit/rpc/client_spec.rb +32 -42
- data/spec/unit/rpc/connector_spec.rb +11 -16
- data/spec/unit/rpc/connectors/base_spec.rb +14 -3
- data/spec/unit/rpc/connectors/common_spec.rb +132 -0
- data/spec/unit/rpc/connectors/{eventmachine/client_spec.rb → eventmachine_client_spec.rb} +0 -0
- data/spec/unit/rpc/connectors/socket_spec.rb +49 -0
- data/spec/unit/rpc/servers/evented_server_spec.rb +18 -0
- data/spec/unit/rpc/servers/socket_server_spec.rb +57 -0
- metadata +86 -16
- data/spec/unit/rpc/server_spec.rb +0 -27
@@ -6,79 +6,71 @@ module Protobuf
|
|
6
6
|
module Connectors
|
7
7
|
class EventMachine < Base
|
8
8
|
|
9
|
-
def initialize options
|
10
|
-
super(EMClient::DEFAULT_OPTIONS.merge(options))
|
11
|
-
end
|
12
|
-
|
13
9
|
def send_request
|
14
|
-
|
10
|
+
ensure_em_running do
|
11
|
+
f = Fiber.current
|
12
|
+
|
13
|
+
EM.schedule do
|
14
|
+
log_debug "[#{log_signature}] Scheduling EventMachine client request to be created on next tick"
|
15
|
+
cnxn = EMClient.connect(options, &ensure_cb)
|
16
|
+
cnxn.on_success(&success_cb) if success_cb
|
17
|
+
cnxn.on_failure(&ensure_cb)
|
18
|
+
cnxn.on_complete { resume_fiber(f) } unless async?
|
19
|
+
log_debug "[#{log_signature}] Connection scheduled"
|
20
|
+
end
|
15
21
|
|
16
|
-
|
17
|
-
|
18
|
-
EM.schedule do
|
19
|
-
log_debug '[client] Scheduling EventMachine client request to be created on next tick'
|
20
|
-
cnxn = EMClient.connect options, &ensure_cb
|
21
|
-
cnxn.on_success &success_cb if success_cb
|
22
|
-
cnxn.on_failure &ensure_cb
|
23
|
-
cnxn.on_complete { resume_fiber f } unless async?
|
24
|
-
log_debug '[client] Connection scheduled'
|
22
|
+
async? ? true : set_timeout_and_validate_fiber
|
25
23
|
end
|
26
|
-
|
27
|
-
return set_timeout_and_validate_fiber unless async?
|
28
|
-
return true
|
29
24
|
end
|
30
25
|
|
31
|
-
# Returns a
|
26
|
+
# Returns a callable that ensures any errors will be returned to the client
|
32
27
|
#
|
33
28
|
# If a failure callback was set, just use that as a direct assignment
|
34
29
|
# otherwise implement one here that simply throws an exception, since we
|
35
30
|
# don't want to swallow the black holes.
|
36
31
|
#
|
37
32
|
def ensure_cb
|
38
|
-
@ensure_cb ||=
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
cbk = proc do |error|
|
44
|
-
raise '%s: %s' % [error.code.name, error.message]
|
45
|
-
end
|
46
|
-
end
|
47
|
-
cbk
|
48
|
-
end
|
33
|
+
@ensure_cb ||= (@failure_cb || lambda { |error| raise '%s: %s' % [error.code.name, error.message] } )
|
34
|
+
end
|
35
|
+
|
36
|
+
def log_signature
|
37
|
+
@log_signature ||= "client-#{self.class}"
|
49
38
|
end
|
50
39
|
|
51
40
|
private
|
52
41
|
|
53
|
-
def
|
54
|
-
|
55
|
-
message = 'Client timeout of %d seconds expired' % @options[:timeout]
|
56
|
-
err = ClientError.new(Protobuf::Socketrpc::ErrorReason::RPC_ERROR, message)
|
57
|
-
ensure_cb.call(err)
|
58
|
-
end
|
59
|
-
|
60
|
-
Fiber.yield
|
61
|
-
rescue FiberError
|
62
|
-
message = "Synchronous calls must be in 'EM.fiber_run' block"
|
63
|
-
err = ClientError.new(Protobuf::Socketrpc::ErrorReason::RPC_ERROR, message)
|
64
|
-
ensure_cb.call(err)
|
42
|
+
def ensure_em_running(&blk)
|
43
|
+
EM.reactor_running? ? yield : EM.fiber_run { blk.call; EM.stop }
|
65
44
|
end
|
66
45
|
|
67
46
|
def resume_fiber(fib)
|
68
47
|
EM::cancel_timer(@timeout_timer)
|
69
48
|
fib.resume(true)
|
70
49
|
rescue => ex
|
71
|
-
log_error
|
50
|
+
log_error "[#{log_signature}] An exception occurred while waiting for server response:"
|
72
51
|
log_error ex.message
|
73
52
|
log_error ex.backtrace.join("\n")
|
74
53
|
|
75
54
|
message = 'Synchronous client failed: %s' % ex.message
|
76
|
-
err = ClientError.new(Protobuf::Socketrpc::ErrorReason::RPC_ERROR, message)
|
55
|
+
err = Protobuf::Rpc::ClientError.new(Protobuf::Socketrpc::ErrorReason::RPC_ERROR, message)
|
77
56
|
ensure_cb.call(err)
|
78
57
|
end
|
79
58
|
|
59
|
+
def set_timeout_and_validate_fiber
|
60
|
+
@timeout_timer = EM::add_timer(@options[:timeout]) do
|
61
|
+
message = 'Client timeout of %d seconds expired' % @options[:timeout]
|
62
|
+
err = Protobuf::Rpc::ClientError.new(Protobuf::Socketrpc::ErrorReason::RPC_ERROR, message)
|
63
|
+
ensure_cb.call(err)
|
64
|
+
end
|
65
|
+
|
66
|
+
Fiber.yield
|
67
|
+
rescue FiberError
|
68
|
+
message = "Synchronous calls must be in 'EM.fiber_run' block"
|
69
|
+
err = Protobuf::Rpc::ClientError.new(Protobuf::Socketrpc::ErrorReason::RPC_ERROR, message)
|
70
|
+
ensure_cb.call(err)
|
71
|
+
end
|
80
72
|
|
81
73
|
end
|
82
74
|
end
|
83
75
|
end
|
84
|
-
end
|
76
|
+
end
|
@@ -4,10 +4,67 @@ module Protobuf
|
|
4
4
|
module Rpc
|
5
5
|
module Connectors
|
6
6
|
class Socket < Base
|
7
|
+
include Protobuf::Rpc::Connectors::Common
|
8
|
+
include Protobuf::Logger::LogMethods
|
7
9
|
|
8
10
|
def send_request
|
11
|
+
check_async
|
12
|
+
initialize_stats
|
13
|
+
connect_to_rpc_server
|
14
|
+
post_init # calls _send_request
|
15
|
+
read_response
|
9
16
|
end
|
10
|
-
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def check_async
|
21
|
+
if async?
|
22
|
+
log_error "[client-#{self.class}] Cannot run in async mode"
|
23
|
+
raise "Cannot use Socket client in async mode"
|
24
|
+
else
|
25
|
+
log_debug "[client-#{self.class}] Async check passed"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def close_connection
|
30
|
+
@socket.close
|
31
|
+
log_debug "[client-#{self.class}] Connector closed"
|
32
|
+
end
|
33
|
+
|
34
|
+
def connect_to_rpc_server
|
35
|
+
@socket = TCPSocket.new(options[:host], options[:port])
|
36
|
+
log_debug "[client-#{self.class}] Connection established #{options[:host]}:#{options[:port]}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Method to determine error state, must be used with Connector api
|
40
|
+
def error?
|
41
|
+
log_debug "[client-#{self.class}] Error state : #{@socket.closed?}"
|
42
|
+
@socket.closed?
|
43
|
+
end
|
44
|
+
|
45
|
+
def read_data
|
46
|
+
size_io = StringIO.new
|
47
|
+
|
48
|
+
while((size_reader = @socket.getc) != "-")
|
49
|
+
size_io << size_reader
|
50
|
+
end
|
51
|
+
str_size_io = size_io.string
|
52
|
+
|
53
|
+
"#{str_size_io}-#{@socket.read(str_size_io.to_i)}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def read_response
|
57
|
+
@buffer << read_data
|
58
|
+
parse_response if @buffer.flushed?
|
59
|
+
end
|
60
|
+
|
61
|
+
def send_data(data)
|
62
|
+
@socket.write(data)
|
63
|
+
@socket.flush
|
64
|
+
@socket.close_write
|
65
|
+
log_debug "[client-#{self.class}] write closed"
|
66
|
+
end
|
67
|
+
|
11
68
|
end
|
12
69
|
end
|
13
70
|
end
|
data/lib/protobuf/rpc/error.rb
CHANGED
@@ -2,20 +2,9 @@ require 'protobuf/rpc/rpc.pb'
|
|
2
2
|
|
3
3
|
module Protobuf
|
4
4
|
module Rpc
|
5
|
+
ClientError = Struct.new("ClientError", :code, :message)
|
5
6
|
|
6
|
-
|
7
|
-
autoload :BadRequestProto, 'protobuf/rpc/error/server_error'
|
8
|
-
autoload :ServiceNotFound, 'protobuf/rpc/error/server_error'
|
9
|
-
autoload :MethodNotFound, 'protobuf/rpc/error/server_error'
|
10
|
-
autoload :RpcError, 'protobuf/rpc/error/server_error'
|
11
|
-
autoload :RpcFailed, 'protobuf/rpc/error/server_error'
|
12
|
-
|
13
|
-
autoload :InvalidRequestProto, 'protobuf/rpc/error/client_error'
|
14
|
-
autoload :BadResponseProto, 'protobuf/rpc/error/client_error'
|
15
|
-
autoload :UnknownHost, 'protobuf/rpc/error/client_error'
|
16
|
-
autoload :IOError, 'protobuf/rpc/error/client_error'
|
17
|
-
|
18
|
-
# Base RpcError class for client and server errors
|
7
|
+
# Base PbError class for client and server errors
|
19
8
|
class PbError < StandardError
|
20
9
|
attr_reader :error_type
|
21
10
|
|
@@ -31,4 +20,7 @@ module Protobuf
|
|
31
20
|
end
|
32
21
|
|
33
22
|
end
|
34
|
-
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'protobuf/rpc/error/server_error'
|
26
|
+
require 'protobuf/rpc/error/client_error'
|
data/lib/protobuf/rpc/server.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'eventmachine'
|
2
|
-
require 'socket'
|
3
1
|
require 'protobuf/common/logger'
|
4
2
|
require 'protobuf/rpc/rpc.pb'
|
5
3
|
require 'protobuf/rpc/buffer'
|
@@ -8,26 +6,7 @@ require 'protobuf/rpc/stat'
|
|
8
6
|
|
9
7
|
module Protobuf
|
10
8
|
module Rpc
|
11
|
-
|
12
|
-
include Protobuf::Logger::LogMethods
|
13
|
-
|
14
|
-
# Initialize a new read buffer for storing client request info
|
15
|
-
def post_init
|
16
|
-
log_debug '[server] Post init, new read buffer created'
|
17
|
-
|
18
|
-
@stats = Protobuf::Rpc::Stat.new(:SERVER, true)
|
19
|
-
@stats.client = Socket.unpack_sockaddr_in(get_peername)
|
20
|
-
|
21
|
-
@buffer = Protobuf::Rpc::Buffer.new :read
|
22
|
-
@did_respond = false
|
23
|
-
end
|
24
|
-
|
25
|
-
# Receive a chunk of data, potentially flushed to handle_client
|
26
|
-
def receive_data data
|
27
|
-
log_debug '[server] receive_data: %s' % data
|
28
|
-
@buffer << data
|
29
|
-
handle_client if @buffer.flushed?
|
30
|
-
end
|
9
|
+
module Server
|
31
10
|
|
32
11
|
# Invoke the service method dictated by the proto wrapper request object
|
33
12
|
def handle_client
|
@@ -38,17 +17,16 @@ module Protobuf
|
|
38
17
|
@response = Protobuf::Socketrpc::Response.new
|
39
18
|
|
40
19
|
# Parse the protobuf request from the socket
|
41
|
-
log_debug
|
20
|
+
log_debug "[#{log_signature}] Parsing request from client"
|
42
21
|
parse_request_from_buffer
|
43
22
|
|
44
23
|
# Determine the service class and method name from the request
|
45
|
-
log_debug
|
24
|
+
log_debug "[#{log_signature}] Extracting procedure call info from request"
|
46
25
|
parse_service_info
|
47
26
|
|
48
27
|
# Call the service method
|
49
|
-
log_debug
|
28
|
+
log_debug "[#{log_signature}] Dispatching client request to service"
|
50
29
|
invoke_rpc_method
|
51
|
-
|
52
30
|
rescue => error
|
53
31
|
# Ensure we're handling any errors that try to slip out the back door
|
54
32
|
log_error error.message
|
@@ -56,8 +34,18 @@ module Protobuf
|
|
56
34
|
handle_error(error)
|
57
35
|
send_response
|
58
36
|
end
|
59
|
-
|
60
|
-
|
37
|
+
|
38
|
+
# Client error handler. Receives an exception object and writes it into the @response
|
39
|
+
def handle_error(error)
|
40
|
+
log_debug "[#{log_signature}] handle_error: %s" % error.inspect
|
41
|
+
if error.respond_to?(:to_response)
|
42
|
+
error.to_response(@response)
|
43
|
+
else
|
44
|
+
message = error.respond_to?(:message) ? error.message : error.to_s
|
45
|
+
code = error.respond_to?(:code) ? error.code.to_s : "RPC_ERROR"
|
46
|
+
PbError.new(message, code).to_response(@response)
|
47
|
+
end
|
48
|
+
end
|
61
49
|
|
62
50
|
# Assuming all things check out, we can call the service method
|
63
51
|
def invoke_rpc_method
|
@@ -85,99 +73,84 @@ module Protobuf
|
|
85
73
|
end
|
86
74
|
|
87
75
|
# Call the service method
|
88
|
-
log_debug
|
89
|
-
@service.__send__
|
76
|
+
log_debug "[#{log_signature}] Invoking %s#%s with request %s" % [@klass.name, @method, @request.inspect]
|
77
|
+
@service.__send__(@method, @request)
|
78
|
+
end
|
79
|
+
|
80
|
+
def log_signature
|
81
|
+
@log_signature ||= "server-#{self.class}"
|
90
82
|
end
|
91
83
|
|
92
84
|
# Parse the incoming request object into our expected request object
|
93
85
|
def parse_request_from_buffer
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
raise exc
|
101
|
-
end
|
86
|
+
log_debug "[#{log_signature}] parsing request from buffer: %s" % @buffer.data.inspect
|
87
|
+
@request.parse_from_string(@buffer.data)
|
88
|
+
rescue => error
|
89
|
+
exc = BadRequestData.new 'Unable to parse request: %s' % error.message
|
90
|
+
log_error exc.message
|
91
|
+
raise exc
|
102
92
|
end
|
103
|
-
|
93
|
+
|
104
94
|
# Read out the response from the service method,
|
105
95
|
# setting it on the pb request, and serializing the
|
106
96
|
# response to the protobuf response wrapper
|
107
|
-
def parse_response_from_service
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
if expected == actual
|
119
|
-
begin
|
120
|
-
# Response types match, so go ahead and serialize
|
121
|
-
log_debug '[server] serializing response: %s' % response.inspect
|
122
|
-
@response.response_proto = response.serialize_to_string
|
123
|
-
rescue
|
124
|
-
raise BadResponseProto, $!.message
|
125
|
-
end
|
126
|
-
else
|
127
|
-
# response types do not match, throw the appropriate error
|
128
|
-
raise BadResponseProto, 'Response proto changed from %s to %s' % [expected.name, actual.name]
|
129
|
-
end
|
130
|
-
rescue => error
|
131
|
-
log_error error.message
|
132
|
-
log_error error.backtrace.join("\n")
|
133
|
-
handle_error(error)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# Write the response wrapper to the client
|
138
|
-
def send_response
|
139
|
-
raise 'Response already sent to client' if @did_respond
|
140
|
-
log_debug '[server] Sending response to client: %s' % @response.inspect
|
141
|
-
response_buffer = Protobuf::Rpc::Buffer.new(:write, @response)
|
142
|
-
send_data(response_buffer.write)
|
143
|
-
@stats.response_size = response_buffer.size
|
144
|
-
@stats.end
|
145
|
-
@stats.log_stats
|
146
|
-
@did_respond = true
|
147
|
-
end
|
148
|
-
|
149
|
-
# Client error handler. Receives an exception object and writes it into the @response
|
150
|
-
def handle_error error
|
151
|
-
log_debug '[server] handle_error: %s' % error.inspect
|
152
|
-
if error.is_a? PbError
|
153
|
-
error.to_response @response
|
154
|
-
elsif error.is_a? ClientError
|
155
|
-
PbError.new(error.message, error.code.to_s).to_response @response
|
97
|
+
def parse_response_from_service(response)
|
98
|
+
expected = @klass.rpcs[@klass][@method].response_type
|
99
|
+
|
100
|
+
# Cannibalize the response if it's a Hash
|
101
|
+
response = expected.new(response) if response.is_a?(Hash)
|
102
|
+
actual = response.class
|
103
|
+
log_debug "[#{log_signature}] response (should/actual): %s/%s" % [expected.name, actual.name]
|
104
|
+
|
105
|
+
# Determine if the service tried to change response types on us
|
106
|
+
if expected == actual
|
107
|
+
serialize_response(response)
|
156
108
|
else
|
157
|
-
|
158
|
-
|
109
|
+
# response types do not match, throw the appropriate error
|
110
|
+
raise BadResponseProto, 'Response proto changed from %s to %s' % [expected.name, actual.name]
|
159
111
|
end
|
112
|
+
rescue => error
|
113
|
+
log_error error.message
|
114
|
+
log_error error.backtrace.join("\n")
|
115
|
+
handle_error(error)
|
160
116
|
end
|
161
|
-
|
117
|
+
|
162
118
|
# Parses and returns the service and method name from the request wrapper proto
|
163
119
|
def parse_service_info
|
164
|
-
@klass
|
165
|
-
|
166
|
-
begin
|
167
|
-
@klass = Util.constantize(@request.service_name)
|
168
|
-
rescue
|
169
|
-
raise ServiceNotFound, "Service class #{@request.service_name} is not found"
|
170
|
-
end
|
171
|
-
|
120
|
+
@klass = Util.constantize(@request.service_name)
|
172
121
|
@method = Util.underscore(@request.method_name).to_sym
|
122
|
+
|
173
123
|
unless @klass.instance_methods.include?(@method)
|
174
124
|
raise MethodNotFound, "Service method #{@request.method_name} is not defined by the service"
|
175
125
|
end
|
176
126
|
|
177
127
|
@stats.service = @klass.name
|
178
128
|
@stats.method = @method
|
129
|
+
rescue NameError
|
130
|
+
raise ServiceNotFound, "Service class #{@request.service_name} is not found"
|
131
|
+
end
|
132
|
+
|
133
|
+
# Write the response wrapper to the client
|
134
|
+
def send_response
|
135
|
+
raise 'Response already sent to client' if @did_respond
|
136
|
+
log_debug "[#{log_signature}] Sending response to client: %s" % @response.inspect
|
137
|
+
response_buffer = Protobuf::Rpc::Buffer.new(:write, @response)
|
138
|
+
send_data(response_buffer.write)
|
139
|
+
@stats.response_size = response_buffer.size
|
140
|
+
@stats.end
|
141
|
+
@stats.log_stats
|
142
|
+
@did_respond = true
|
143
|
+
end
|
144
|
+
|
145
|
+
def serialize_response(response)
|
146
|
+
log_debug "[#{log_signature}] serializing response: %s" % response.inspect
|
147
|
+
@response.response_proto = response.serialize_to_string
|
148
|
+
rescue
|
149
|
+
raise BadResponseProto, $!.message
|
179
150
|
end
|
180
151
|
|
181
152
|
end
|
153
|
+
|
182
154
|
end
|
155
|
+
|
183
156
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Protobuf
|
2
|
+
module Rpc
|
3
|
+
class EventedRunner
|
4
|
+
|
5
|
+
def self.stop
|
6
|
+
EventMachine.stop_event_loop if EventMachine.reactor_running?
|
7
|
+
Protobuf::Logger.info 'Shutdown complete'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.run(server)
|
11
|
+
# Ensure errors thrown within EM are caught and logged appropriately
|
12
|
+
EventMachine.error_handler do |error|
|
13
|
+
if error.message == 'no acceptor'
|
14
|
+
raise 'Failed binding to %s:%d (%s)' % [server.host, server.port, error.message]
|
15
|
+
else
|
16
|
+
Protobuf::Logger.error error.message
|
17
|
+
Protobuf::Logger.error error.backtrace.join("\n")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Startup and run the rpc server
|
22
|
+
EM.schedule do
|
23
|
+
EventMachine.start_server(server.host, server.port, Protobuf::Rpc::EventedServer) && \
|
24
|
+
Protobuf::Logger.info('RPC Server listening at %s:%d in %s' % [server.host, server.port, server.env])
|
25
|
+
end
|
26
|
+
|
27
|
+
# Join or start the reactor
|
28
|
+
EM.reactor_running? ? EM.reactor_thread.join : EM.run
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|