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.
Files changed (192) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.travis.yml +12 -0
  4. data/.yardopts +5 -0
  5. data/CHANGES.md +261 -0
  6. data/CONTRIBUTING.md +16 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE.txt +14 -0
  9. data/README.md +58 -0
  10. data/Rakefile +61 -0
  11. data/bin/protoc-gen-ruby +17 -0
  12. data/bin/rpc_server +4 -0
  13. data/examples/bin/reverse-client-http +4 -0
  14. data/examples/bin/reverse-client-socket +4 -0
  15. data/examples/bin/reverse-client-zmq +4 -0
  16. data/examples/config.ru +6 -0
  17. data/examples/definitions/example/reverse.proto +12 -0
  18. data/examples/lib/example/reverse-client.rb +23 -0
  19. data/examples/lib/example/reverse-service.rb +9 -0
  20. data/examples/lib/example/reverse.pb.rb +36 -0
  21. data/lib/protobuf.rb +106 -0
  22. data/lib/protobuf/cli.rb +249 -0
  23. data/lib/protobuf/code_generator.rb +41 -0
  24. data/lib/protobuf/decoder.rb +74 -0
  25. data/lib/protobuf/deprecator.rb +42 -0
  26. data/lib/protobuf/descriptors.rb +3 -0
  27. data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +52 -0
  28. data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +249 -0
  29. data/lib/protobuf/encoder.rb +62 -0
  30. data/lib/protobuf/enum.rb +319 -0
  31. data/lib/protobuf/exceptions.rb +9 -0
  32. data/lib/protobuf/field.rb +74 -0
  33. data/lib/protobuf/field/base_field.rb +280 -0
  34. data/lib/protobuf/field/bool_field.rb +53 -0
  35. data/lib/protobuf/field/bytes_field.rb +81 -0
  36. data/lib/protobuf/field/double_field.rb +26 -0
  37. data/lib/protobuf/field/enum_field.rb +57 -0
  38. data/lib/protobuf/field/field_array.rb +86 -0
  39. data/lib/protobuf/field/fixed32_field.rb +25 -0
  40. data/lib/protobuf/field/fixed64_field.rb +29 -0
  41. data/lib/protobuf/field/float_field.rb +38 -0
  42. data/lib/protobuf/field/int32_field.rb +22 -0
  43. data/lib/protobuf/field/int64_field.rb +22 -0
  44. data/lib/protobuf/field/integer_field.rb +24 -0
  45. data/lib/protobuf/field/message_field.rb +66 -0
  46. data/lib/protobuf/field/sfixed32_field.rb +28 -0
  47. data/lib/protobuf/field/sfixed64_field.rb +29 -0
  48. data/lib/protobuf/field/signed_integer_field.rb +30 -0
  49. data/lib/protobuf/field/sint32_field.rb +22 -0
  50. data/lib/protobuf/field/sint64_field.rb +22 -0
  51. data/lib/protobuf/field/string_field.rb +35 -0
  52. data/lib/protobuf/field/uint32_field.rb +22 -0
  53. data/lib/protobuf/field/uint64_field.rb +22 -0
  54. data/lib/protobuf/field/varint_field.rb +68 -0
  55. data/lib/protobuf/generators/base.rb +71 -0
  56. data/lib/protobuf/generators/enum_generator.rb +42 -0
  57. data/lib/protobuf/generators/extension_generator.rb +28 -0
  58. data/lib/protobuf/generators/field_generator.rb +132 -0
  59. data/lib/protobuf/generators/file_generator.rb +140 -0
  60. data/lib/protobuf/generators/group_generator.rb +113 -0
  61. data/lib/protobuf/generators/message_generator.rb +99 -0
  62. data/lib/protobuf/generators/printable.rb +161 -0
  63. data/lib/protobuf/generators/service_generator.rb +27 -0
  64. data/lib/protobuf/http.rb +20 -0
  65. data/lib/protobuf/lifecycle.rb +46 -0
  66. data/lib/protobuf/logger.rb +86 -0
  67. data/lib/protobuf/message.rb +182 -0
  68. data/lib/protobuf/message/fields.rb +122 -0
  69. data/lib/protobuf/message/serialization.rb +84 -0
  70. data/lib/protobuf/optionable.rb +23 -0
  71. data/lib/protobuf/rpc/buffer.rb +79 -0
  72. data/lib/protobuf/rpc/client.rb +168 -0
  73. data/lib/protobuf/rpc/connector.rb +21 -0
  74. data/lib/protobuf/rpc/connectors/base.rb +54 -0
  75. data/lib/protobuf/rpc/connectors/common.rb +172 -0
  76. data/lib/protobuf/rpc/connectors/http.rb +90 -0
  77. data/lib/protobuf/rpc/connectors/socket.rb +73 -0
  78. data/lib/protobuf/rpc/connectors/zmq.rb +205 -0
  79. data/lib/protobuf/rpc/dynamic_discovery.pb.rb +47 -0
  80. data/lib/protobuf/rpc/env.rb +58 -0
  81. data/lib/protobuf/rpc/error.rb +28 -0
  82. data/lib/protobuf/rpc/error/client_error.rb +31 -0
  83. data/lib/protobuf/rpc/error/server_error.rb +43 -0
  84. data/lib/protobuf/rpc/middleware.rb +25 -0
  85. data/lib/protobuf/rpc/middleware/exception_handler.rb +36 -0
  86. data/lib/protobuf/rpc/middleware/logger.rb +91 -0
  87. data/lib/protobuf/rpc/middleware/request_decoder.rb +83 -0
  88. data/lib/protobuf/rpc/middleware/response_encoder.rb +88 -0
  89. data/lib/protobuf/rpc/middleware/runner.rb +18 -0
  90. data/lib/protobuf/rpc/rpc.pb.rb +53 -0
  91. data/lib/protobuf/rpc/server.rb +39 -0
  92. data/lib/protobuf/rpc/servers/http/server.rb +101 -0
  93. data/lib/protobuf/rpc/servers/http_runner.rb +34 -0
  94. data/lib/protobuf/rpc/servers/socket/server.rb +113 -0
  95. data/lib/protobuf/rpc/servers/socket/worker.rb +56 -0
  96. data/lib/protobuf/rpc/servers/socket_runner.rb +34 -0
  97. data/lib/protobuf/rpc/servers/zmq/broker.rb +155 -0
  98. data/lib/protobuf/rpc/servers/zmq/server.rb +313 -0
  99. data/lib/protobuf/rpc/servers/zmq/util.rb +47 -0
  100. data/lib/protobuf/rpc/servers/zmq/worker.rb +105 -0
  101. data/lib/protobuf/rpc/servers/zmq_runner.rb +51 -0
  102. data/lib/protobuf/rpc/service.rb +179 -0
  103. data/lib/protobuf/rpc/service_directory.rb +245 -0
  104. data/lib/protobuf/rpc/service_dispatcher.rb +46 -0
  105. data/lib/protobuf/rpc/service_filters.rb +273 -0
  106. data/lib/protobuf/rpc/stat.rb +148 -0
  107. data/lib/protobuf/socket.rb +22 -0
  108. data/lib/protobuf/tasks.rb +1 -0
  109. data/lib/protobuf/tasks/compile.rake +61 -0
  110. data/lib/protobuf/version.rb +3 -0
  111. data/lib/protobuf/wire_type.rb +10 -0
  112. data/lib/protobuf/zmq.rb +21 -0
  113. data/proto/dynamic_discovery.proto +44 -0
  114. data/proto/google/protobuf/compiler/plugin.proto +147 -0
  115. data/proto/google/protobuf/descriptor.proto +620 -0
  116. data/proto/rpc.proto +62 -0
  117. data/protobuffy.gemspec +37 -0
  118. data/spec/benchmark/tasks.rb +113 -0
  119. data/spec/bin/protoc-gen-ruby_spec.rb +18 -0
  120. data/spec/data/data.bin +3 -0
  121. data/spec/data/types.bin +0 -0
  122. data/spec/encoding/all_types_spec.rb +91 -0
  123. data/spec/encoding/extreme_values_spec.rb +0 -0
  124. data/spec/functional/socket_server_spec.rb +59 -0
  125. data/spec/functional/zmq_server_spec.rb +103 -0
  126. data/spec/lib/protobuf/cli_spec.rb +267 -0
  127. data/spec/lib/protobuf/code_generator_spec.rb +60 -0
  128. data/spec/lib/protobuf/enum_spec.rb +239 -0
  129. data/spec/lib/protobuf/field/int32_field_spec.rb +7 -0
  130. data/spec/lib/protobuf/field/string_field_spec.rb +46 -0
  131. data/spec/lib/protobuf/field_spec.rb +194 -0
  132. data/spec/lib/protobuf/generators/base_spec.rb +87 -0
  133. data/spec/lib/protobuf/generators/enum_generator_spec.rb +68 -0
  134. data/spec/lib/protobuf/generators/extension_generator_spec.rb +43 -0
  135. data/spec/lib/protobuf/generators/field_generator_spec.rb +99 -0
  136. data/spec/lib/protobuf/generators/file_generator_spec.rb +29 -0
  137. data/spec/lib/protobuf/generators/message_generator_spec.rb +0 -0
  138. data/spec/lib/protobuf/generators/service_generator_spec.rb +43 -0
  139. data/spec/lib/protobuf/lifecycle_spec.rb +89 -0
  140. data/spec/lib/protobuf/logger_spec.rb +136 -0
  141. data/spec/lib/protobuf/message_spec.rb +368 -0
  142. data/spec/lib/protobuf/optionable_spec.rb +46 -0
  143. data/spec/lib/protobuf/rpc/client_spec.rb +66 -0
  144. data/spec/lib/protobuf/rpc/connector_spec.rb +26 -0
  145. data/spec/lib/protobuf/rpc/connectors/base_spec.rb +50 -0
  146. data/spec/lib/protobuf/rpc/connectors/common_spec.rb +170 -0
  147. data/spec/lib/protobuf/rpc/connectors/connector_spec.rb +13 -0
  148. data/spec/lib/protobuf/rpc/connectors/http_spec.rb +61 -0
  149. data/spec/lib/protobuf/rpc/connectors/socket_spec.rb +24 -0
  150. data/spec/lib/protobuf/rpc/connectors/zmq_spec.rb +129 -0
  151. data/spec/lib/protobuf/rpc/middleware/exception_handler_spec.rb +62 -0
  152. data/spec/lib/protobuf/rpc/middleware/logger_spec.rb +49 -0
  153. data/spec/lib/protobuf/rpc/middleware/request_decoder_spec.rb +115 -0
  154. data/spec/lib/protobuf/rpc/middleware/response_encoder_spec.rb +75 -0
  155. data/spec/lib/protobuf/rpc/servers/http/server_spec.rb +104 -0
  156. data/spec/lib/protobuf/rpc/servers/socket_server_spec.rb +38 -0
  157. data/spec/lib/protobuf/rpc/servers/zmq/server_spec.rb +41 -0
  158. data/spec/lib/protobuf/rpc/servers/zmq/util_spec.rb +55 -0
  159. data/spec/lib/protobuf/rpc/servers/zmq/worker_spec.rb +35 -0
  160. data/spec/lib/protobuf/rpc/service_directory_spec.rb +295 -0
  161. data/spec/lib/protobuf/rpc/service_dispatcher_spec.rb +52 -0
  162. data/spec/lib/protobuf/rpc/service_filters_spec.rb +484 -0
  163. data/spec/lib/protobuf/rpc/service_spec.rb +161 -0
  164. data/spec/lib/protobuf/rpc/stat_spec.rb +151 -0
  165. data/spec/lib/protobuf_spec.rb +78 -0
  166. data/spec/spec_helper.rb +57 -0
  167. data/spec/support/all.rb +7 -0
  168. data/spec/support/packed_field.rb +22 -0
  169. data/spec/support/server.rb +94 -0
  170. data/spec/support/test/all_types.data.bin +0 -0
  171. data/spec/support/test/all_types.data.txt +119 -0
  172. data/spec/support/test/defaults.pb.rb +25 -0
  173. data/spec/support/test/defaults.proto +9 -0
  174. data/spec/support/test/enum.pb.rb +59 -0
  175. data/spec/support/test/enum.proto +34 -0
  176. data/spec/support/test/extended.pb.rb +22 -0
  177. data/spec/support/test/extended.proto +10 -0
  178. data/spec/support/test/extreme_values.data.bin +0 -0
  179. data/spec/support/test/google_unittest.pb.rb +543 -0
  180. data/spec/support/test/google_unittest.proto +713 -0
  181. data/spec/support/test/google_unittest_import.pb.rb +37 -0
  182. data/spec/support/test/google_unittest_import.proto +64 -0
  183. data/spec/support/test/google_unittest_import_public.pb.rb +8 -0
  184. data/spec/support/test/google_unittest_import_public.proto +38 -0
  185. data/spec/support/test/multi_field_extensions.pb.rb +56 -0
  186. data/spec/support/test/multi_field_extensions.proto +33 -0
  187. data/spec/support/test/resource.pb.rb +117 -0
  188. data/spec/support/test/resource.proto +94 -0
  189. data/spec/support/test/resource_service.rb +26 -0
  190. data/spec/support/test_app_file.rb +2 -0
  191. data/spec/support/tolerance_matcher.rb +40 -0
  192. 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