protobuffy 3.1.0
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/.travis.yml +12 -0
- data/.yardopts +5 -0
- data/CHANGES.md +261 -0
- data/CONTRIBUTING.md +16 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +14 -0
- data/README.md +58 -0
- data/Rakefile +61 -0
- data/bin/protoc-gen-ruby +17 -0
- data/bin/rpc_server +4 -0
- data/examples/bin/reverse-client-http +4 -0
- data/examples/bin/reverse-client-socket +4 -0
- data/examples/bin/reverse-client-zmq +4 -0
- data/examples/config.ru +6 -0
- data/examples/definitions/example/reverse.proto +12 -0
- data/examples/lib/example/reverse-client.rb +23 -0
- data/examples/lib/example/reverse-service.rb +9 -0
- data/examples/lib/example/reverse.pb.rb +36 -0
- data/lib/protobuf.rb +106 -0
- data/lib/protobuf/cli.rb +249 -0
- data/lib/protobuf/code_generator.rb +41 -0
- data/lib/protobuf/decoder.rb +74 -0
- data/lib/protobuf/deprecator.rb +42 -0
- data/lib/protobuf/descriptors.rb +3 -0
- data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +52 -0
- data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +249 -0
- data/lib/protobuf/encoder.rb +62 -0
- data/lib/protobuf/enum.rb +319 -0
- data/lib/protobuf/exceptions.rb +9 -0
- data/lib/protobuf/field.rb +74 -0
- data/lib/protobuf/field/base_field.rb +280 -0
- data/lib/protobuf/field/bool_field.rb +53 -0
- data/lib/protobuf/field/bytes_field.rb +81 -0
- data/lib/protobuf/field/double_field.rb +26 -0
- data/lib/protobuf/field/enum_field.rb +57 -0
- data/lib/protobuf/field/field_array.rb +86 -0
- data/lib/protobuf/field/fixed32_field.rb +25 -0
- data/lib/protobuf/field/fixed64_field.rb +29 -0
- data/lib/protobuf/field/float_field.rb +38 -0
- data/lib/protobuf/field/int32_field.rb +22 -0
- data/lib/protobuf/field/int64_field.rb +22 -0
- data/lib/protobuf/field/integer_field.rb +24 -0
- data/lib/protobuf/field/message_field.rb +66 -0
- data/lib/protobuf/field/sfixed32_field.rb +28 -0
- data/lib/protobuf/field/sfixed64_field.rb +29 -0
- data/lib/protobuf/field/signed_integer_field.rb +30 -0
- data/lib/protobuf/field/sint32_field.rb +22 -0
- data/lib/protobuf/field/sint64_field.rb +22 -0
- data/lib/protobuf/field/string_field.rb +35 -0
- data/lib/protobuf/field/uint32_field.rb +22 -0
- data/lib/protobuf/field/uint64_field.rb +22 -0
- data/lib/protobuf/field/varint_field.rb +68 -0
- data/lib/protobuf/generators/base.rb +71 -0
- data/lib/protobuf/generators/enum_generator.rb +42 -0
- data/lib/protobuf/generators/extension_generator.rb +28 -0
- data/lib/protobuf/generators/field_generator.rb +132 -0
- data/lib/protobuf/generators/file_generator.rb +140 -0
- data/lib/protobuf/generators/group_generator.rb +113 -0
- data/lib/protobuf/generators/message_generator.rb +99 -0
- data/lib/protobuf/generators/printable.rb +161 -0
- data/lib/protobuf/generators/service_generator.rb +27 -0
- data/lib/protobuf/http.rb +20 -0
- data/lib/protobuf/lifecycle.rb +46 -0
- data/lib/protobuf/logger.rb +86 -0
- data/lib/protobuf/message.rb +182 -0
- data/lib/protobuf/message/fields.rb +122 -0
- data/lib/protobuf/message/serialization.rb +84 -0
- data/lib/protobuf/optionable.rb +23 -0
- data/lib/protobuf/rpc/buffer.rb +79 -0
- data/lib/protobuf/rpc/client.rb +168 -0
- data/lib/protobuf/rpc/connector.rb +21 -0
- data/lib/protobuf/rpc/connectors/base.rb +54 -0
- data/lib/protobuf/rpc/connectors/common.rb +172 -0
- data/lib/protobuf/rpc/connectors/http.rb +90 -0
- data/lib/protobuf/rpc/connectors/socket.rb +73 -0
- data/lib/protobuf/rpc/connectors/zmq.rb +205 -0
- data/lib/protobuf/rpc/dynamic_discovery.pb.rb +47 -0
- data/lib/protobuf/rpc/env.rb +58 -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 +36 -0
- data/lib/protobuf/rpc/middleware/logger.rb +91 -0
- data/lib/protobuf/rpc/middleware/request_decoder.rb +83 -0
- data/lib/protobuf/rpc/middleware/response_encoder.rb +88 -0
- data/lib/protobuf/rpc/middleware/runner.rb +18 -0
- data/lib/protobuf/rpc/rpc.pb.rb +53 -0
- data/lib/protobuf/rpc/server.rb +39 -0
- data/lib/protobuf/rpc/servers/http/server.rb +101 -0
- data/lib/protobuf/rpc/servers/http_runner.rb +34 -0
- data/lib/protobuf/rpc/servers/socket/server.rb +113 -0
- data/lib/protobuf/rpc/servers/socket/worker.rb +56 -0
- data/lib/protobuf/rpc/servers/socket_runner.rb +34 -0
- data/lib/protobuf/rpc/servers/zmq/broker.rb +155 -0
- data/lib/protobuf/rpc/servers/zmq/server.rb +313 -0
- data/lib/protobuf/rpc/servers/zmq/util.rb +47 -0
- data/lib/protobuf/rpc/servers/zmq/worker.rb +105 -0
- data/lib/protobuf/rpc/servers/zmq_runner.rb +51 -0
- data/lib/protobuf/rpc/service.rb +179 -0
- data/lib/protobuf/rpc/service_directory.rb +245 -0
- data/lib/protobuf/rpc/service_dispatcher.rb +46 -0
- data/lib/protobuf/rpc/service_filters.rb +273 -0
- data/lib/protobuf/rpc/stat.rb +148 -0
- data/lib/protobuf/socket.rb +22 -0
- data/lib/protobuf/tasks.rb +1 -0
- data/lib/protobuf/tasks/compile.rake +61 -0
- data/lib/protobuf/version.rb +3 -0
- data/lib/protobuf/wire_type.rb +10 -0
- data/lib/protobuf/zmq.rb +21 -0
- data/proto/dynamic_discovery.proto +44 -0
- data/proto/google/protobuf/compiler/plugin.proto +147 -0
- data/proto/google/protobuf/descriptor.proto +620 -0
- data/proto/rpc.proto +62 -0
- data/protobuffy.gemspec +37 -0
- data/spec/benchmark/tasks.rb +113 -0
- data/spec/bin/protoc-gen-ruby_spec.rb +18 -0
- data/spec/data/data.bin +3 -0
- data/spec/data/types.bin +0 -0
- data/spec/encoding/all_types_spec.rb +91 -0
- data/spec/encoding/extreme_values_spec.rb +0 -0
- data/spec/functional/socket_server_spec.rb +59 -0
- data/spec/functional/zmq_server_spec.rb +103 -0
- data/spec/lib/protobuf/cli_spec.rb +267 -0
- data/spec/lib/protobuf/code_generator_spec.rb +60 -0
- data/spec/lib/protobuf/enum_spec.rb +239 -0
- data/spec/lib/protobuf/field/int32_field_spec.rb +7 -0
- data/spec/lib/protobuf/field/string_field_spec.rb +46 -0
- data/spec/lib/protobuf/field_spec.rb +194 -0
- data/spec/lib/protobuf/generators/base_spec.rb +87 -0
- data/spec/lib/protobuf/generators/enum_generator_spec.rb +68 -0
- data/spec/lib/protobuf/generators/extension_generator_spec.rb +43 -0
- data/spec/lib/protobuf/generators/field_generator_spec.rb +99 -0
- data/spec/lib/protobuf/generators/file_generator_spec.rb +29 -0
- data/spec/lib/protobuf/generators/message_generator_spec.rb +0 -0
- data/spec/lib/protobuf/generators/service_generator_spec.rb +43 -0
- data/spec/lib/protobuf/lifecycle_spec.rb +89 -0
- data/spec/lib/protobuf/logger_spec.rb +136 -0
- data/spec/lib/protobuf/message_spec.rb +368 -0
- data/spec/lib/protobuf/optionable_spec.rb +46 -0
- data/spec/lib/protobuf/rpc/client_spec.rb +66 -0
- data/spec/lib/protobuf/rpc/connector_spec.rb +26 -0
- data/spec/lib/protobuf/rpc/connectors/base_spec.rb +50 -0
- data/spec/lib/protobuf/rpc/connectors/common_spec.rb +170 -0
- data/spec/lib/protobuf/rpc/connectors/connector_spec.rb +13 -0
- data/spec/lib/protobuf/rpc/connectors/http_spec.rb +61 -0
- data/spec/lib/protobuf/rpc/connectors/socket_spec.rb +24 -0
- data/spec/lib/protobuf/rpc/connectors/zmq_spec.rb +129 -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 +75 -0
- data/spec/lib/protobuf/rpc/servers/http/server_spec.rb +104 -0
- data/spec/lib/protobuf/rpc/servers/socket_server_spec.rb +38 -0
- data/spec/lib/protobuf/rpc/servers/zmq/server_spec.rb +41 -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 +295 -0
- data/spec/lib/protobuf/rpc/service_dispatcher_spec.rb +52 -0
- data/spec/lib/protobuf/rpc/service_filters_spec.rb +484 -0
- data/spec/lib/protobuf/rpc/service_spec.rb +161 -0
- data/spec/lib/protobuf/rpc/stat_spec.rb +151 -0
- data/spec/lib/protobuf_spec.rb +78 -0
- data/spec/spec_helper.rb +57 -0
- data/spec/support/all.rb +7 -0
- data/spec/support/packed_field.rb +22 -0
- data/spec/support/server.rb +94 -0
- data/spec/support/test/all_types.data.bin +0 -0
- data/spec/support/test/all_types.data.txt +119 -0
- data/spec/support/test/defaults.pb.rb +25 -0
- data/spec/support/test/defaults.proto +9 -0
- data/spec/support/test/enum.pb.rb +59 -0
- data/spec/support/test/enum.proto +34 -0
- data/spec/support/test/extended.pb.rb +22 -0
- data/spec/support/test/extended.proto +10 -0
- data/spec/support/test/extreme_values.data.bin +0 -0
- data/spec/support/test/google_unittest.pb.rb +543 -0
- data/spec/support/test/google_unittest.proto +713 -0
- data/spec/support/test/google_unittest_import.pb.rb +37 -0
- data/spec/support/test/google_unittest_import.proto +64 -0
- data/spec/support/test/google_unittest_import_public.pb.rb +8 -0
- data/spec/support/test/google_unittest_import_public.proto +38 -0
- data/spec/support/test/multi_field_extensions.pb.rb +56 -0
- data/spec/support/test/multi_field_extensions.proto +33 -0
- data/spec/support/test/resource.pb.rb +117 -0
- data/spec/support/test/resource.proto +94 -0
- data/spec/support/test/resource_service.rb +26 -0
- data/spec/support/test_app_file.rb +2 -0
- data/spec/support/tolerance_matcher.rb +40 -0
- metadata +367 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Protobuf
|
4
|
+
module Rpc
|
5
|
+
class ZmqRunner
|
6
|
+
include ::Protobuf::Logger::LogMethods
|
7
|
+
|
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
|
17
|
+
|
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(:TTIN) do
|
40
|
+
@server.add_worker
|
41
|
+
log_info { "Increased worker size to: #{@server.total_workers}" }
|
42
|
+
end
|
43
|
+
|
44
|
+
trap(:TTOU) do
|
45
|
+
log_info { "Current worker size: #{@server.workers.size}" }
|
46
|
+
log_info { "Current worker size: #{@server.busy_worker_count}" }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'protobuf/logger'
|
2
|
+
require 'protobuf/rpc/client'
|
3
|
+
require 'protobuf/rpc/error'
|
4
|
+
require 'protobuf/rpc/service_filters'
|
5
|
+
|
6
|
+
module Protobuf
|
7
|
+
module Rpc
|
8
|
+
# Object to encapsulate the request/response types for a given service method
|
9
|
+
#
|
10
|
+
RpcMethod = Struct.new("RpcMethod", :method, :request_type, :response_type)
|
11
|
+
|
12
|
+
class Service
|
13
|
+
include ::Protobuf::Logger::LogMethods
|
14
|
+
include ::Protobuf::Rpc::ServiceFilters
|
15
|
+
|
16
|
+
DEFAULT_HOST = '127.0.0.1'.freeze
|
17
|
+
DEFAULT_PORT = 9399
|
18
|
+
|
19
|
+
attr_reader :env, :request
|
20
|
+
|
21
|
+
##
|
22
|
+
# Constructor!
|
23
|
+
#
|
24
|
+
# Initialize a service with the rpc endpoint name and the bytes
|
25
|
+
# for the request.
|
26
|
+
def initialize(env)
|
27
|
+
@env = env.dup # Dup the env so it doesn't change out from under us
|
28
|
+
@request = env.request
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Class Methods
|
33
|
+
#
|
34
|
+
# Create a new client for the given service.
|
35
|
+
# See Client#initialize and ClientConnection::DEFAULT_OPTIONS
|
36
|
+
# for all available options.
|
37
|
+
#
|
38
|
+
def self.client(options = {})
|
39
|
+
::Protobuf::Rpc::Client.new({ :service => self,
|
40
|
+
:host => host,
|
41
|
+
:port => port }.merge(options))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Allows service-level configuration of location.
|
45
|
+
# Useful for system-startup configuration of a service
|
46
|
+
# so that any Clients using the Service.client sugar
|
47
|
+
# will not have to configure the location each time.
|
48
|
+
#
|
49
|
+
def self.configure(config = {})
|
50
|
+
self.host = config[:host] if config.key?(:host)
|
51
|
+
self.port = config[:port] if config.key?(:port)
|
52
|
+
end
|
53
|
+
|
54
|
+
# The host location of the service.
|
55
|
+
#
|
56
|
+
def self.host
|
57
|
+
@_host ||= DEFAULT_HOST
|
58
|
+
end
|
59
|
+
|
60
|
+
# The host location setter.
|
61
|
+
#
|
62
|
+
def self.host=(new_host)
|
63
|
+
@_host = new_host
|
64
|
+
end
|
65
|
+
|
66
|
+
# An array of defined service classes that contain implementation
|
67
|
+
# code
|
68
|
+
def self.implemented_services
|
69
|
+
classes = (self.subclasses || []).select do |subclass|
|
70
|
+
subclass.rpcs.any? do |(name, _)|
|
71
|
+
subclass.method_defined? name
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
classes.map(&:name)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Shorthand call to configure, passing a string formatted as hostname:port
|
79
|
+
# e.g. 127.0.0.1:9933
|
80
|
+
# e.g. localhost:0
|
81
|
+
#
|
82
|
+
def self.located_at(location)
|
83
|
+
return if location.nil? || location.downcase.strip !~ /.+:\d+/
|
84
|
+
host, port = location.downcase.strip.split ':'
|
85
|
+
configure(:host => host, :port => port.to_i)
|
86
|
+
end
|
87
|
+
|
88
|
+
# The port of the service on the destination server.
|
89
|
+
#
|
90
|
+
def self.port
|
91
|
+
@_port ||= DEFAULT_PORT
|
92
|
+
end
|
93
|
+
|
94
|
+
# The port location setter.
|
95
|
+
#
|
96
|
+
def self.port=(new_port)
|
97
|
+
@_port = new_port
|
98
|
+
end
|
99
|
+
|
100
|
+
# Define an rpc method with the given request and response types.
|
101
|
+
# This methods is only used by the generated service definitions
|
102
|
+
# and not useful for user code.
|
103
|
+
#
|
104
|
+
def self.rpc(method, request_type, response_type)
|
105
|
+
rpcs[method] = RpcMethod.new(method, request_type, response_type)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Hash containing the set of methods defined via `rpc`.
|
109
|
+
#
|
110
|
+
def self.rpcs
|
111
|
+
@_rpcs ||= {}
|
112
|
+
end
|
113
|
+
|
114
|
+
# Check if the given method name is a known rpc endpoint.
|
115
|
+
#
|
116
|
+
def self.rpc_method?(name)
|
117
|
+
rpcs.key?(name)
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Instance Methods
|
122
|
+
#
|
123
|
+
# Get a callable object that will be used by the dispatcher
|
124
|
+
# to invoke the specified rpc method. Facilitates callback dispatch.
|
125
|
+
# The returned lambda is expected to be called at a later time (which
|
126
|
+
# is why we wrap the method call).
|
127
|
+
#
|
128
|
+
def callable_rpc_method(method_name)
|
129
|
+
lambda { run_filters(method_name) }
|
130
|
+
end
|
131
|
+
|
132
|
+
# Response object for this rpc cycle. Not assignable.
|
133
|
+
#
|
134
|
+
def response
|
135
|
+
@_response ||= response_type.new
|
136
|
+
end
|
137
|
+
|
138
|
+
# Convenience method to get back to class method.
|
139
|
+
#
|
140
|
+
def rpc_method?(name)
|
141
|
+
self.class.rpc_method?(name)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Convenience method to get back to class rpcs hash.
|
145
|
+
#
|
146
|
+
def rpcs
|
147
|
+
self.class.rpcs
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def request_type
|
153
|
+
@_request_type ||= env.request_type
|
154
|
+
end
|
155
|
+
|
156
|
+
# Sugar to make an rpc method feel like a controller method.
|
157
|
+
# If this method is not called, the response will be the memoized
|
158
|
+
# object returned by the response reader.
|
159
|
+
#
|
160
|
+
def respond_with(candidate)
|
161
|
+
@_response = candidate
|
162
|
+
end
|
163
|
+
alias_method :return_from_whence_you_came, :respond_with
|
164
|
+
|
165
|
+
def response_type
|
166
|
+
@_response_type ||= env.response_type
|
167
|
+
end
|
168
|
+
|
169
|
+
# Automatically fail a service method.
|
170
|
+
#
|
171
|
+
def rpc_failed(message)
|
172
|
+
message = message.message if message.respond_to?(:message)
|
173
|
+
raise RpcFailed.new(message)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
ActiveSupport.run_load_hooks(:protobuf_rpc_service, Service)
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,245 @@
|
|
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
|
+
class ServiceDirectory
|
13
|
+
include ::Singleton
|
14
|
+
include ::Protobuf::Logger::LogMethods
|
15
|
+
|
16
|
+
DEFAULT_ADDRESS = "0.0.0.0"
|
17
|
+
DEFAULT_PORT = 53000
|
18
|
+
DEFAULT_TIMEOUT = 1
|
19
|
+
|
20
|
+
class Listing < Delegator
|
21
|
+
attr_reader :expires_at
|
22
|
+
|
23
|
+
def initialize(server)
|
24
|
+
update(server)
|
25
|
+
end
|
26
|
+
|
27
|
+
def current?
|
28
|
+
!expired?
|
29
|
+
end
|
30
|
+
|
31
|
+
def eql?(other)
|
32
|
+
uuid.eql?(other.uuid)
|
33
|
+
end
|
34
|
+
|
35
|
+
def expired?
|
36
|
+
Time.now.to_i >= @expires_at
|
37
|
+
end
|
38
|
+
|
39
|
+
def hash
|
40
|
+
uuid.hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def ttl
|
44
|
+
[super.to_i, 1].max
|
45
|
+
end
|
46
|
+
|
47
|
+
def update(server)
|
48
|
+
@server = server
|
49
|
+
@expires_at = Time.now.to_i + ttl
|
50
|
+
end
|
51
|
+
|
52
|
+
def __getobj__
|
53
|
+
@server
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Class Methods
|
58
|
+
#
|
59
|
+
class << self
|
60
|
+
attr_writer :address, :port
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.address
|
64
|
+
@address ||= DEFAULT_ADDRESS
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.port
|
68
|
+
@port ||= DEFAULT_PORT
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.start
|
72
|
+
yield(self) if block_given?
|
73
|
+
self.instance.start
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.stop
|
77
|
+
self.instance.stop
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Instance Methods
|
82
|
+
#
|
83
|
+
def initialize
|
84
|
+
reset
|
85
|
+
end
|
86
|
+
|
87
|
+
def all_listings_for(service)
|
88
|
+
if running? && @listings_by_service.key?(service.to_s)
|
89
|
+
@listings_by_service[service.to_s].entries.shuffle
|
90
|
+
else
|
91
|
+
[]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def each_listing(&block)
|
96
|
+
@listings_by_uuid.each_value(&block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def lookup(service)
|
100
|
+
if running?
|
101
|
+
if @listings_by_service.key?(service.to_s)
|
102
|
+
@listings_by_service[service.to_s].entries.sample
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def restart
|
108
|
+
stop
|
109
|
+
start
|
110
|
+
end
|
111
|
+
|
112
|
+
def running?
|
113
|
+
!!@thread.try(:alive?)
|
114
|
+
end
|
115
|
+
|
116
|
+
def start
|
117
|
+
unless running?
|
118
|
+
init_socket
|
119
|
+
log_info { sign_message("listening to udp://#{self.class.address}:#{self.class.port}") }
|
120
|
+
@thread = Thread.new { self.send(:run) }
|
121
|
+
end
|
122
|
+
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
def stop
|
127
|
+
log_info { sign_message("Stopping directory") }
|
128
|
+
|
129
|
+
@thread.try(:kill).try(:join)
|
130
|
+
@socket.try(:close)
|
131
|
+
|
132
|
+
reset
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def add_or_update_listing(uuid, server)
|
138
|
+
listing = @listings_by_uuid[uuid]
|
139
|
+
|
140
|
+
if listing
|
141
|
+
action = :updated
|
142
|
+
listing.update(server)
|
143
|
+
else
|
144
|
+
action = :added
|
145
|
+
listing = Listing.new(server)
|
146
|
+
@listings_by_uuid[uuid] = listing
|
147
|
+
end
|
148
|
+
|
149
|
+
listing.services.each do |service|
|
150
|
+
@listings_by_service[service] << listing
|
151
|
+
end
|
152
|
+
|
153
|
+
trigger(action, listing)
|
154
|
+
log_debug { sign_message("#{action} server: #{server.inspect}") }
|
155
|
+
end
|
156
|
+
|
157
|
+
def init_socket
|
158
|
+
@socket = UDPSocket.new
|
159
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, true)
|
160
|
+
|
161
|
+
if defined?(::Socket::SO_REUSEPORT)
|
162
|
+
@socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, true)
|
163
|
+
end
|
164
|
+
|
165
|
+
@socket.bind(self.class.address, self.class.port.to_i)
|
166
|
+
end
|
167
|
+
|
168
|
+
def process_beacon(beacon)
|
169
|
+
server = beacon.server
|
170
|
+
uuid = server.try(:uuid)
|
171
|
+
|
172
|
+
if server && uuid
|
173
|
+
case beacon.beacon_type
|
174
|
+
when ::Protobuf::Rpc::DynamicDiscovery::BeaconType::HEARTBEAT
|
175
|
+
add_or_update_listing(uuid, server)
|
176
|
+
when ::Protobuf::Rpc::DynamicDiscovery::BeaconType::FLATLINE
|
177
|
+
remove_listing(uuid)
|
178
|
+
end
|
179
|
+
else
|
180
|
+
log_info { sign_message("Ignoring incomplete beacon: #{beacon.inspect}") }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def read_beacon
|
185
|
+
data, addr = @socket.recvfrom(2048)
|
186
|
+
|
187
|
+
beacon = ::Protobuf::Rpc::DynamicDiscovery::Beacon.decode(data)
|
188
|
+
|
189
|
+
# Favor the address captured by the socket
|
190
|
+
beacon.try(:server).try(:address=, addr[3])
|
191
|
+
|
192
|
+
beacon
|
193
|
+
end
|
194
|
+
|
195
|
+
def remove_expired_listings
|
196
|
+
log_debug { sign_message("Removing expired listings") }
|
197
|
+
@listings_by_uuid.each do |uuid, listing|
|
198
|
+
remove_listing(uuid) if listing.expired?
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def remove_listing(uuid)
|
203
|
+
listing = @listings_by_uuid[uuid] or return
|
204
|
+
|
205
|
+
log_debug { sign_message("Removing listing: #{listing.inspect}") }
|
206
|
+
|
207
|
+
@listings_by_service.each do |service, listings|
|
208
|
+
listings.delete(listing)
|
209
|
+
end
|
210
|
+
|
211
|
+
trigger(:removed, @listings_by_uuid.delete(uuid))
|
212
|
+
end
|
213
|
+
|
214
|
+
def reset
|
215
|
+
@thread = nil
|
216
|
+
@socket = nil
|
217
|
+
@listings_by_uuid = {}
|
218
|
+
@listings_by_service = Hash.new { |h, k| h[k] = Set.new }
|
219
|
+
end
|
220
|
+
|
221
|
+
def run
|
222
|
+
sweep_interval = 1 # sweep expired listings every 1 second
|
223
|
+
next_sweep = Time.now.to_i + sweep_interval
|
224
|
+
|
225
|
+
loop do
|
226
|
+
timeout = [next_sweep - Time.now.to_i, 0.1].max
|
227
|
+
readable = IO.select([@socket], nil, nil, timeout)
|
228
|
+
process_beacon(read_beacon) if readable
|
229
|
+
|
230
|
+
if Time.now.to_i >= next_sweep
|
231
|
+
remove_expired_listings
|
232
|
+
next_sweep = Time.now.to_i + sweep_interval
|
233
|
+
end
|
234
|
+
end
|
235
|
+
rescue => e
|
236
|
+
log_debug { sign_message("ERROR: (#{e.class}) #{e.message}\n#{e.backtrace.join("\n")}") }
|
237
|
+
retry
|
238
|
+
end
|
239
|
+
|
240
|
+
def trigger(action, listing)
|
241
|
+
::ActiveSupport::Notifications.instrument("directory.listing.#{action}", :listing => listing)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|