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