protobuf 2.7.11-java → 2.8.0.beta1-java

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 (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
@@ -2,30 +2,33 @@ module Protobuf
2
2
  module Rpc
3
3
  class SocketRunner
4
4
 
5
- def self.register_signals
6
- # noop
7
- end
5
+ def initialize(options)
6
+ @options = case
7
+ when options.is_a?(OpenStruct) then
8
+ options.marshal_dump
9
+ when options.is_a?(Hash) then
10
+ options
11
+ when options.respond_to?(:to_hash) then
12
+ options.to_hash
13
+ else
14
+ raise "Cannot parser Socket Server - server options"
15
+ end
8
16
 
9
- def self.run(server)
10
- server_config = case
11
- when server.is_a?(OpenStruct) then
12
- server.marshal_dump
13
- when server.is_a?(Hash) then
14
- server
15
- when server.respond_to?(:to_hash) then
16
- server.to_hash
17
- else
18
- raise "Cannot parser Socket Server - server options"
19
- end
17
+ @server = ::Protobuf::Rpc::Socket::Server.new(@options)
18
+ end
20
19
 
21
- yield if block_given?
22
- ::Protobuf::Rpc::Socket::Server.run(server_config)
20
+ def run
21
+ yield if block_given?
22
+ @server.run
23
23
  end
24
24
 
25
- def self.stop
26
- ::Protobuf::Rpc::Socket::Server.stop
25
+ def running?
26
+ @server.running?
27
27
  end
28
28
 
29
+ def stop
30
+ @server.stop
31
+ end
29
32
  end
30
33
  end
31
34
  end
@@ -1,128 +1,138 @@
1
- require 'resolv'
2
- require 'protobuf/rpc/servers/zmq/util'
3
1
 
4
2
  module Protobuf
5
3
  module Rpc
6
4
  module Zmq
7
5
  class Broker
8
6
  include ::Protobuf::Rpc::Zmq::Util
9
- attr_reader :frontend, :backend, :poller, :context, :available_workers, :options, :expected_worker_count
10
-
11
- ##
12
- # Constructor
13
- #
14
- def initialize(options = {})
15
- @available_workers = []
16
- @options = options.dup
17
- @expected_worker_count = @options[:threads]
18
- @context = ::ZMQ::Context.new
19
- @poller = ::ZMQ::Poller.new
20
- setup_backend
21
- end
22
-
23
- ##
24
- # Instance Methods
25
- #
26
- def poll
27
- if frontend.nil?
28
- if local_workers_have_started?
29
- # only open the front end when the workers are done booting
30
- log_info { "Starting frontend socket in broker, all workers ready!" }
31
- setup_frontend
32
- end
33
- else
34
- # Start checking the poller after startup
35
- if available_workers.size > 0
36
- poller.register(frontend, ::ZMQ::POLLIN) if poller.size < 2
37
- else
38
- poller.delete(frontend)
39
- end
40
- end
41
7
 
42
- poller.poll(1000)
43
- poller.readables.each do |socket|
44
- case socket
45
- when backend then
46
- move_to_frontend(socket)
47
- when frontend then
48
- move_to_backend(socket)
8
+ def initialize(server)
9
+ @server = server
10
+
11
+ init_zmq_context
12
+ init_backend_socket
13
+ init_frontend_socket
14
+ init_shutdown_socket
15
+ init_poller
16
+ rescue
17
+ teardown
18
+ raise
19
+ end
20
+
21
+ def join
22
+ @thread.try(:join)
23
+ end
24
+
25
+ def run
26
+ @idle_workers = []
27
+
28
+ catch(:shutdown) do
29
+ while @poller.poll > 0
30
+ @poller.readables.each do |readable|
31
+ case readable
32
+ when @frontend_socket
33
+ process_frontend
34
+ when @backend_socket
35
+ process_backend
36
+ when @shutdown_socket
37
+ throw :shutdown
38
+ end
39
+ end
49
40
  end
50
41
  end
42
+ ensure
43
+ teardown
51
44
  end
52
45
 
53
- def setup_backend
54
- host = options[:host]
55
- port = options[:worker_port]
46
+ def start
47
+ log_debug { sign_message("starting broker") }
56
48
 
57
- zmq_backend = context.socket(::ZMQ::ROUTER)
58
- zmq_error_check(zmq_backend.bind(bind_address(host, port)))
49
+ @thread = Thread.new { self.run }
59
50
 
60
- @backend = zmq_backend
61
- @poller.register(@backend, ::ZMQ::POLLIN)
51
+ self
62
52
  end
63
53
 
64
- def setup_frontend
65
- host = options[:host]
66
- port = options[:port]
67
-
68
- zmq_frontend = context.socket(::ZMQ::ROUTER)
69
- zmq_error_check(zmq_frontend.bind(bind_address(host, port)))
70
-
71
- @frontend = zmq_frontend
72
- @poller.register(@frontend, ::ZMQ::POLLIN)
54
+ def shutdown_uri
55
+ "inproc://#{object_id}"
73
56
  end
74
57
 
75
- def teardown
76
- frontend.try(:close)
77
- backend.try(:close)
78
- context.try(:terminate)
58
+ def signal_shutdown
59
+ socket = @zmq_context.socket(ZMQ::PAIR)
60
+ zmq_error_check(socket.connect(shutdown_uri))
61
+ zmq_error_check(socket.send_string ".")
62
+ zmq_error_check(socket.close)
79
63
  end
80
64
 
81
65
  private
82
66
 
83
- def local_workers_have_started?
84
- @local_workers_have_started ||= available_workers.size >= expected_worker_count
85
- end
67
+ def init_backend_socket
68
+ @backend_socket = @zmq_context.socket(ZMQ::ROUTER)
69
+ zmq_error_check(@backend_socket.bind(@server.backend_uri))
70
+ end
86
71
 
87
- def move_to_backend(socket)
88
- message_array = []
89
- zmq_error_check(socket.recv_strings(message_array))
72
+ def init_frontend_socket
73
+ @frontend_socket = @zmq_context.socket(ZMQ::ROUTER)
74
+ zmq_error_check(@frontend_socket.bind(@server.frontend_uri))
75
+ end
90
76
 
91
- backend_message_set = [
92
- available_workers.shift, # Worker UUID for router
93
- "",
94
- message_array[0], # Client UUID for return value
95
- "",
96
- message_array[2] # Client Message payload (request)
97
- ]
77
+ def init_poller
78
+ @poller = ZMQ::Poller.new
79
+ @poller.register_readable(@frontend_socket)
80
+ @poller.register_readable(@backend_socket)
81
+ @poller.register_readable(@shutdown_socket)
82
+ end
98
83
 
99
- zmq_error_check(backend.send_strings(backend_message_set))
100
- end
84
+ def init_shutdown_socket
85
+ @shutdown_socket = @zmq_context.socket(ZMQ::PAIR)
86
+ zmq_error_check(@shutdown_socket.bind(shutdown_uri))
87
+ end
101
88
 
102
- def move_to_frontend(socket)
103
- message_array = []
104
- zmq_error_check(socket.recv_strings(message_array))
89
+ def init_zmq_context
90
+ @zmq_context = ZMQ::Context.new
91
+ end
105
92
 
106
- # Push UUID of socket on the available workers queue
107
- available_workers << message_array[0]
93
+ def process_backend
94
+ worker, ignore, *frames = read_from_backend
108
95
 
109
- # messages should be [ "uuid of socket", "", "READY_MESSAGE || uuid of client socket"]
110
- if message_array[2] == ::Protobuf::Rpc::Zmq::WORKER_READY_MESSAGE
111
- log_info { "Worker #{available_workers.size} of #{expected_worker_count} ready!" }
112
- else
113
- frontend_message_set = [
114
- message_array[2], # client UUID
115
- "",
116
- message_array[4] # Reply payload
117
- ]
96
+ @idle_workers << worker
118
97
 
119
- zmq_error_check(frontend.send_strings(frontend_message_set))
120
- end
98
+ unless frames == [::Protobuf::Rpc::Zmq::WORKER_READY_MESSAGE]
99
+ write_to_frontend(frames)
100
+ end
101
+ end
102
+
103
+ def process_frontend
104
+ if @idle_workers.any?
105
+ frames = read_from_frontend
106
+ write_to_backend([@idle_workers.shift, ""] + frames)
121
107
  end
108
+ end
109
+
110
+ def read_from_backend
111
+ [].tap do |frames|
112
+ zmq_error_check(@backend_socket.recv_strings(frames))
113
+ end
114
+ end
122
115
 
123
- def bind_address(host, port)
124
- "tcp://#{resolve_ip(host)}:#{port}"
116
+ def read_from_frontend
117
+ [].tap do |frames|
118
+ zmq_error_check(@frontend_socket.recv_strings(frames))
125
119
  end
120
+ end
121
+
122
+ def teardown
123
+ @frontend_socket.try(:close)
124
+ @backend_socket.try(:close)
125
+ @shutdown_socket.try(:close)
126
+ @zmq_context.try(:terminate)
127
+ end
128
+
129
+ def write_to_backend(frames)
130
+ zmq_error_check(@backend_socket.send_strings(frames))
131
+ end
132
+
133
+ def write_to_frontend(frames)
134
+ zmq_error_check(@frontend_socket.send_strings(frames))
135
+ end
126
136
  end
127
137
  end
128
138
  end
@@ -1,6 +1,8 @@
1
- require 'protobuf/rpc/servers/zmq/broker'
2
- require 'protobuf/rpc/servers/zmq/worker'
3
1
  require 'protobuf/rpc/servers/zmq/util'
2
+ require 'protobuf/rpc/servers/zmq/worker'
3
+ require 'protobuf/rpc/servers/zmq/broker'
4
+ require 'protobuf/rpc/dynamic_discovery.pb'
5
+ require 'securerandom'
4
6
 
5
7
  module Protobuf
6
8
  module Rpc
@@ -8,69 +10,287 @@ module Protobuf
8
10
  class Server
9
11
  include ::Protobuf::Rpc::Zmq::Util
10
12
 
11
- ##
12
- # Class Methods
13
- #
14
- def self.run(options = {})
15
- @options = options
13
+ DEFAULT_OPTIONS = {
14
+ :beacon_interval => 5,
15
+ :broadcast_beacons => false
16
+ }
16
17
 
17
- unless options[:workers_only]
18
- log_debug { sign_message("initializing broker") }
19
- @broker = ::Protobuf::Rpc::Zmq::Broker.new(options)
20
- end
18
+ attr_accessor :options
21
19
 
22
- local_worker_threads = options[:threads]
23
- log_debug { sign_message("starting server workers") }
20
+ def initialize(options)
21
+ @options = DEFAULT_OPTIONS.merge(options)
22
+ @workers = []
24
23
 
25
- @running = true
26
- local_worker_threads.times do
27
- self.start_worker
24
+ init_zmq_context
25
+ init_beacon_socket if broadcast_beacons?
26
+ init_shutdown_socket
27
+ rescue
28
+ teardown
29
+ raise
30
+ end
31
+
32
+ def backend_ip
33
+ frontend_ip
34
+ end
35
+
36
+ def backend_port
37
+ options[:worker_port] || frontend_port + 1
38
+ end
39
+
40
+ def backend_uri
41
+ "tcp://#{backend_ip}:#{backend_port}"
42
+ end
43
+
44
+ def beacon_interval
45
+ [options[:beacon_interval].to_i, 1].max
46
+ end
47
+
48
+ def beacon_ip
49
+ unless @beacon_ip
50
+ unless address = options[:beacon_address]
51
+ address = ::Protobuf::Rpc::ServiceDirectory.address
52
+ end
53
+
54
+ @beacon_ip = resolve_ip(address)
28
55
  end
29
56
 
30
- log_debug { sign_message("server started") }
31
- while self.running? do
32
- if options[:workers_only]
33
- sleep 5
34
- Thread.pass
35
- else
36
- @broker.poll
57
+ @beacon_ip
58
+ end
59
+
60
+ def beacon_port
61
+ unless @beacon_port
62
+ unless port = options[:beacon_port]
63
+ port = ::Protobuf::Rpc::ServiceDirectory.port
37
64
  end
65
+
66
+ @beacon_port = port.to_i
67
+ end
68
+
69
+ @beacon_port
70
+ end
71
+
72
+ def beacon_uri
73
+ "udp://#{beacon_ip}:#{beacon_port}"
74
+ end
75
+
76
+ def broadcast_beacons?
77
+ !brokerless? && options[:broadcast_beacons]
78
+ end
79
+
80
+ def broadcast_flatline
81
+ flatline = ::Protobuf::Rpc::DynamicDiscovery::Beacon.new(
82
+ :beacon_type => ::Protobuf::Rpc::DynamicDiscovery::BeaconType::FLATLINE,
83
+ :server => self.to_proto
84
+ )
85
+
86
+ @beacon_socket.send flatline.serialize_to_string, 0
87
+ end
88
+
89
+ def broadcast_heartbeat
90
+ @last_beacon = Time.now.to_i
91
+
92
+ heartbeat = ::Protobuf::Rpc::DynamicDiscovery::Beacon.new(
93
+ :beacon_type => ::Protobuf::Rpc::DynamicDiscovery::BeaconType::HEARTBEAT,
94
+ :server => self.to_proto
95
+ )
96
+
97
+ @beacon_socket.send(heartbeat.serialize_to_string, 0)
98
+
99
+ log_debug { sign_message("sent heartbeat to #{beacon_uri}") }
100
+ end
101
+
102
+ def broadcast_heartbeat?
103
+ Time.now.to_i >= next_beacon && broadcast_beacons?
104
+ end
105
+
106
+ def brokerless?
107
+ !!options[:workers_only]
108
+ end
109
+
110
+ def frontend_ip
111
+ @frontend_ip ||= resolve_ip(options[:host])
112
+ end
113
+
114
+ def frontend_port
115
+ options[:port]
116
+ end
117
+
118
+ def frontend_uri
119
+ "tcp://#{frontend_ip}:#{frontend_port}"
120
+ end
121
+
122
+ def maintenance_timeout
123
+ 1_000 * (next_maintenance - Time.now.to_i)
124
+ end
125
+
126
+ def next_maintenance
127
+ cycles = [next_reaping]
128
+ cycles << next_beacon if broadcast_beacons?
129
+
130
+ cycles.min
131
+ end
132
+
133
+ def minimum_timeout
134
+ 100
135
+ end
136
+
137
+ def next_beacon
138
+ if @last_beacon.nil?
139
+ 0
140
+ else
141
+ @last_beacon + beacon_interval
38
142
  end
143
+ end
144
+
145
+ def next_reaping
146
+ if @last_reaping.nil?
147
+ 0
148
+ else
149
+ @last_reaping + reaping_interval
150
+ end
151
+ end
152
+
153
+ def reap_dead_workers
154
+ @last_reaping = Time.now.to_i
155
+
156
+ @workers.keep_if do |worker|
157
+ worker.alive? or worker.join && false
158
+ end
159
+ end
160
+
161
+ def reap_dead_workers?
162
+ Time.now.to_i >= next_reaping
163
+ end
164
+
165
+ def reaping_interval
166
+ 5
167
+ end
168
+
169
+ def run
170
+ @running = true
171
+
172
+ start_broker unless brokerless?
173
+ start_missing_workers
174
+ wait_for_shutdown_signal
175
+ broadcast_flatline if broadcast_beacons?
176
+ stop_workers
177
+ stop_broker unless brokerless?
39
178
  ensure
40
- @broker.teardown if @broker
179
+ @running = false
180
+ teardown
41
181
  end
42
182
 
43
- def self.running?
183
+ def running?
44
184
  !!@running
45
185
  end
46
186
 
47
- def self.start_worker
48
- @threads << Thread.new(@options) { |options|
49
- begin
50
- ::Protobuf::Rpc::Zmq::Worker.new(options).run
51
- rescue => e
52
- message = "Worker Failed, spawning new worker: #{e.inspect}\n #{e.backtrace.join($/)}"
53
- $stderr.puts message
54
- log_error { message }
187
+ def shutdown_uri
188
+ "inproc://#{object_id}"
189
+ end
55
190
 
56
- retry if ::Protobuf::Rpc::Zmq::Server.running?
57
- end
58
- }
191
+ def signal_shutdown
192
+ socket = @zmq_context.socket ZMQ::PAIR
193
+ zmq_error_check(socket.connect shutdown_uri)
194
+ zmq_error_check(socket.send_string ".")
195
+ zmq_error_check(socket.close)
59
196
  end
60
197
 
61
- def self.stop
62
- @running = false
198
+ def start_broker
199
+ @broker = ::Protobuf::Rpc::Zmq::Broker.new(self).start
200
+ end
201
+
202
+ def start_missing_workers
203
+ missing_workers = total_workers - @workers.size
204
+
205
+ if missing_workers > 0
206
+ missing_workers.times { start_worker }
207
+ log_debug { sign_message("#{total_workers} workers started") }
208
+ end
209
+ end
63
210
 
64
- @threads.each do |t|
65
- t.join(5) || t.kill
211
+ def start_worker
212
+ @workers << ::Protobuf::Rpc::Zmq::Worker.new(self).start
213
+ end
214
+
215
+ def stop
216
+ signal_shutdown
217
+ end
218
+
219
+ def stop_broker
220
+ @broker.signal_shutdown
221
+ @broker.join
222
+ end
223
+
224
+ def stop_workers
225
+ @workers.each(&:signal_shutdown)
226
+ Thread.pass until reap_dead_workers.empty?
227
+ end
228
+
229
+ def teardown
230
+ @shutdown_socket.try(:close)
231
+ @beacon_socket.try(:close)
232
+ @zmq_context.try(:terminate)
233
+ @last_reaping = @last_beacon = @timeout = nil
234
+ end
235
+
236
+ def total_workers
237
+ @total_workers ||= [@options[:threads].to_i, 1].max
238
+ end
239
+
240
+ def timeout
241
+ if @timeout.nil?
242
+ @timeout = 0
243
+ else
244
+ @timeout = [minimum_timeout, maintenance_timeout].max
245
+ end
246
+ end
247
+
248
+ def to_proto
249
+ @proto ||= ::Protobuf::Rpc::DynamicDiscovery::Server.new(
250
+ :uuid => uuid,
251
+ :address => frontend_ip,
252
+ :port => frontend_port.to_s,
253
+ :ttl => (beacon_interval * 1.5).ceil,
254
+ :services => ::Protobuf::Rpc::Service.implemented_services
255
+ )
256
+ end
257
+
258
+ def uuid
259
+ @uuid ||= SecureRandom.uuid
260
+ end
261
+
262
+ def wait_for_shutdown_signal
263
+ poller = ZMQ::Poller.new
264
+ poller.register_readable(@shutdown_socket)
265
+
266
+ # If the poller returns 1, a shutdown signal has been received.
267
+ # If the poller returns -1, something went wrong.
268
+ while poller.poll(timeout) === 0
269
+ if reap_dead_workers?
270
+ reap_dead_workers
271
+ start_missing_workers
272
+ end
273
+
274
+ broadcast_heartbeat if broadcast_heartbeat?
66
275
  end
67
276
  end
68
277
 
69
- def self.threads
70
- @threads
278
+ private
279
+
280
+ def init_beacon_socket
281
+ @beacon_socket = UDPSocket.new
282
+ @beacon_socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BROADCAST, true)
283
+ @beacon_socket.connect(beacon_ip, beacon_port)
284
+ end
285
+
286
+ def init_shutdown_socket
287
+ @shutdown_socket = @zmq_context.socket(ZMQ::PAIR)
288
+ zmq_error_check(@shutdown_socket.bind shutdown_uri)
71
289
  end
72
290
 
73
- @threads ||= []
291
+ def init_zmq_context
292
+ @zmq_context = ZMQ::Context.new
293
+ end
74
294
  end
75
295
  end
76
296
  end