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