protobuf-cucumber 3.10.4
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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rubocop.yml +70 -0
- data/.rubocop_todo.yml +145 -0
- data/.travis.yml +40 -0
- data/.yardopts +5 -0
- data/CHANGES.md +344 -0
- data/CONTRIBUTING.md +16 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/Rakefile +64 -0
- data/bin/protoc-gen-ruby +22 -0
- data/bin/rpc_server +5 -0
- data/install-protobuf.sh +28 -0
- data/lib/protobuf.rb +129 -0
- data/lib/protobuf/cli.rb +257 -0
- data/lib/protobuf/code_generator.rb +120 -0
- data/lib/protobuf/decoder.rb +28 -0
- data/lib/protobuf/deprecation.rb +117 -0
- data/lib/protobuf/descriptors.rb +3 -0
- data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +62 -0
- data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +301 -0
- data/lib/protobuf/encoder.rb +11 -0
- data/lib/protobuf/enum.rb +365 -0
- data/lib/protobuf/exceptions.rb +9 -0
- data/lib/protobuf/field.rb +74 -0
- data/lib/protobuf/field/base_field.rb +380 -0
- data/lib/protobuf/field/base_field_object_definitions.rb +504 -0
- data/lib/protobuf/field/bool_field.rb +64 -0
- data/lib/protobuf/field/bytes_field.rb +78 -0
- data/lib/protobuf/field/double_field.rb +25 -0
- data/lib/protobuf/field/enum_field.rb +61 -0
- data/lib/protobuf/field/field_array.rb +104 -0
- data/lib/protobuf/field/field_hash.rb +122 -0
- data/lib/protobuf/field/fixed32_field.rb +25 -0
- data/lib/protobuf/field/fixed64_field.rb +28 -0
- data/lib/protobuf/field/float_field.rb +43 -0
- data/lib/protobuf/field/int32_field.rb +21 -0
- data/lib/protobuf/field/int64_field.rb +34 -0
- data/lib/protobuf/field/integer_field.rb +23 -0
- data/lib/protobuf/field/message_field.rb +51 -0
- data/lib/protobuf/field/sfixed32_field.rb +27 -0
- data/lib/protobuf/field/sfixed64_field.rb +28 -0
- data/lib/protobuf/field/signed_integer_field.rb +29 -0
- data/lib/protobuf/field/sint32_field.rb +21 -0
- data/lib/protobuf/field/sint64_field.rb +21 -0
- data/lib/protobuf/field/string_field.rb +51 -0
- data/lib/protobuf/field/uint32_field.rb +21 -0
- data/lib/protobuf/field/uint64_field.rb +21 -0
- data/lib/protobuf/field/varint_field.rb +77 -0
- data/lib/protobuf/generators/base.rb +85 -0
- data/lib/protobuf/generators/enum_generator.rb +39 -0
- data/lib/protobuf/generators/extension_generator.rb +27 -0
- data/lib/protobuf/generators/field_generator.rb +193 -0
- data/lib/protobuf/generators/file_generator.rb +262 -0
- data/lib/protobuf/generators/group_generator.rb +122 -0
- data/lib/protobuf/generators/message_generator.rb +104 -0
- data/lib/protobuf/generators/option_generator.rb +17 -0
- data/lib/protobuf/generators/printable.rb +160 -0
- data/lib/protobuf/generators/service_generator.rb +50 -0
- data/lib/protobuf/lifecycle.rb +33 -0
- data/lib/protobuf/logging.rb +39 -0
- data/lib/protobuf/message.rb +260 -0
- data/lib/protobuf/message/fields.rb +233 -0
- data/lib/protobuf/message/serialization.rb +85 -0
- data/lib/protobuf/optionable.rb +70 -0
- data/lib/protobuf/rpc/buffer.rb +78 -0
- data/lib/protobuf/rpc/client.rb +140 -0
- data/lib/protobuf/rpc/connectors/base.rb +221 -0
- data/lib/protobuf/rpc/connectors/ping.rb +89 -0
- data/lib/protobuf/rpc/connectors/socket.rb +78 -0
- data/lib/protobuf/rpc/connectors/zmq.rb +319 -0
- data/lib/protobuf/rpc/dynamic_discovery.pb.rb +50 -0
- data/lib/protobuf/rpc/env.rb +60 -0
- data/lib/protobuf/rpc/error.rb +28 -0
- data/lib/protobuf/rpc/error/client_error.rb +31 -0
- data/lib/protobuf/rpc/error/server_error.rb +43 -0
- data/lib/protobuf/rpc/middleware.rb +25 -0
- data/lib/protobuf/rpc/middleware/exception_handler.rb +40 -0
- data/lib/protobuf/rpc/middleware/logger.rb +95 -0
- data/lib/protobuf/rpc/middleware/request_decoder.rb +79 -0
- data/lib/protobuf/rpc/middleware/response_encoder.rb +83 -0
- data/lib/protobuf/rpc/middleware/runner.rb +18 -0
- data/lib/protobuf/rpc/rpc.pb.rb +64 -0
- data/lib/protobuf/rpc/rpc_method.rb +16 -0
- data/lib/protobuf/rpc/server.rb +39 -0
- data/lib/protobuf/rpc/servers/socket/server.rb +121 -0
- data/lib/protobuf/rpc/servers/socket/worker.rb +56 -0
- data/lib/protobuf/rpc/servers/socket_runner.rb +46 -0
- data/lib/protobuf/rpc/servers/zmq/broker.rb +194 -0
- data/lib/protobuf/rpc/servers/zmq/server.rb +321 -0
- data/lib/protobuf/rpc/servers/zmq/util.rb +48 -0
- data/lib/protobuf/rpc/servers/zmq/worker.rb +105 -0
- data/lib/protobuf/rpc/servers/zmq_runner.rb +70 -0
- data/lib/protobuf/rpc/service.rb +172 -0
- data/lib/protobuf/rpc/service_directory.rb +261 -0
- data/lib/protobuf/rpc/service_dispatcher.rb +45 -0
- data/lib/protobuf/rpc/service_filters.rb +250 -0
- data/lib/protobuf/rpc/stat.rb +119 -0
- data/lib/protobuf/socket.rb +21 -0
- data/lib/protobuf/tasks.rb +1 -0
- data/lib/protobuf/tasks/compile.rake +80 -0
- data/lib/protobuf/varint.rb +20 -0
- data/lib/protobuf/varint_pure.rb +31 -0
- data/lib/protobuf/version.rb +3 -0
- data/lib/protobuf/wire_type.rb +10 -0
- data/lib/protobuf/zmq.rb +21 -0
- data/profile.html +5103 -0
- data/proto/dynamic_discovery.proto +44 -0
- data/proto/google/protobuf/compiler/plugin.proto +147 -0
- data/proto/google/protobuf/descriptor.proto +779 -0
- data/proto/rpc.proto +69 -0
- data/protobuf-cucumber.gemspec +57 -0
- data/spec/benchmark/tasks.rb +143 -0
- data/spec/bin/protoc-gen-ruby_spec.rb +23 -0
- data/spec/encoding/all_types_spec.rb +103 -0
- data/spec/encoding/extreme_values_spec.rb +0 -0
- data/spec/functional/class_inheritance_spec.rb +52 -0
- data/spec/functional/code_generator_spec.rb +58 -0
- data/spec/functional/socket_server_spec.rb +59 -0
- data/spec/functional/zmq_server_spec.rb +105 -0
- data/spec/lib/protobuf/cli_spec.rb +317 -0
- data/spec/lib/protobuf/code_generator_spec.rb +87 -0
- data/spec/lib/protobuf/enum_spec.rb +307 -0
- data/spec/lib/protobuf/field/bool_field_spec.rb +91 -0
- data/spec/lib/protobuf/field/double_field_spec.rb +9 -0
- data/spec/lib/protobuf/field/enum_field_spec.rb +44 -0
- data/spec/lib/protobuf/field/field_array_spec.rb +105 -0
- data/spec/lib/protobuf/field/field_hash_spec.rb +168 -0
- data/spec/lib/protobuf/field/fixed32_field_spec.rb +7 -0
- data/spec/lib/protobuf/field/fixed64_field_spec.rb +7 -0
- data/spec/lib/protobuf/field/float_field_spec.rb +90 -0
- data/spec/lib/protobuf/field/int32_field_spec.rb +120 -0
- data/spec/lib/protobuf/field/int64_field_spec.rb +7 -0
- data/spec/lib/protobuf/field/message_field_spec.rb +132 -0
- data/spec/lib/protobuf/field/sfixed32_field_spec.rb +9 -0
- data/spec/lib/protobuf/field/sfixed64_field_spec.rb +9 -0
- data/spec/lib/protobuf/field/sint32_field_spec.rb +9 -0
- data/spec/lib/protobuf/field/sint64_field_spec.rb +9 -0
- data/spec/lib/protobuf/field/string_field_spec.rb +79 -0
- data/spec/lib/protobuf/field/uint32_field_spec.rb +7 -0
- data/spec/lib/protobuf/field/uint64_field_spec.rb +7 -0
- data/spec/lib/protobuf/field_spec.rb +192 -0
- data/spec/lib/protobuf/generators/base_spec.rb +154 -0
- data/spec/lib/protobuf/generators/enum_generator_spec.rb +82 -0
- data/spec/lib/protobuf/generators/extension_generator_spec.rb +42 -0
- data/spec/lib/protobuf/generators/field_generator_spec.rb +197 -0
- data/spec/lib/protobuf/generators/file_generator_spec.rb +119 -0
- data/spec/lib/protobuf/generators/message_generator_spec.rb +0 -0
- data/spec/lib/protobuf/generators/service_generator_spec.rb +99 -0
- data/spec/lib/protobuf/lifecycle_spec.rb +94 -0
- data/spec/lib/protobuf/message_spec.rb +944 -0
- data/spec/lib/protobuf/optionable_spec.rb +265 -0
- data/spec/lib/protobuf/rpc/client_spec.rb +66 -0
- data/spec/lib/protobuf/rpc/connectors/base_spec.rb +226 -0
- data/spec/lib/protobuf/rpc/connectors/ping_spec.rb +69 -0
- data/spec/lib/protobuf/rpc/connectors/socket_spec.rb +34 -0
- data/spec/lib/protobuf/rpc/connectors/zmq_spec.rb +110 -0
- data/spec/lib/protobuf/rpc/middleware/exception_handler_spec.rb +62 -0
- data/spec/lib/protobuf/rpc/middleware/logger_spec.rb +49 -0
- data/spec/lib/protobuf/rpc/middleware/request_decoder_spec.rb +115 -0
- data/spec/lib/protobuf/rpc/middleware/response_encoder_spec.rb +91 -0
- data/spec/lib/protobuf/rpc/servers/socket_server_spec.rb +38 -0
- data/spec/lib/protobuf/rpc/servers/zmq/server_spec.rb +43 -0
- data/spec/lib/protobuf/rpc/servers/zmq/util_spec.rb +55 -0
- data/spec/lib/protobuf/rpc/servers/zmq/worker_spec.rb +35 -0
- data/spec/lib/protobuf/rpc/service_directory_spec.rb +293 -0
- data/spec/lib/protobuf/rpc/service_dispatcher_spec.rb +35 -0
- data/spec/lib/protobuf/rpc/service_filters_spec.rb +517 -0
- data/spec/lib/protobuf/rpc/service_spec.rb +162 -0
- data/spec/lib/protobuf/rpc/stat_spec.rb +101 -0
- data/spec/lib/protobuf/varint_spec.rb +29 -0
- data/spec/lib/protobuf_spec.rb +105 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/support/all.rb +6 -0
- data/spec/support/packed_field.rb +23 -0
- data/spec/support/protos/all_types.data.bin +0 -0
- data/spec/support/protos/all_types.data.txt +119 -0
- data/spec/support/protos/enum.pb.rb +63 -0
- data/spec/support/protos/enum.proto +37 -0
- data/spec/support/protos/extreme_values.data.bin +0 -0
- data/spec/support/protos/google_unittest.bin +0 -0
- data/spec/support/protos/google_unittest.pb.rb +798 -0
- data/spec/support/protos/google_unittest.proto +884 -0
- data/spec/support/protos/google_unittest_custom_options.bin +0 -0
- data/spec/support/protos/google_unittest_custom_options.pb.rb +361 -0
- data/spec/support/protos/google_unittest_custom_options.proto +424 -0
- data/spec/support/protos/google_unittest_import.pb.rb +55 -0
- data/spec/support/protos/google_unittest_import.proto +73 -0
- data/spec/support/protos/google_unittest_import_public.pb.rb +31 -0
- data/spec/support/protos/google_unittest_import_public.proto +41 -0
- data/spec/support/protos/map-test.bin +157 -0
- data/spec/support/protos/map-test.pb.rb +85 -0
- data/spec/support/protos/map-test.proto +68 -0
- data/spec/support/protos/multi_field_extensions.pb.rb +59 -0
- data/spec/support/protos/multi_field_extensions.proto +35 -0
- data/spec/support/protos/resource.pb.rb +172 -0
- data/spec/support/protos/resource.proto +137 -0
- data/spec/support/resource_service.rb +23 -0
- data/spec/support/server.rb +65 -0
- data/spec/support/test_app_file.rb +2 -0
- data/varint_prof.rb +82 -0
- metadata +579 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
|
3
|
+
module Protobuf
|
4
|
+
module Rpc
|
5
|
+
module Zmq
|
6
|
+
|
7
|
+
ADDRESS_MATCH = /\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/
|
8
|
+
WORKER_READY_MESSAGE = "\1".freeze
|
9
|
+
CHECK_AVAILABLE_MESSAGE = "\3".freeze
|
10
|
+
NO_WORKERS_AVAILABLE = "\4".freeze
|
11
|
+
WORKERS_AVAILABLE = "\5".freeze
|
12
|
+
EMPTY_STRING = "".freeze
|
13
|
+
|
14
|
+
module Util
|
15
|
+
include ::Protobuf::Logging
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.extend(::Protobuf::Rpc::Zmq::Util)
|
19
|
+
end
|
20
|
+
|
21
|
+
def zmq_error_check(return_code, source = nil)
|
22
|
+
return if ::ZMQ::Util.resultcode_ok?(return_code)
|
23
|
+
|
24
|
+
fail <<-ERROR
|
25
|
+
Last ZMQ API call #{source ? "to #{source}" : ''} failed with "#{::ZMQ::Util.error_string}".
|
26
|
+
|
27
|
+
#{caller(1).join($INPUT_RECORD_SEPARATOR)}
|
28
|
+
ERROR
|
29
|
+
end
|
30
|
+
|
31
|
+
def log_signature
|
32
|
+
unless @_log_signature
|
33
|
+
name = (self.class == Class ? self.name : self.class.name)
|
34
|
+
@_log_signature = "[server-#{name}-#{object_id}]"
|
35
|
+
end
|
36
|
+
|
37
|
+
@_log_signature
|
38
|
+
end
|
39
|
+
|
40
|
+
def resolve_ip(hostname)
|
41
|
+
::Resolv.getaddresses(hostname).find do |address|
|
42
|
+
address =~ ADDRESS_MATCH
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'protobuf/rpc/server'
|
2
|
+
require 'protobuf/rpc/servers/zmq/util'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
module Protobuf
|
6
|
+
module Rpc
|
7
|
+
module Zmq
|
8
|
+
class Worker
|
9
|
+
include ::Protobuf::Rpc::Server
|
10
|
+
include ::Protobuf::Rpc::Zmq::Util
|
11
|
+
|
12
|
+
##
|
13
|
+
# Constructor
|
14
|
+
#
|
15
|
+
def initialize(server, broker)
|
16
|
+
@server = server
|
17
|
+
@broker = broker
|
18
|
+
|
19
|
+
init_zmq_context
|
20
|
+
init_backend_socket
|
21
|
+
rescue
|
22
|
+
teardown
|
23
|
+
raise
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Instance Methods
|
28
|
+
#
|
29
|
+
def process_request
|
30
|
+
client_address, _, data = read_from_backend
|
31
|
+
return unless data
|
32
|
+
|
33
|
+
gc_pause do
|
34
|
+
encoded_response = handle_request(data)
|
35
|
+
write_to_backend([client_address, ::Protobuf::Rpc::Zmq::EMPTY_STRING, encoded_response])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
poller = ::ZMQ::Poller.new
|
41
|
+
poller.register_readable(@backend_socket)
|
42
|
+
poller.register_readable(@shutdown_socket)
|
43
|
+
|
44
|
+
# Send request to broker telling it we are ready
|
45
|
+
write_to_backend([::Protobuf::Rpc::Zmq::WORKER_READY_MESSAGE])
|
46
|
+
|
47
|
+
loop do
|
48
|
+
rc = poller.poll(500)
|
49
|
+
|
50
|
+
if rc == 0 && !running? # rubocop:disable Style/GuardClause
|
51
|
+
break # The server was shutdown and no requests are pending
|
52
|
+
elsif rc == -1
|
53
|
+
break # Something went wrong
|
54
|
+
elsif rc > 0
|
55
|
+
::Thread.current[:busy] = true
|
56
|
+
process_request
|
57
|
+
::Thread.current[:busy] = false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
ensure
|
61
|
+
teardown
|
62
|
+
end
|
63
|
+
|
64
|
+
def running?
|
65
|
+
@broker.running? && @server.running?
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def init_zmq_context
|
71
|
+
@zmq_context =
|
72
|
+
if inproc?
|
73
|
+
@server.zmq_context
|
74
|
+
else
|
75
|
+
ZMQ::Context.new
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def init_backend_socket
|
80
|
+
@backend_socket = @zmq_context.socket(ZMQ::REQ)
|
81
|
+
zmq_error_check(@backend_socket.connect(@server.backend_uri))
|
82
|
+
end
|
83
|
+
|
84
|
+
def inproc?
|
85
|
+
!!@server.try(:inproc?)
|
86
|
+
end
|
87
|
+
|
88
|
+
def read_from_backend
|
89
|
+
frames = []
|
90
|
+
zmq_error_check(@backend_socket.recv_strings(frames))
|
91
|
+
frames
|
92
|
+
end
|
93
|
+
|
94
|
+
def teardown
|
95
|
+
@backend_socket.try(:close)
|
96
|
+
@zmq_context.try(:terminate) unless inproc?
|
97
|
+
end
|
98
|
+
|
99
|
+
def write_to_backend(frames)
|
100
|
+
zmq_error_check(@backend_socket.send_strings(frames))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Protobuf
|
5
|
+
module Rpc
|
6
|
+
class ZmqRunner
|
7
|
+
include ::Protobuf::Logging
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@options = case
|
11
|
+
when options.is_a?(OpenStruct) then
|
12
|
+
options.marshal_dump
|
13
|
+
when options.respond_to?(:to_hash) then
|
14
|
+
options.to_hash.symbolize_keys
|
15
|
+
else
|
16
|
+
fail "Cannot parser Zmq Server - server options"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
@server = ::Protobuf::Rpc::Zmq::Server.new(@options)
|
22
|
+
register_signals
|
23
|
+
@server.run do
|
24
|
+
yield if block_given?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def running?
|
29
|
+
@server.try :running?
|
30
|
+
end
|
31
|
+
|
32
|
+
def stop
|
33
|
+
@server.try :stop
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def register_signals
|
39
|
+
trap(:TRAP) do
|
40
|
+
::Thread.list.each do |thread|
|
41
|
+
logger.info do
|
42
|
+
<<-THREAD_TRACE
|
43
|
+
#{thread.inspect}:
|
44
|
+
#{thread.backtrace.try(:join, $INPUT_RECORD_SEPARATOR)}"
|
45
|
+
THREAD_TRACE
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
trap(:TTIN) do
|
51
|
+
@server.add_worker
|
52
|
+
logger.info { "Increased worker size to: #{@server.total_workers}" }
|
53
|
+
end
|
54
|
+
|
55
|
+
trap(:TTOU) do
|
56
|
+
logger.info { "Current worker size: #{@server.workers.size}" }
|
57
|
+
logger.info { "Current busy worker size: #{@server.busy_worker_count}" }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module Protobuf
|
65
|
+
module Rpc
|
66
|
+
module Servers # bad file namespacing
|
67
|
+
ZmqRunner = ::Protobuf::Rpc::ZmqRunner
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'protobuf/logging'
|
2
|
+
require 'protobuf/message'
|
3
|
+
require 'protobuf/rpc/client'
|
4
|
+
require 'protobuf/rpc/error'
|
5
|
+
require 'protobuf/rpc/rpc_method'
|
6
|
+
require 'protobuf/rpc/service_filters'
|
7
|
+
|
8
|
+
module Protobuf
|
9
|
+
module Rpc
|
10
|
+
# Object to encapsulate the request/response types for a given service method
|
11
|
+
|
12
|
+
class Service
|
13
|
+
include ::Protobuf::Logging
|
14
|
+
include ::Protobuf::Rpc::ServiceFilters
|
15
|
+
::Protobuf::Optionable.inject(self) { ::Google::Protobuf::ServiceOptions }
|
16
|
+
|
17
|
+
DEFAULT_HOST = '127.0.0.1'.freeze
|
18
|
+
DEFAULT_PORT = 9399
|
19
|
+
|
20
|
+
attr_reader :env, :request
|
21
|
+
|
22
|
+
##
|
23
|
+
# Constructor!
|
24
|
+
#
|
25
|
+
# Initialize a service with the rpc endpoint name and the bytes
|
26
|
+
# for the request.
|
27
|
+
def initialize(env)
|
28
|
+
@env = env.dup # Dup the env so it doesn't change out from under us
|
29
|
+
@request = env.request
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Class Methods
|
34
|
+
#
|
35
|
+
# Create a new client for the given service.
|
36
|
+
# See Client#initialize and ClientConnection::DEFAULT_OPTIONS
|
37
|
+
# for all available options.
|
38
|
+
#
|
39
|
+
def self.client(options = {})
|
40
|
+
::Protobuf::Rpc::Client.new({ :service => self,
|
41
|
+
:host => host,
|
42
|
+
:port => port }.merge(options))
|
43
|
+
end
|
44
|
+
|
45
|
+
# Allows service-level configuration of location.
|
46
|
+
# Useful for system-startup configuration of a service
|
47
|
+
# so that any Clients using the Service.client sugar
|
48
|
+
# will not have to configure the location each time.
|
49
|
+
#
|
50
|
+
def self.configure(config = {})
|
51
|
+
self.host = config[:host] if config.key?(:host)
|
52
|
+
self.port = config[:port] if config.key?(:port)
|
53
|
+
end
|
54
|
+
|
55
|
+
# The host location of the service.
|
56
|
+
#
|
57
|
+
def self.host
|
58
|
+
@host ||= DEFAULT_HOST
|
59
|
+
end
|
60
|
+
|
61
|
+
# The host location setter.
|
62
|
+
#
|
63
|
+
class << self
|
64
|
+
attr_writer :host
|
65
|
+
end
|
66
|
+
|
67
|
+
# An array of defined service classes that contain implementation
|
68
|
+
# code
|
69
|
+
def self.implemented_services
|
70
|
+
classes = (subclasses || []).select do |subclass|
|
71
|
+
subclass.rpcs.any? do |(name, _)|
|
72
|
+
subclass.method_defined? name
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
classes.map(&:name)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Shorthand call to configure, passing a string formatted as hostname:port
|
80
|
+
# e.g. 127.0.0.1:9933
|
81
|
+
# e.g. localhost:0
|
82
|
+
#
|
83
|
+
def self.located_at(location)
|
84
|
+
return if location.nil? || location.downcase.strip !~ /.+:\d+/
|
85
|
+
host, port = location.downcase.strip.split ':'
|
86
|
+
configure(:host => host, :port => port.to_i)
|
87
|
+
end
|
88
|
+
|
89
|
+
# The port of the service on the destination server.
|
90
|
+
#
|
91
|
+
def self.port
|
92
|
+
@port ||= DEFAULT_PORT
|
93
|
+
end
|
94
|
+
|
95
|
+
# The port location setter.
|
96
|
+
#
|
97
|
+
class << self
|
98
|
+
attr_writer :port
|
99
|
+
end
|
100
|
+
|
101
|
+
# Define an rpc method with the given request and response types.
|
102
|
+
# This methods is only used by the generated service definitions
|
103
|
+
# and not useful for user code.
|
104
|
+
#
|
105
|
+
def self.rpc(method, request_type, response_type, &options_block)
|
106
|
+
rpcs[method] = RpcMethod.new(method, request_type, response_type, &options_block)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Hash containing the set of methods defined via `rpc`.
|
110
|
+
#
|
111
|
+
def self.rpcs
|
112
|
+
@rpcs ||= {}
|
113
|
+
end
|
114
|
+
|
115
|
+
# Check if the given method name is a known rpc endpoint.
|
116
|
+
#
|
117
|
+
def self.rpc_method?(name)
|
118
|
+
rpcs.key?(name)
|
119
|
+
end
|
120
|
+
|
121
|
+
def call(method_name)
|
122
|
+
run_filters(method_name)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Response object for this rpc cycle. Not assignable.
|
126
|
+
#
|
127
|
+
def response
|
128
|
+
@response ||= response_type.new
|
129
|
+
end
|
130
|
+
|
131
|
+
# Convenience method to get back to class method.
|
132
|
+
#
|
133
|
+
def rpc_method?(name)
|
134
|
+
self.class.rpc_method?(name)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Convenience method to get back to class rpcs hash.
|
138
|
+
#
|
139
|
+
def rpcs
|
140
|
+
self.class.rpcs
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def request_type
|
146
|
+
@request_type ||= env.request_type
|
147
|
+
end
|
148
|
+
|
149
|
+
# Sugar to make an rpc method feel like a controller method.
|
150
|
+
# If this method is not called, the response will be the memoized
|
151
|
+
# object returned by the response reader.
|
152
|
+
#
|
153
|
+
def respond_with(candidate)
|
154
|
+
@response = candidate
|
155
|
+
end
|
156
|
+
alias :return_from_whence_you_came respond_with
|
157
|
+
|
158
|
+
def response_type
|
159
|
+
@response_type ||= env.response_type
|
160
|
+
end
|
161
|
+
|
162
|
+
# Automatically fail a service method.
|
163
|
+
#
|
164
|
+
def rpc_failed(message)
|
165
|
+
message = message.message if message.respond_to?(:message)
|
166
|
+
fail RpcFailed, message
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
ActiveSupport.run_load_hooks(:protobuf_rpc_service, Service)
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'singleton'
|
3
|
+
require 'socket'
|
4
|
+
require 'set'
|
5
|
+
require 'thread'
|
6
|
+
require 'timeout'
|
7
|
+
|
8
|
+
require 'protobuf/rpc/dynamic_discovery.pb'
|
9
|
+
|
10
|
+
module Protobuf
|
11
|
+
module Rpc
|
12
|
+
def self.service_directory
|
13
|
+
@service_directory ||= ::Protobuf::Rpc::ServiceDirectory.instance
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.service_directory=(directory)
|
17
|
+
@service_directory = directory
|
18
|
+
end
|
19
|
+
|
20
|
+
class ServiceDirectory
|
21
|
+
include ::Singleton
|
22
|
+
include ::Protobuf::Logging
|
23
|
+
|
24
|
+
DEFAULT_ADDRESS = '0.0.0.0'.freeze
|
25
|
+
DEFAULT_PORT = 53000
|
26
|
+
DEFAULT_TIMEOUT = 1
|
27
|
+
|
28
|
+
class Listing < SimpleDelegator
|
29
|
+
attr_reader :expires_at
|
30
|
+
|
31
|
+
def initialize(server)
|
32
|
+
update(server)
|
33
|
+
end
|
34
|
+
|
35
|
+
def current?
|
36
|
+
!expired?
|
37
|
+
end
|
38
|
+
|
39
|
+
def eql?(other)
|
40
|
+
uuid.eql?(other.uuid)
|
41
|
+
end
|
42
|
+
|
43
|
+
def expired?
|
44
|
+
Time.now.to_i >= @expires_at
|
45
|
+
end
|
46
|
+
|
47
|
+
def hash
|
48
|
+
uuid.hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def ttl
|
52
|
+
[super.to_i, 1].max
|
53
|
+
end
|
54
|
+
|
55
|
+
def update(server)
|
56
|
+
__setobj__(server)
|
57
|
+
@expires_at = Time.now.to_i + ttl
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Class Methods
|
62
|
+
#
|
63
|
+
class << self
|
64
|
+
attr_writer :address, :port
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.address
|
68
|
+
@address ||= DEFAULT_ADDRESS
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.port
|
72
|
+
@port ||= DEFAULT_PORT
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.start
|
76
|
+
yield(self) if block_given?
|
77
|
+
instance.start
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.stop
|
81
|
+
instance.stop
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Instance Methods
|
86
|
+
#
|
87
|
+
def initialize
|
88
|
+
reset
|
89
|
+
end
|
90
|
+
|
91
|
+
def all_listings_for(service)
|
92
|
+
if running? && @listings_by_service.key?(service.to_s)
|
93
|
+
start_listener_thread if listener_dead?
|
94
|
+
@listings_by_service[service.to_s].entries.shuffle
|
95
|
+
else
|
96
|
+
[]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def each_listing(&block)
|
101
|
+
start_listener_thread if listener_dead?
|
102
|
+
@listings_by_uuid.each_value(&block)
|
103
|
+
end
|
104
|
+
|
105
|
+
def lookup(service)
|
106
|
+
return unless running?
|
107
|
+
start_listener_thread if listener_dead?
|
108
|
+
return unless @listings_by_service.key?(service.to_s)
|
109
|
+
@listings_by_service[service.to_s].entries.sample
|
110
|
+
end
|
111
|
+
|
112
|
+
def listener_dead?
|
113
|
+
@thread.nil? || !@thread.alive?
|
114
|
+
end
|
115
|
+
|
116
|
+
def restart
|
117
|
+
stop
|
118
|
+
start
|
119
|
+
end
|
120
|
+
|
121
|
+
def running?
|
122
|
+
!!@running
|
123
|
+
end
|
124
|
+
|
125
|
+
def start
|
126
|
+
unless running?
|
127
|
+
init_socket
|
128
|
+
logger.info { sign_message("listening to udp://#{self.class.address}:#{self.class.port}") }
|
129
|
+
@running = true
|
130
|
+
end
|
131
|
+
|
132
|
+
start_listener_thread if listener_dead?
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
def start_listener_thread
|
137
|
+
return if @thread.try(:alive?)
|
138
|
+
@thread = Thread.new { send(:run) }
|
139
|
+
end
|
140
|
+
|
141
|
+
def stop
|
142
|
+
logger.info { sign_message("Stopping directory") }
|
143
|
+
|
144
|
+
@running = false
|
145
|
+
@thread.try(:kill).try(:join)
|
146
|
+
@socket.try(:close)
|
147
|
+
|
148
|
+
reset
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def add_or_update_listing(uuid, server)
|
154
|
+
listing = @listings_by_uuid[uuid]
|
155
|
+
|
156
|
+
if listing
|
157
|
+
action = :updated
|
158
|
+
listing.update(server)
|
159
|
+
else
|
160
|
+
action = :added
|
161
|
+
listing = Listing.new(server)
|
162
|
+
@listings_by_uuid[uuid] = listing
|
163
|
+
end
|
164
|
+
|
165
|
+
listing.services.each do |service|
|
166
|
+
@listings_by_service[service] << listing
|
167
|
+
end
|
168
|
+
|
169
|
+
trigger(action, listing)
|
170
|
+
logger.debug { sign_message("#{action} server: #{server.inspect}") }
|
171
|
+
end
|
172
|
+
|
173
|
+
def init_socket
|
174
|
+
@socket = UDPSocket.new
|
175
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
|
176
|
+
|
177
|
+
if defined?(::Socket::SO_REUSEPORT)
|
178
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, true)
|
179
|
+
end
|
180
|
+
|
181
|
+
@socket.bind(self.class.address, self.class.port.to_i)
|
182
|
+
end
|
183
|
+
|
184
|
+
def process_beacon(beacon)
|
185
|
+
server = beacon.server
|
186
|
+
uuid = server.try(:uuid)
|
187
|
+
|
188
|
+
if server && uuid
|
189
|
+
case beacon.beacon_type
|
190
|
+
when ::Protobuf::Rpc::DynamicDiscovery::BeaconType::HEARTBEAT
|
191
|
+
add_or_update_listing(uuid, server)
|
192
|
+
when ::Protobuf::Rpc::DynamicDiscovery::BeaconType::FLATLINE
|
193
|
+
remove_listing(uuid)
|
194
|
+
end
|
195
|
+
else
|
196
|
+
logger.info { sign_message("Ignoring incomplete beacon: #{beacon.inspect}") }
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def read_beacon
|
201
|
+
data, addr = @socket.recvfrom(2048)
|
202
|
+
|
203
|
+
beacon = ::Protobuf::Rpc::DynamicDiscovery::Beacon.decode(data)
|
204
|
+
|
205
|
+
# Favor the address captured by the socket
|
206
|
+
beacon.try(:server).try(:address=, addr[3])
|
207
|
+
|
208
|
+
beacon
|
209
|
+
end
|
210
|
+
|
211
|
+
def remove_expired_listings
|
212
|
+
logger.debug { sign_message("Removing expired listings") }
|
213
|
+
@listings_by_uuid.each do |uuid, listing|
|
214
|
+
remove_listing(uuid) if listing.expired?
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def remove_listing(uuid)
|
219
|
+
listing = @listings_by_uuid[uuid] || return
|
220
|
+
|
221
|
+
logger.debug { sign_message("Removing listing: #{listing.inspect}") }
|
222
|
+
|
223
|
+
@listings_by_service.each_value do |listings|
|
224
|
+
listings.delete(listing)
|
225
|
+
end
|
226
|
+
|
227
|
+
trigger(:removed, @listings_by_uuid.delete(uuid))
|
228
|
+
end
|
229
|
+
|
230
|
+
def reset
|
231
|
+
@thread = nil
|
232
|
+
@socket = nil
|
233
|
+
@listings_by_uuid = {}
|
234
|
+
@listings_by_service = Hash.new { |h, k| h[k] = Set.new }
|
235
|
+
end
|
236
|
+
|
237
|
+
def run
|
238
|
+
sweep_interval = 5 # sweep expired listings every 5 seconds
|
239
|
+
next_sweep = Time.now.to_i + sweep_interval
|
240
|
+
|
241
|
+
loop do
|
242
|
+
timeout = [next_sweep - Time.now.to_i, 0.1].max
|
243
|
+
readable = IO.select([@socket], nil, nil, timeout)
|
244
|
+
process_beacon(read_beacon) if readable
|
245
|
+
|
246
|
+
if Time.now.to_i >= next_sweep
|
247
|
+
remove_expired_listings
|
248
|
+
next_sweep = Time.now.to_i + sweep_interval
|
249
|
+
end
|
250
|
+
end
|
251
|
+
rescue => e
|
252
|
+
logger.debug { sign_message("ERROR: (#{e.class}) #{e.message}\n#{e.backtrace.join("\n")}") }
|
253
|
+
retry
|
254
|
+
end
|
255
|
+
|
256
|
+
def trigger(action, listing)
|
257
|
+
::ActiveSupport::Notifications.instrument("directory.listing.#{action}", :listing => listing)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|