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.
Files changed (41) hide show
  1. data/README.md +39 -2
  2. data/lib/protobuf.rb +17 -26
  3. data/lib/protobuf/cli.rb +106 -86
  4. data/lib/protobuf/field/float_field.rb +5 -1
  5. data/lib/protobuf/rpc/connectors/base.rb +1 -1
  6. data/lib/protobuf/rpc/connectors/zmq.rb +157 -29
  7. data/lib/protobuf/rpc/dynamic_discovery.pb.rb +49 -0
  8. data/lib/protobuf/rpc/error/client_error.rb +5 -5
  9. data/lib/protobuf/rpc/error/server_error.rb +7 -7
  10. data/lib/protobuf/rpc/rpc.pb.rb +13 -12
  11. data/lib/protobuf/rpc/servers/evented_runner.rb +11 -6
  12. data/lib/protobuf/rpc/servers/socket/server.rb +19 -15
  13. data/lib/protobuf/rpc/servers/socket_runner.rb +21 -18
  14. data/lib/protobuf/rpc/servers/zmq/broker.rb +104 -94
  15. data/lib/protobuf/rpc/servers/zmq/server.rb +263 -43
  16. data/lib/protobuf/rpc/servers/zmq/util.rb +18 -6
  17. data/lib/protobuf/rpc/servers/zmq/worker.rb +102 -39
  18. data/lib/protobuf/rpc/servers/zmq_runner.rb +31 -20
  19. data/lib/protobuf/rpc/service.rb +24 -12
  20. data/lib/protobuf/rpc/service_directory.rb +206 -0
  21. data/lib/protobuf/rpc/stat.rb +1 -1
  22. data/lib/protobuf/version.rb +1 -1
  23. data/proto/dynamic_discovery.proto +44 -0
  24. data/spec/benchmark/tasks.rb +1 -3
  25. data/spec/functional/socket_server_spec.rb +6 -5
  26. data/spec/functional/zmq_server_spec.rb +59 -30
  27. data/spec/lib/protobuf/cli_spec.rb +49 -54
  28. data/spec/lib/protobuf/enum_spec.rb +1 -1
  29. data/spec/lib/protobuf/rpc/client_spec.rb +1 -1
  30. data/spec/lib/protobuf/rpc/connectors/zmq_spec.rb +43 -1
  31. data/spec/lib/protobuf/rpc/servers/evented_server_spec.rb +2 -1
  32. data/spec/lib/protobuf/rpc/servers/socket_server_spec.rb +9 -8
  33. data/spec/lib/protobuf/rpc/servers/zmq/server_spec.rb +24 -19
  34. data/spec/lib/protobuf/rpc/servers/zmq/util_spec.rb +5 -5
  35. data/spec/lib/protobuf/rpc/service_directory_spec.rb +183 -0
  36. data/spec/support/server.rb +21 -12
  37. data/spec/support/test/resource.pb.rb +6 -0
  38. data/spec/support/test/resource.proto +5 -0
  39. data/spec/support/test/resource_service.rb +7 -0
  40. metadata +11 -11
  41. 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 = "WORKER_READY"
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
- raise "Last API call failed with \"#{::ZMQ::Util.error_string}\"#{$/}#{$/}#{caller(1)}" unless return_code >= 0
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 ||= "server-#{self.class}-#{object_id}"
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(options = {})
15
- host = options[:host]
16
- port = options[:worker_port]
17
-
18
- @zmq_context = ::ZMQ::Context.new
19
- @socket = @zmq_context.socket(::ZMQ::REQ)
20
- zmq_error_check(@socket.connect("tcp://#{resolve_ip(host)}:#{port}"))
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 handle_request(socket)
33
- message_array = []
34
- zmq_error_check(socket.recv_strings(message_array))
27
+ def alive?
28
+ @thread.try(:alive?) || false
29
+ end
35
30
 
36
- @request_data = message_array[2]
37
- @client_address = message_array[0]
38
- log_debug { sign_message("handling request") } unless @request_data.nil?
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
- while ::Protobuf::Rpc::Zmq::Server.running? do
43
- # poll for 1_000 milliseconds then continue looping
44
- # This lets us see whether we need to die
45
- @poller.poll(1_000)
46
- @poller.readables.each do |socket|
47
- initialize_request!
48
- handle_request(socket)
49
- handle_client unless @request_data.nil?
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
- @socket.close
54
- @zmq_context.terminate
72
+ teardown
55
73
  end
56
74
 
57
75
  def send_data
58
- response_data = @response.to_s # to_s is aliases as serialize_to_string in Message
76
+ data = @response.serialize_to_string
59
77
 
60
- response_message_set = [
61
- @client_address, # client uuid address
62
- "",
63
- response_data
64
- ]
78
+ @stats.response_size = data.size
65
79
 
66
- @stats.response_size = response_data.size
67
- zmq_error_check(@socket.send_strings(response_message_set))
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 self.register_signals
7
- trap(:TTIN) do
8
- log_info { "TTIN received: Starting new worker" }
9
- ::Protobuf::Rpc::Zmq::Server.start_worker
10
- log_info { "Worker count : #{::Protobuf::Rpc::Zmq::Server.threads.size}" }
11
- end
12
- end
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
- def self.run(server)
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
- yield if block_given?
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
- ::Protobuf::Rpc::Zmq::Server.run(server_config)
27
+ def running?
28
+ @server.try :running?
27
29
  end
28
30
 
29
- def self.stop
30
- ::Protobuf::Rpc::Zmq::Server.stop
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
@@ -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