protobuf 2.7.11-java → 2.8.0.beta1-java
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +39 -2
- data/lib/protobuf.rb +17 -26
- data/lib/protobuf/cli.rb +106 -86
- data/lib/protobuf/field/float_field.rb +5 -1
- data/lib/protobuf/rpc/connectors/base.rb +1 -1
- data/lib/protobuf/rpc/connectors/zmq.rb +157 -29
- data/lib/protobuf/rpc/dynamic_discovery.pb.rb +49 -0
- data/lib/protobuf/rpc/error/client_error.rb +5 -5
- data/lib/protobuf/rpc/error/server_error.rb +7 -7
- data/lib/protobuf/rpc/rpc.pb.rb +13 -12
- data/lib/protobuf/rpc/servers/evented_runner.rb +11 -6
- data/lib/protobuf/rpc/servers/socket/server.rb +19 -15
- data/lib/protobuf/rpc/servers/socket_runner.rb +21 -18
- data/lib/protobuf/rpc/servers/zmq/broker.rb +104 -94
- data/lib/protobuf/rpc/servers/zmq/server.rb +263 -43
- data/lib/protobuf/rpc/servers/zmq/util.rb +18 -6
- data/lib/protobuf/rpc/servers/zmq/worker.rb +102 -39
- data/lib/protobuf/rpc/servers/zmq_runner.rb +31 -20
- data/lib/protobuf/rpc/service.rb +24 -12
- data/lib/protobuf/rpc/service_directory.rb +206 -0
- data/lib/protobuf/rpc/stat.rb +1 -1
- data/lib/protobuf/version.rb +1 -1
- data/proto/dynamic_discovery.proto +44 -0
- data/spec/benchmark/tasks.rb +1 -3
- data/spec/functional/socket_server_spec.rb +6 -5
- data/spec/functional/zmq_server_spec.rb +59 -30
- data/spec/lib/protobuf/cli_spec.rb +49 -54
- data/spec/lib/protobuf/enum_spec.rb +1 -1
- data/spec/lib/protobuf/rpc/client_spec.rb +1 -1
- data/spec/lib/protobuf/rpc/connectors/zmq_spec.rb +43 -1
- data/spec/lib/protobuf/rpc/servers/evented_server_spec.rb +2 -1
- data/spec/lib/protobuf/rpc/servers/socket_server_spec.rb +9 -8
- data/spec/lib/protobuf/rpc/servers/zmq/server_spec.rb +24 -19
- data/spec/lib/protobuf/rpc/servers/zmq/util_spec.rb +5 -5
- data/spec/lib/protobuf/rpc/service_directory_spec.rb +183 -0
- data/spec/support/server.rb +21 -12
- data/spec/support/test/resource.pb.rb +6 -0
- data/spec/support/test/resource.proto +5 -0
- data/spec/support/test/resource_service.rb +7 -0
- metadata +11 -11
- data/spec/lib/protobuf/rpc/servers/zmq/broker_spec.rb +0 -31
@@ -1,29 +1,41 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
|
1
3
|
module Protobuf
|
2
4
|
module Rpc
|
3
5
|
module Zmq
|
4
6
|
|
5
|
-
WORKER_READY_MESSAGE = "
|
7
|
+
WORKER_READY_MESSAGE = "\1"
|
6
8
|
|
7
9
|
module Util
|
8
10
|
include ::Protobuf::Logger::LogMethods
|
11
|
+
|
9
12
|
def self.included(base)
|
10
13
|
base.extend(::Protobuf::Rpc::Zmq::Util)
|
11
14
|
end
|
12
15
|
|
13
|
-
def zmq_error_check(return_code)
|
14
|
-
|
16
|
+
def zmq_error_check(return_code, source = nil)
|
17
|
+
unless ::ZMQ::Util.resultcode_ok?(return_code)
|
18
|
+
raise <<-ERROR
|
19
|
+
Last ZMQ API call #{source ? "to #{source}" : ""} failed with "#{::ZMQ::Util.error_string}".
|
20
|
+
|
21
|
+
#{caller(1).join($/)}
|
22
|
+
ERROR
|
23
|
+
end
|
15
24
|
end
|
16
25
|
|
17
26
|
def log_signature
|
18
|
-
@_log_signature
|
27
|
+
unless @_log_signature
|
28
|
+
name = (self.class == Class ? self.name : self.class.name)
|
29
|
+
@_log_signature = "[server-#{name}-#{object_id}]"
|
30
|
+
end
|
31
|
+
|
32
|
+
@_log_signature
|
19
33
|
end
|
20
34
|
|
21
35
|
def resolve_ip(hostname)
|
22
36
|
::Resolv.getaddress(hostname)
|
23
37
|
end
|
24
|
-
|
25
38
|
end
|
26
|
-
|
27
39
|
end
|
28
40
|
end
|
29
41
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'protobuf/rpc/server'
|
2
2
|
require 'protobuf/rpc/servers/zmq/util'
|
3
|
+
|
3
4
|
module Protobuf
|
4
5
|
module Rpc
|
5
6
|
module Zmq
|
6
|
-
|
7
7
|
class Worker
|
8
8
|
include ::Protobuf::Rpc::Server
|
9
9
|
include ::Protobuf::Rpc::Zmq::Util
|
@@ -11,63 +11,126 @@ module Protobuf
|
|
11
11
|
##
|
12
12
|
# Constructor
|
13
13
|
#
|
14
|
-
def initialize(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
@poller = ::ZMQ::Poller.new
|
23
|
-
@poller.register(@socket, ::ZMQ::POLLIN)
|
24
|
-
|
25
|
-
# Send request to broker telling it we are ready
|
26
|
-
zmq_error_check(@socket.send_string(::Protobuf::Rpc::Zmq::WORKER_READY_MESSAGE))
|
14
|
+
def initialize(server)
|
15
|
+
@server = server
|
16
|
+
init_zmq_context
|
17
|
+
init_backend_socket
|
18
|
+
init_shutdown_socket
|
19
|
+
rescue
|
20
|
+
teardown
|
21
|
+
raise
|
27
22
|
end
|
28
23
|
|
29
24
|
##
|
30
25
|
# Instance Methods
|
31
26
|
#
|
32
|
-
def
|
33
|
-
|
34
|
-
|
27
|
+
def alive?
|
28
|
+
@thread.try(:alive?) || false
|
29
|
+
end
|
35
30
|
|
36
|
-
|
37
|
-
@
|
38
|
-
|
31
|
+
def join
|
32
|
+
@thread.try(:join)
|
33
|
+
end
|
34
|
+
|
35
|
+
def process_request
|
36
|
+
@client_address, empty, @request_data = read_from_backend
|
37
|
+
|
38
|
+
unless @request_data.nil?
|
39
|
+
log_debug { sign_message("handling request") }
|
40
|
+
handle_client
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_from_backend
|
45
|
+
[].tap do |frames|
|
46
|
+
zmq_error_check(@backend_socket.recv_strings(frames))
|
47
|
+
end
|
39
48
|
end
|
40
49
|
|
41
50
|
def run
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
51
|
+
poller = ::ZMQ::Poller.new
|
52
|
+
poller.register_readable(@backend_socket)
|
53
|
+
poller.register_readable(@shutdown_socket)
|
54
|
+
|
55
|
+
# Send request to broker telling it we are ready
|
56
|
+
write_to_backend([::Protobuf::Rpc::Zmq::WORKER_READY_MESSAGE])
|
57
|
+
|
58
|
+
catch(:shutdown) do
|
59
|
+
while poller.poll > 0
|
60
|
+
poller.readables.each do |readable|
|
61
|
+
case readable
|
62
|
+
when @backend_socket
|
63
|
+
initialize_request!
|
64
|
+
process_request
|
65
|
+
when @shutdown_socket
|
66
|
+
throw :shutdown
|
67
|
+
end
|
68
|
+
end
|
50
69
|
end
|
51
70
|
end
|
52
71
|
ensure
|
53
|
-
|
54
|
-
@zmq_context.terminate
|
72
|
+
teardown
|
55
73
|
end
|
56
74
|
|
57
75
|
def send_data
|
58
|
-
|
76
|
+
data = @response.serialize_to_string
|
59
77
|
|
60
|
-
|
61
|
-
@client_address, # client uuid address
|
62
|
-
"",
|
63
|
-
response_data
|
64
|
-
]
|
78
|
+
@stats.response_size = data.size
|
65
79
|
|
66
|
-
@
|
67
|
-
|
80
|
+
write_to_backend([@client_address, "", data])
|
81
|
+
end
|
82
|
+
|
83
|
+
def shutdown_uri
|
84
|
+
"inproc://#{object_id}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def signal_shutdown
|
88
|
+
socket = @zmq_context.socket ZMQ::PAIR
|
89
|
+
zmq_error_check(socket.connect(shutdown_uri))
|
90
|
+
zmq_error_check(socket.send_string("."))
|
91
|
+
zmq_error_check(socket.close)
|
92
|
+
end
|
93
|
+
|
94
|
+
def start
|
95
|
+
@thread = Thread.new do
|
96
|
+
begin
|
97
|
+
self.run
|
98
|
+
rescue => e
|
99
|
+
message = "Worker failed: #{e.inspect}\n #{e.backtrace.join($/)}"
|
100
|
+
$stderr.puts(message)
|
101
|
+
log_error { message }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
def teardown
|
109
|
+
@backend_socket.try(:close)
|
110
|
+
@shutdown_socket.try(:close)
|
111
|
+
@zmq_context.try(:terminate)
|
68
112
|
end
|
69
|
-
end
|
70
113
|
|
114
|
+
def write_to_backend(frames)
|
115
|
+
zmq_error_check(@backend_socket.send_strings(frames))
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def init_zmq_context
|
121
|
+
@zmq_context = ZMQ::Context.new
|
122
|
+
end
|
123
|
+
|
124
|
+
def init_backend_socket
|
125
|
+
@backend_socket = @zmq_context.socket(ZMQ::REQ)
|
126
|
+
zmq_error_check(@backend_socket.connect(@server.backend_uri))
|
127
|
+
end
|
128
|
+
|
129
|
+
def init_shutdown_socket
|
130
|
+
@shutdown_socket = @zmq_context.socket(ZMQ::PAIR)
|
131
|
+
zmq_error_check(@shutdown_socket.bind(shutdown_uri))
|
132
|
+
end
|
133
|
+
end
|
71
134
|
end
|
72
135
|
end
|
73
136
|
end
|
@@ -1,35 +1,46 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
1
3
|
module Protobuf
|
2
4
|
module Rpc
|
3
5
|
class ZmqRunner
|
4
6
|
include ::Protobuf::Logger::LogMethods
|
5
7
|
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
def initialize(options)
|
9
|
+
@options = case
|
10
|
+
when options.is_a?(OpenStruct) then
|
11
|
+
options.marshal_dump
|
12
|
+
when options.respond_to?(:to_hash) then
|
13
|
+
options.to_hash
|
14
|
+
else
|
15
|
+
raise "Cannot parser Zmq Server - server options"
|
16
|
+
end
|
13
17
|
|
14
|
-
|
15
|
-
server_config = case
|
16
|
-
when server.is_a?(OpenStruct) then
|
17
|
-
server.marshal_dump
|
18
|
-
when server.respond_to?(:to_hash) then
|
19
|
-
server.to_hash
|
20
|
-
else
|
21
|
-
raise "Cannot parser Zmq Server - server options"
|
22
|
-
end
|
18
|
+
end
|
23
19
|
|
24
|
-
|
20
|
+
def run
|
21
|
+
@server = ::Protobuf::Rpc::Zmq::Server.new(@options)
|
22
|
+
register_signals
|
23
|
+
yield if block_given?
|
24
|
+
@server.run
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
+
def running?
|
28
|
+
@server.try :running?
|
27
29
|
end
|
28
30
|
|
29
|
-
def
|
30
|
-
|
31
|
+
def stop
|
32
|
+
@server.try :stop
|
31
33
|
end
|
32
34
|
|
35
|
+
private
|
36
|
+
|
37
|
+
def register_signals
|
38
|
+
trap(:TTIN) do
|
39
|
+
log_info { "TTIN received: Starting new worker" }
|
40
|
+
@server.start_worker
|
41
|
+
log_info { "Worker count : #{::Protobuf::Rpc::Zmq::Server.threads.size}" }
|
42
|
+
end
|
43
|
+
end
|
33
44
|
end
|
34
45
|
end
|
35
46
|
end
|
data/lib/protobuf/rpc/service.rb
CHANGED
@@ -96,6 +96,18 @@ module Protobuf
|
|
96
96
|
rpcs.key?(name)
|
97
97
|
end
|
98
98
|
|
99
|
+
# An array of defined service classes that contain implementation
|
100
|
+
# code
|
101
|
+
def self.implemented_services
|
102
|
+
classes = (self.subclasses || []).select do |subclass|
|
103
|
+
subclass.rpcs.any? do |(name, method)|
|
104
|
+
subclass.method_defined? name
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
classes.map &:name
|
109
|
+
end
|
110
|
+
|
99
111
|
##
|
100
112
|
# Instance Methods
|
101
113
|
#
|
@@ -122,6 +134,18 @@ module Protobuf
|
|
122
134
|
@_response ||= response_type.new
|
123
135
|
end
|
124
136
|
|
137
|
+
# Request object for this rpc cycle. Not assignable.
|
138
|
+
#
|
139
|
+
def request
|
140
|
+
@_request ||= if @_request_bytes.present?
|
141
|
+
request_type.new.parse_from_string(@_request_bytes)
|
142
|
+
else
|
143
|
+
request_type.new
|
144
|
+
end
|
145
|
+
rescue => e
|
146
|
+
raise BadRequestProto, "Unable to parse request: #{e.message}"
|
147
|
+
end
|
148
|
+
|
125
149
|
# Convenience method to get back to class method.
|
126
150
|
#
|
127
151
|
def rpc_method?(name)
|
@@ -149,18 +173,6 @@ module Protobuf
|
|
149
173
|
@_response_type ||= rpcs[@method_name].response_type
|
150
174
|
end
|
151
175
|
|
152
|
-
# Request object for this rpc cycle. Not assignable.
|
153
|
-
#
|
154
|
-
def request
|
155
|
-
@_request ||= if @_request_bytes.present?
|
156
|
-
request_type.new.parse_from_string(@_request_bytes)
|
157
|
-
else
|
158
|
-
request_type.new
|
159
|
-
end
|
160
|
-
rescue => e
|
161
|
-
raise BadRequestProto, "Unable to parse request: #{e.message}"
|
162
|
-
end
|
163
|
-
|
164
176
|
def request_type
|
165
177
|
@_request_type ||= rpcs[@method_name].request_type
|
166
178
|
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'singleton'
|
3
|
+
require 'socket'
|
4
|
+
require 'thread'
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
require 'protobuf/rpc/dynamic_discovery.pb'
|
8
|
+
|
9
|
+
module Protobuf
|
10
|
+
module Rpc
|
11
|
+
class ServiceDirectory
|
12
|
+
include ::Singleton
|
13
|
+
include ::Protobuf::Logger::LogMethods
|
14
|
+
|
15
|
+
DEFAULT_ADDRESS = "255.255.255.255"
|
16
|
+
DEFAULT_PORT = 53000
|
17
|
+
DEFAULT_TIMEOUT = 1
|
18
|
+
|
19
|
+
class Listing < Delegator
|
20
|
+
attr_reader :expires_at
|
21
|
+
|
22
|
+
def initialize(server)
|
23
|
+
@server = server
|
24
|
+
@expires_at = Time.now.to_i + ttl
|
25
|
+
end
|
26
|
+
|
27
|
+
def current?
|
28
|
+
!expired?
|
29
|
+
end
|
30
|
+
|
31
|
+
def expired?
|
32
|
+
Time.now.to_i >= @expires_at
|
33
|
+
end
|
34
|
+
|
35
|
+
def ttl
|
36
|
+
[super.to_i, 3].max
|
37
|
+
end
|
38
|
+
|
39
|
+
def __getobj__
|
40
|
+
@server
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Class Methods
|
45
|
+
#
|
46
|
+
class << self
|
47
|
+
attr_writer :address, :port
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.address
|
51
|
+
@address ||= DEFAULT_ADDRESS
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.port
|
55
|
+
@port ||= DEFAULT_PORT
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.start
|
59
|
+
yield(self) if block_given?
|
60
|
+
self.instance.start
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.stop
|
64
|
+
self.instance.stop
|
65
|
+
end
|
66
|
+
|
67
|
+
# Instance Methods
|
68
|
+
#
|
69
|
+
def initialize
|
70
|
+
@listings = {}
|
71
|
+
@mutex = Mutex.new
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_listing_for(server)
|
75
|
+
if server && server.uuid
|
76
|
+
|
77
|
+
log_debug do
|
78
|
+
action = @listings[server.uuid] ? "Updating" : "Adding";
|
79
|
+
sign_message("#{action} server: #{server.inspect}")
|
80
|
+
end
|
81
|
+
|
82
|
+
@mutex.synchronize do
|
83
|
+
@listings[server.uuid] = Listing.new(server)
|
84
|
+
end
|
85
|
+
|
86
|
+
else
|
87
|
+
log_info { sign_message("Cannot add server without uuid: #{server.inspect}") }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def lookup(service)
|
92
|
+
@mutex.synchronize do
|
93
|
+
listings = @listings.values.select do |listing|
|
94
|
+
listing.services.any? do |listed_service|
|
95
|
+
listing.current? && listed_service == service.to_s
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
listings.sample
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def remove_expired_listings
|
104
|
+
@mutex.synchronize do
|
105
|
+
@listings.delete_if do |uuid, listing|
|
106
|
+
listing.expired?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def remove_listing_for(server)
|
112
|
+
if server && server.uuid
|
113
|
+
log_debug { sign_message("Removing server: #{server.inspect}") }
|
114
|
+
|
115
|
+
@mutex.synchronize do
|
116
|
+
@listings.delete(server.uuid)
|
117
|
+
end
|
118
|
+
|
119
|
+
else
|
120
|
+
log_info { sign_message("Cannot remove server without uuid: #{server.inspect}") }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def restart
|
125
|
+
stop
|
126
|
+
start
|
127
|
+
end
|
128
|
+
|
129
|
+
def running?
|
130
|
+
!!@thread.try(:alive?)
|
131
|
+
end
|
132
|
+
|
133
|
+
def start
|
134
|
+
unless running?
|
135
|
+
init_socket
|
136
|
+
log_info { sign_message("listening to udp://#{self.class.address}:#{self.class.port}") }
|
137
|
+
@thread = Thread.new { self.send(:run) }
|
138
|
+
end
|
139
|
+
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
def stop
|
144
|
+
log_info { sign_message("Stopping directory") }
|
145
|
+
|
146
|
+
@mutex.synchronize do
|
147
|
+
@thread.try(:kill)
|
148
|
+
@thread = nil
|
149
|
+
@listings = {}
|
150
|
+
end
|
151
|
+
|
152
|
+
@socket.try(:close)
|
153
|
+
@socket = nil
|
154
|
+
end
|
155
|
+
|
156
|
+
def wait_for(service, timeout = DEFAULT_TIMEOUT)
|
157
|
+
log_debug { sign_message("waiting for #{service}") }
|
158
|
+
Timeout.timeout(timeout) do
|
159
|
+
sleep(timeout / 10.0) until listing = lookup(service)
|
160
|
+
listing
|
161
|
+
end
|
162
|
+
rescue
|
163
|
+
log_info { sign_message("no listing found for #{service}") }
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def init_socket
|
170
|
+
@socket = UDPSocket.new
|
171
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
|
172
|
+
@socket.bind(self.class.address, self.class.port.to_i)
|
173
|
+
end
|
174
|
+
|
175
|
+
def process_beacon(beacon)
|
176
|
+
case beacon.beacon_type
|
177
|
+
when ::Protobuf::Rpc::DynamicDiscovery::BeaconType::HEARTBEAT
|
178
|
+
add_listing_for(beacon.server)
|
179
|
+
when ::Protobuf::Rpc::DynamicDiscovery::BeaconType::FLATLINE
|
180
|
+
remove_listing_for(beacon.server)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def run
|
185
|
+
loop do
|
186
|
+
process_beacon(wait_for_beacon)
|
187
|
+
remove_expired_listings
|
188
|
+
end
|
189
|
+
rescue => e
|
190
|
+
log_debug { sign_message("error: (#{e.class}) #{e.message}") }
|
191
|
+
retry
|
192
|
+
end
|
193
|
+
|
194
|
+
def wait_for_beacon
|
195
|
+
data, addr = @socket.recvfrom(2048)
|
196
|
+
|
197
|
+
::Protobuf::Rpc::DynamicDiscovery::Beacon.new.tap do |beacon|
|
198
|
+
beacon.parse_from_string(data) rescue nil
|
199
|
+
|
200
|
+
# Favor the address captured by the socket
|
201
|
+
beacon.try(:server).try(:address=, addr[3])
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|