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.
Files changed (204) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rubocop.yml +70 -0
  4. data/.rubocop_todo.yml +145 -0
  5. data/.travis.yml +40 -0
  6. data/.yardopts +5 -0
  7. data/CHANGES.md +344 -0
  8. data/CONTRIBUTING.md +16 -0
  9. data/Gemfile +3 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +33 -0
  12. data/Rakefile +64 -0
  13. data/bin/protoc-gen-ruby +22 -0
  14. data/bin/rpc_server +5 -0
  15. data/install-protobuf.sh +28 -0
  16. data/lib/protobuf.rb +129 -0
  17. data/lib/protobuf/cli.rb +257 -0
  18. data/lib/protobuf/code_generator.rb +120 -0
  19. data/lib/protobuf/decoder.rb +28 -0
  20. data/lib/protobuf/deprecation.rb +117 -0
  21. data/lib/protobuf/descriptors.rb +3 -0
  22. data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +62 -0
  23. data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +301 -0
  24. data/lib/protobuf/encoder.rb +11 -0
  25. data/lib/protobuf/enum.rb +365 -0
  26. data/lib/protobuf/exceptions.rb +9 -0
  27. data/lib/protobuf/field.rb +74 -0
  28. data/lib/protobuf/field/base_field.rb +380 -0
  29. data/lib/protobuf/field/base_field_object_definitions.rb +504 -0
  30. data/lib/protobuf/field/bool_field.rb +64 -0
  31. data/lib/protobuf/field/bytes_field.rb +78 -0
  32. data/lib/protobuf/field/double_field.rb +25 -0
  33. data/lib/protobuf/field/enum_field.rb +61 -0
  34. data/lib/protobuf/field/field_array.rb +104 -0
  35. data/lib/protobuf/field/field_hash.rb +122 -0
  36. data/lib/protobuf/field/fixed32_field.rb +25 -0
  37. data/lib/protobuf/field/fixed64_field.rb +28 -0
  38. data/lib/protobuf/field/float_field.rb +43 -0
  39. data/lib/protobuf/field/int32_field.rb +21 -0
  40. data/lib/protobuf/field/int64_field.rb +34 -0
  41. data/lib/protobuf/field/integer_field.rb +23 -0
  42. data/lib/protobuf/field/message_field.rb +51 -0
  43. data/lib/protobuf/field/sfixed32_field.rb +27 -0
  44. data/lib/protobuf/field/sfixed64_field.rb +28 -0
  45. data/lib/protobuf/field/signed_integer_field.rb +29 -0
  46. data/lib/protobuf/field/sint32_field.rb +21 -0
  47. data/lib/protobuf/field/sint64_field.rb +21 -0
  48. data/lib/protobuf/field/string_field.rb +51 -0
  49. data/lib/protobuf/field/uint32_field.rb +21 -0
  50. data/lib/protobuf/field/uint64_field.rb +21 -0
  51. data/lib/protobuf/field/varint_field.rb +77 -0
  52. data/lib/protobuf/generators/base.rb +85 -0
  53. data/lib/protobuf/generators/enum_generator.rb +39 -0
  54. data/lib/protobuf/generators/extension_generator.rb +27 -0
  55. data/lib/protobuf/generators/field_generator.rb +193 -0
  56. data/lib/protobuf/generators/file_generator.rb +262 -0
  57. data/lib/protobuf/generators/group_generator.rb +122 -0
  58. data/lib/protobuf/generators/message_generator.rb +104 -0
  59. data/lib/protobuf/generators/option_generator.rb +17 -0
  60. data/lib/protobuf/generators/printable.rb +160 -0
  61. data/lib/protobuf/generators/service_generator.rb +50 -0
  62. data/lib/protobuf/lifecycle.rb +33 -0
  63. data/lib/protobuf/logging.rb +39 -0
  64. data/lib/protobuf/message.rb +260 -0
  65. data/lib/protobuf/message/fields.rb +233 -0
  66. data/lib/protobuf/message/serialization.rb +85 -0
  67. data/lib/protobuf/optionable.rb +70 -0
  68. data/lib/protobuf/rpc/buffer.rb +78 -0
  69. data/lib/protobuf/rpc/client.rb +140 -0
  70. data/lib/protobuf/rpc/connectors/base.rb +221 -0
  71. data/lib/protobuf/rpc/connectors/ping.rb +89 -0
  72. data/lib/protobuf/rpc/connectors/socket.rb +78 -0
  73. data/lib/protobuf/rpc/connectors/zmq.rb +319 -0
  74. data/lib/protobuf/rpc/dynamic_discovery.pb.rb +50 -0
  75. data/lib/protobuf/rpc/env.rb +60 -0
  76. data/lib/protobuf/rpc/error.rb +28 -0
  77. data/lib/protobuf/rpc/error/client_error.rb +31 -0
  78. data/lib/protobuf/rpc/error/server_error.rb +43 -0
  79. data/lib/protobuf/rpc/middleware.rb +25 -0
  80. data/lib/protobuf/rpc/middleware/exception_handler.rb +40 -0
  81. data/lib/protobuf/rpc/middleware/logger.rb +95 -0
  82. data/lib/protobuf/rpc/middleware/request_decoder.rb +79 -0
  83. data/lib/protobuf/rpc/middleware/response_encoder.rb +83 -0
  84. data/lib/protobuf/rpc/middleware/runner.rb +18 -0
  85. data/lib/protobuf/rpc/rpc.pb.rb +64 -0
  86. data/lib/protobuf/rpc/rpc_method.rb +16 -0
  87. data/lib/protobuf/rpc/server.rb +39 -0
  88. data/lib/protobuf/rpc/servers/socket/server.rb +121 -0
  89. data/lib/protobuf/rpc/servers/socket/worker.rb +56 -0
  90. data/lib/protobuf/rpc/servers/socket_runner.rb +46 -0
  91. data/lib/protobuf/rpc/servers/zmq/broker.rb +194 -0
  92. data/lib/protobuf/rpc/servers/zmq/server.rb +321 -0
  93. data/lib/protobuf/rpc/servers/zmq/util.rb +48 -0
  94. data/lib/protobuf/rpc/servers/zmq/worker.rb +105 -0
  95. data/lib/protobuf/rpc/servers/zmq_runner.rb +70 -0
  96. data/lib/protobuf/rpc/service.rb +172 -0
  97. data/lib/protobuf/rpc/service_directory.rb +261 -0
  98. data/lib/protobuf/rpc/service_dispatcher.rb +45 -0
  99. data/lib/protobuf/rpc/service_filters.rb +250 -0
  100. data/lib/protobuf/rpc/stat.rb +119 -0
  101. data/lib/protobuf/socket.rb +21 -0
  102. data/lib/protobuf/tasks.rb +1 -0
  103. data/lib/protobuf/tasks/compile.rake +80 -0
  104. data/lib/protobuf/varint.rb +20 -0
  105. data/lib/protobuf/varint_pure.rb +31 -0
  106. data/lib/protobuf/version.rb +3 -0
  107. data/lib/protobuf/wire_type.rb +10 -0
  108. data/lib/protobuf/zmq.rb +21 -0
  109. data/profile.html +5103 -0
  110. data/proto/dynamic_discovery.proto +44 -0
  111. data/proto/google/protobuf/compiler/plugin.proto +147 -0
  112. data/proto/google/protobuf/descriptor.proto +779 -0
  113. data/proto/rpc.proto +69 -0
  114. data/protobuf-cucumber.gemspec +57 -0
  115. data/spec/benchmark/tasks.rb +143 -0
  116. data/spec/bin/protoc-gen-ruby_spec.rb +23 -0
  117. data/spec/encoding/all_types_spec.rb +103 -0
  118. data/spec/encoding/extreme_values_spec.rb +0 -0
  119. data/spec/functional/class_inheritance_spec.rb +52 -0
  120. data/spec/functional/code_generator_spec.rb +58 -0
  121. data/spec/functional/socket_server_spec.rb +59 -0
  122. data/spec/functional/zmq_server_spec.rb +105 -0
  123. data/spec/lib/protobuf/cli_spec.rb +317 -0
  124. data/spec/lib/protobuf/code_generator_spec.rb +87 -0
  125. data/spec/lib/protobuf/enum_spec.rb +307 -0
  126. data/spec/lib/protobuf/field/bool_field_spec.rb +91 -0
  127. data/spec/lib/protobuf/field/double_field_spec.rb +9 -0
  128. data/spec/lib/protobuf/field/enum_field_spec.rb +44 -0
  129. data/spec/lib/protobuf/field/field_array_spec.rb +105 -0
  130. data/spec/lib/protobuf/field/field_hash_spec.rb +168 -0
  131. data/spec/lib/protobuf/field/fixed32_field_spec.rb +7 -0
  132. data/spec/lib/protobuf/field/fixed64_field_spec.rb +7 -0
  133. data/spec/lib/protobuf/field/float_field_spec.rb +90 -0
  134. data/spec/lib/protobuf/field/int32_field_spec.rb +120 -0
  135. data/spec/lib/protobuf/field/int64_field_spec.rb +7 -0
  136. data/spec/lib/protobuf/field/message_field_spec.rb +132 -0
  137. data/spec/lib/protobuf/field/sfixed32_field_spec.rb +9 -0
  138. data/spec/lib/protobuf/field/sfixed64_field_spec.rb +9 -0
  139. data/spec/lib/protobuf/field/sint32_field_spec.rb +9 -0
  140. data/spec/lib/protobuf/field/sint64_field_spec.rb +9 -0
  141. data/spec/lib/protobuf/field/string_field_spec.rb +79 -0
  142. data/spec/lib/protobuf/field/uint32_field_spec.rb +7 -0
  143. data/spec/lib/protobuf/field/uint64_field_spec.rb +7 -0
  144. data/spec/lib/protobuf/field_spec.rb +192 -0
  145. data/spec/lib/protobuf/generators/base_spec.rb +154 -0
  146. data/spec/lib/protobuf/generators/enum_generator_spec.rb +82 -0
  147. data/spec/lib/protobuf/generators/extension_generator_spec.rb +42 -0
  148. data/spec/lib/protobuf/generators/field_generator_spec.rb +197 -0
  149. data/spec/lib/protobuf/generators/file_generator_spec.rb +119 -0
  150. data/spec/lib/protobuf/generators/message_generator_spec.rb +0 -0
  151. data/spec/lib/protobuf/generators/service_generator_spec.rb +99 -0
  152. data/spec/lib/protobuf/lifecycle_spec.rb +94 -0
  153. data/spec/lib/protobuf/message_spec.rb +944 -0
  154. data/spec/lib/protobuf/optionable_spec.rb +265 -0
  155. data/spec/lib/protobuf/rpc/client_spec.rb +66 -0
  156. data/spec/lib/protobuf/rpc/connectors/base_spec.rb +226 -0
  157. data/spec/lib/protobuf/rpc/connectors/ping_spec.rb +69 -0
  158. data/spec/lib/protobuf/rpc/connectors/socket_spec.rb +34 -0
  159. data/spec/lib/protobuf/rpc/connectors/zmq_spec.rb +110 -0
  160. data/spec/lib/protobuf/rpc/middleware/exception_handler_spec.rb +62 -0
  161. data/spec/lib/protobuf/rpc/middleware/logger_spec.rb +49 -0
  162. data/spec/lib/protobuf/rpc/middleware/request_decoder_spec.rb +115 -0
  163. data/spec/lib/protobuf/rpc/middleware/response_encoder_spec.rb +91 -0
  164. data/spec/lib/protobuf/rpc/servers/socket_server_spec.rb +38 -0
  165. data/spec/lib/protobuf/rpc/servers/zmq/server_spec.rb +43 -0
  166. data/spec/lib/protobuf/rpc/servers/zmq/util_spec.rb +55 -0
  167. data/spec/lib/protobuf/rpc/servers/zmq/worker_spec.rb +35 -0
  168. data/spec/lib/protobuf/rpc/service_directory_spec.rb +293 -0
  169. data/spec/lib/protobuf/rpc/service_dispatcher_spec.rb +35 -0
  170. data/spec/lib/protobuf/rpc/service_filters_spec.rb +517 -0
  171. data/spec/lib/protobuf/rpc/service_spec.rb +162 -0
  172. data/spec/lib/protobuf/rpc/stat_spec.rb +101 -0
  173. data/spec/lib/protobuf/varint_spec.rb +29 -0
  174. data/spec/lib/protobuf_spec.rb +105 -0
  175. data/spec/spec_helper.rb +42 -0
  176. data/spec/support/all.rb +6 -0
  177. data/spec/support/packed_field.rb +23 -0
  178. data/spec/support/protos/all_types.data.bin +0 -0
  179. data/spec/support/protos/all_types.data.txt +119 -0
  180. data/spec/support/protos/enum.pb.rb +63 -0
  181. data/spec/support/protos/enum.proto +37 -0
  182. data/spec/support/protos/extreme_values.data.bin +0 -0
  183. data/spec/support/protos/google_unittest.bin +0 -0
  184. data/spec/support/protos/google_unittest.pb.rb +798 -0
  185. data/spec/support/protos/google_unittest.proto +884 -0
  186. data/spec/support/protos/google_unittest_custom_options.bin +0 -0
  187. data/spec/support/protos/google_unittest_custom_options.pb.rb +361 -0
  188. data/spec/support/protos/google_unittest_custom_options.proto +424 -0
  189. data/spec/support/protos/google_unittest_import.pb.rb +55 -0
  190. data/spec/support/protos/google_unittest_import.proto +73 -0
  191. data/spec/support/protos/google_unittest_import_public.pb.rb +31 -0
  192. data/spec/support/protos/google_unittest_import_public.proto +41 -0
  193. data/spec/support/protos/map-test.bin +157 -0
  194. data/spec/support/protos/map-test.pb.rb +85 -0
  195. data/spec/support/protos/map-test.proto +68 -0
  196. data/spec/support/protos/multi_field_extensions.pb.rb +59 -0
  197. data/spec/support/protos/multi_field_extensions.proto +35 -0
  198. data/spec/support/protos/resource.pb.rb +172 -0
  199. data/spec/support/protos/resource.proto +137 -0
  200. data/spec/support/resource_service.rb +23 -0
  201. data/spec/support/server.rb +65 -0
  202. data/spec/support/test_app_file.rb +2 -0
  203. data/varint_prof.rb +82 -0
  204. 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