grpc 0.13.0.pre1.1-universal-darwin

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grpc might be problematic. Click here for more details.

Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/etc/roots.pem +5114 -0
  3. data/grpc_c.32.ruby +0 -0
  4. data/grpc_c.64.ruby +0 -0
  5. data/src/ruby/bin/apis/google/protobuf/empty.rb +44 -0
  6. data/src/ruby/bin/apis/pubsub_demo.rb +256 -0
  7. data/src/ruby/bin/apis/tech/pubsub/proto/pubsub.rb +174 -0
  8. data/src/ruby/bin/apis/tech/pubsub/proto/pubsub_services.rb +103 -0
  9. data/src/ruby/bin/grpc_ruby_interop_client +33 -0
  10. data/src/ruby/bin/grpc_ruby_interop_server +33 -0
  11. data/src/ruby/bin/interop/interop_client.rb +51 -0
  12. data/src/ruby/bin/interop/interop_server.rb +50 -0
  13. data/src/ruby/bin/math.rb +32 -0
  14. data/src/ruby/bin/math_client.rb +147 -0
  15. data/src/ruby/bin/math_server.rb +206 -0
  16. data/src/ruby/bin/math_services.rb +27 -0
  17. data/src/ruby/bin/noproto_client.rb +108 -0
  18. data/src/ruby/bin/noproto_server.rb +112 -0
  19. data/src/ruby/ext/grpc/extconf.rb +129 -0
  20. data/src/ruby/ext/grpc/rb_byte_buffer.c +70 -0
  21. data/src/ruby/ext/grpc/rb_byte_buffer.h +47 -0
  22. data/src/ruby/ext/grpc/rb_call.c +908 -0
  23. data/src/ruby/ext/grpc/rb_call.h +66 -0
  24. data/src/ruby/ext/grpc/rb_call_credentials.c +319 -0
  25. data/src/ruby/ext/grpc/rb_call_credentials.h +46 -0
  26. data/src/ruby/ext/grpc/rb_channel.c +432 -0
  27. data/src/ruby/ext/grpc/rb_channel.h +47 -0
  28. data/src/ruby/ext/grpc/rb_channel_args.c +169 -0
  29. data/src/ruby/ext/grpc/rb_channel_args.h +53 -0
  30. data/src/ruby/ext/grpc/rb_channel_credentials.c +268 -0
  31. data/src/ruby/ext/grpc/rb_channel_credentials.h +47 -0
  32. data/src/ruby/ext/grpc/rb_completion_queue.c +183 -0
  33. data/src/ruby/ext/grpc/rb_completion_queue.h +55 -0
  34. data/src/ruby/ext/grpc/rb_event_thread.c +158 -0
  35. data/src/ruby/ext/grpc/rb_event_thread.h +37 -0
  36. data/src/ruby/ext/grpc/rb_grpc.c +336 -0
  37. data/src/ruby/ext/grpc/rb_grpc.h +85 -0
  38. data/src/ruby/ext/grpc/rb_grpc_imports.generated.c +560 -0
  39. data/src/ruby/ext/grpc/rb_grpc_imports.generated.h +843 -0
  40. data/src/ruby/ext/grpc/rb_loader.c +72 -0
  41. data/src/ruby/ext/grpc/rb_loader.h +40 -0
  42. data/src/ruby/ext/grpc/rb_server.c +400 -0
  43. data/src/ruby/ext/grpc/rb_server.h +47 -0
  44. data/src/ruby/ext/grpc/rb_server_credentials.c +284 -0
  45. data/src/ruby/ext/grpc/rb_server_credentials.h +47 -0
  46. data/src/ruby/lib/grpc.rb +44 -0
  47. data/src/ruby/lib/grpc/2.0/grpc_c.bundle +0 -0
  48. data/src/ruby/lib/grpc/2.1/grpc_c.bundle +0 -0
  49. data/src/ruby/lib/grpc/2.2/grpc_c.bundle +0 -0
  50. data/src/ruby/lib/grpc/2.3/grpc_c.bundle +0 -0
  51. data/src/ruby/lib/grpc/core/time_consts.rb +71 -0
  52. data/src/ruby/lib/grpc/errors.rb +62 -0
  53. data/src/ruby/lib/grpc/generic/active_call.rb +488 -0
  54. data/src/ruby/lib/grpc/generic/bidi_call.rb +218 -0
  55. data/src/ruby/lib/grpc/generic/client_stub.rb +471 -0
  56. data/src/ruby/lib/grpc/generic/rpc_desc.rb +147 -0
  57. data/src/ruby/lib/grpc/generic/rpc_server.rb +504 -0
  58. data/src/ruby/lib/grpc/generic/service.rb +234 -0
  59. data/src/ruby/lib/grpc/grpc.rb +34 -0
  60. data/src/ruby/lib/grpc/logconfig.rb +59 -0
  61. data/src/ruby/lib/grpc/notifier.rb +60 -0
  62. data/src/ruby/lib/grpc/version.rb +33 -0
  63. data/src/ruby/pb/README.md +42 -0
  64. data/src/ruby/pb/generate_proto_ruby.sh +51 -0
  65. data/src/ruby/pb/grpc/health/checker.rb +75 -0
  66. data/src/ruby/pb/grpc/health/v1alpha/health.rb +29 -0
  67. data/src/ruby/pb/grpc/health/v1alpha/health_services.rb +28 -0
  68. data/src/ruby/pb/test/client.rb +469 -0
  69. data/src/ruby/pb/test/proto/empty.rb +15 -0
  70. data/src/ruby/pb/test/proto/messages.rb +80 -0
  71. data/src/ruby/pb/test/proto/test.rb +14 -0
  72. data/src/ruby/pb/test/proto/test_services.rb +64 -0
  73. data/src/ruby/pb/test/server.rb +253 -0
  74. data/src/ruby/spec/call_credentials_spec.rb +57 -0
  75. data/src/ruby/spec/call_spec.rb +163 -0
  76. data/src/ruby/spec/channel_credentials_spec.rb +97 -0
  77. data/src/ruby/spec/channel_spec.rb +177 -0
  78. data/src/ruby/spec/client_server_spec.rb +475 -0
  79. data/src/ruby/spec/completion_queue_spec.rb +42 -0
  80. data/src/ruby/spec/generic/active_call_spec.rb +373 -0
  81. data/src/ruby/spec/generic/client_stub_spec.rb +476 -0
  82. data/src/ruby/spec/generic/rpc_desc_spec.rb +331 -0
  83. data/src/ruby/spec/generic/rpc_server_pool_spec.rb +138 -0
  84. data/src/ruby/spec/generic/rpc_server_spec.rb +576 -0
  85. data/src/ruby/spec/generic/service_spec.rb +345 -0
  86. data/src/ruby/spec/pb/health/checker_spec.rb +232 -0
  87. data/src/ruby/spec/server_credentials_spec.rb +94 -0
  88. data/src/ruby/spec/server_spec.rb +209 -0
  89. data/src/ruby/spec/spec_helper.rb +69 -0
  90. data/src/ruby/spec/testdata/README +1 -0
  91. data/src/ruby/spec/testdata/ca.pem +15 -0
  92. data/src/ruby/spec/testdata/server1.key +16 -0
  93. data/src/ruby/spec/testdata/server1.pem +16 -0
  94. data/src/ruby/spec/time_consts_spec.rb +89 -0
  95. metadata +319 -0
@@ -0,0 +1,147 @@
1
+ # Copyright 2015-2016, Google Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are
6
+ # met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above
11
+ # copyright notice, this list of conditions and the following disclaimer
12
+ # in the documentation and/or other materials provided with the
13
+ # distribution.
14
+ # * Neither the name of Google Inc. nor the names of its
15
+ # contributors may be used to endorse or promote products derived from
16
+ # this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require 'grpc/grpc'
31
+
32
+ # GRPC contains the General RPC module.
33
+ module GRPC
34
+ # RpcDesc is a Descriptor of an RPC method.
35
+ class RpcDesc < Struct.new(:name, :input, :output, :marshal_method,
36
+ :unmarshal_method)
37
+ include Core::StatusCodes
38
+
39
+ # Used to wrap a message class to indicate that it needs to be streamed.
40
+ class Stream
41
+ attr_accessor :type
42
+
43
+ def initialize(type)
44
+ @type = type
45
+ end
46
+ end
47
+
48
+ # @return [Proc] { |instance| marshalled(instance) }
49
+ def marshal_proc
50
+ proc { |o| o.class.method(marshal_method).call(o).to_s }
51
+ end
52
+
53
+ # @param [:input, :output] target determines whether to produce the an
54
+ # unmarshal Proc for the rpc input parameter or
55
+ # its output parameter
56
+ #
57
+ # @return [Proc] An unmarshal proc { |marshalled(instance)| instance }
58
+ def unmarshal_proc(target)
59
+ fail ArgumentError unless [:input, :output].include?(target)
60
+ unmarshal_class = method(target).call
61
+ unmarshal_class = unmarshal_class.type if unmarshal_class.is_a? Stream
62
+ proc { |o| unmarshal_class.method(unmarshal_method).call(o) }
63
+ end
64
+
65
+ def run_server_method(active_call, mth)
66
+ # While a server method is running, it might be cancelled, its deadline
67
+ # might be reached, the handler could throw an unknown error, or a
68
+ # well-behaved handler could throw a StatusError.
69
+ if request_response?
70
+ req = active_call.remote_read
71
+ resp = mth.call(req, active_call.single_req_view)
72
+ active_call.remote_send(resp)
73
+ elsif client_streamer?
74
+ resp = mth.call(active_call.multi_req_view)
75
+ active_call.remote_send(resp)
76
+ elsif server_streamer?
77
+ req = active_call.remote_read
78
+ replys = mth.call(req, active_call.single_req_view)
79
+ replys.each { |r| active_call.remote_send(r) }
80
+ else # is a bidi_stream
81
+ active_call.run_server_bidi(mth)
82
+ end
83
+ send_status(active_call, OK, 'OK', **active_call.output_metadata)
84
+ rescue BadStatus => e
85
+ # this is raised by handlers that want GRPC to send an application error
86
+ # code and detail message and some additional app-specific metadata.
87
+ GRPC.logger.debug("app err:#{active_call}, status:#{e.code}:#{e.details}")
88
+ send_status(active_call, e.code, e.details, **e.metadata)
89
+ rescue Core::CallError => e
90
+ # This is raised by GRPC internals but should rarely, if ever happen.
91
+ # Log it, but don't notify the other endpoint..
92
+ GRPC.logger.warn("failed call: #{active_call}\n#{e}")
93
+ rescue Core::OutOfTime
94
+ # This is raised when active_call#method.call exceeeds the deadline
95
+ # event. Send a status of deadline exceeded
96
+ GRPC.logger.warn("late call: #{active_call}")
97
+ send_status(active_call, DEADLINE_EXCEEDED, 'late')
98
+ rescue StandardError => e
99
+ # This will usuaally be an unhandled error in the handling code.
100
+ # Send back a UNKNOWN status to the client
101
+ GRPC.logger.warn("failed handler: #{active_call}; sending status:UNKNOWN")
102
+ GRPC.logger.warn(e)
103
+ send_status(active_call, UNKNOWN, 'no reason given')
104
+ end
105
+
106
+ def assert_arity_matches(mth)
107
+ if request_response? || server_streamer?
108
+ if mth.arity != 2
109
+ fail arity_error(mth, 2, "should be #{mth.name}(req, call)")
110
+ end
111
+ else
112
+ if mth.arity != 1
113
+ fail arity_error(mth, 1, "should be #{mth.name}(call)")
114
+ end
115
+ end
116
+ end
117
+
118
+ def request_response?
119
+ !input.is_a?(Stream) && !output.is_a?(Stream)
120
+ end
121
+
122
+ def client_streamer?
123
+ input.is_a?(Stream) && !output.is_a?(Stream)
124
+ end
125
+
126
+ def server_streamer?
127
+ !input.is_a?(Stream) && output.is_a?(Stream)
128
+ end
129
+
130
+ def bidi_streamer?
131
+ input.is_a?(Stream) && output.is_a?(Stream)
132
+ end
133
+
134
+ def arity_error(mth, want, msg)
135
+ "##{mth.name}: bad arg count; got:#{mth.arity}, want:#{want}, #{msg}"
136
+ end
137
+
138
+ def send_status(active_client, code, details, **kw)
139
+ details = 'Not sure why' if details.nil?
140
+ GRPC.logger.debug("Sending status #{code}:#{details}")
141
+ active_client.send_status(code, details, code == OK, **kw)
142
+ rescue StandardError => e
143
+ GRPC.logger.warn("Could not send status #{code}:#{details}")
144
+ GRPC.logger.warn(e)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,504 @@
1
+ # Copyright 2015-2016, Google Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are
6
+ # met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above
11
+ # copyright notice, this list of conditions and the following disclaimer
12
+ # in the documentation and/or other materials provided with the
13
+ # distribution.
14
+ # * Neither the name of Google Inc. nor the names of its
15
+ # contributors may be used to endorse or promote products derived from
16
+ # this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+
30
+ require 'grpc/grpc'
31
+ require 'grpc/generic/active_call'
32
+ require 'grpc/generic/service'
33
+ require 'thread'
34
+
35
+ # A global that contains signals the gRPC servers should respond to.
36
+ $grpc_signals = []
37
+
38
+ # GRPC contains the General RPC module.
39
+ module GRPC
40
+ # Handles the signals in $grpc_signals.
41
+ #
42
+ # @return false if the server should exit, true if not.
43
+ def handle_signals
44
+ loop do
45
+ sig = $grpc_signals.shift
46
+ case sig
47
+ when 'INT'
48
+ return false
49
+ when 'TERM'
50
+ return false
51
+ when nil
52
+ return true
53
+ end
54
+ end
55
+ true
56
+ end
57
+ module_function :handle_signals
58
+
59
+ # Sets up a signal handler that adds signals to the signal handling global.
60
+ #
61
+ # Signal handlers should do as little as humanly possible.
62
+ # Here, they just add themselves to $grpc_signals
63
+ #
64
+ # RpcServer (and later other parts of gRPC) monitors the signals
65
+ # $grpc_signals in its own non-signal context.
66
+ def trap_signals
67
+ %w(INT TERM).each { |sig| trap(sig) { $grpc_signals << sig } }
68
+ end
69
+ module_function :trap_signals
70
+
71
+ # Pool is a simple thread pool.
72
+ class Pool
73
+ # Default keep alive period is 1s
74
+ DEFAULT_KEEP_ALIVE = 1
75
+
76
+ def initialize(size, keep_alive: DEFAULT_KEEP_ALIVE)
77
+ fail 'pool size must be positive' unless size > 0
78
+ @jobs = Queue.new
79
+ @size = size
80
+ @stopped = false
81
+ @stop_mutex = Mutex.new # needs to be held when accessing @stopped
82
+ @stop_cond = ConditionVariable.new
83
+ @workers = []
84
+ @keep_alive = keep_alive
85
+ end
86
+
87
+ # Returns the number of jobs waiting
88
+ def jobs_waiting
89
+ @jobs.size
90
+ end
91
+
92
+ # Runs the given block on the queue with the provided args.
93
+ #
94
+ # @param args the args passed blk when it is called
95
+ # @param blk the block to call
96
+ def schedule(*args, &blk)
97
+ return if blk.nil?
98
+ @stop_mutex.synchronize do
99
+ if @stopped
100
+ GRPC.logger.warn('did not schedule job, already stopped')
101
+ return
102
+ end
103
+ GRPC.logger.info('schedule another job')
104
+ @jobs << [blk, args]
105
+ end
106
+ end
107
+
108
+ # Starts running the jobs in the thread pool.
109
+ def start
110
+ fail 'already stopped' if @stopped
111
+ until @workers.size == @size.to_i
112
+ next_thread = Thread.new do
113
+ catch(:exit) do # allows { throw :exit } to kill a thread
114
+ loop_execute_jobs
115
+ end
116
+ remove_current_thread
117
+ end
118
+ @workers << next_thread
119
+ end
120
+ end
121
+
122
+ # Stops the jobs in the pool
123
+ def stop
124
+ GRPC.logger.info('stopping, will wait for all the workers to exit')
125
+ @workers.size.times { schedule { throw :exit } }
126
+ @stop_mutex.synchronize do # wait @keep_alive for works to stop
127
+ @stopped = true
128
+ @stop_cond.wait(@stop_mutex, @keep_alive) if @workers.size > 0
129
+ end
130
+ forcibly_stop_workers
131
+ GRPC.logger.info('stopped, all workers are shutdown')
132
+ end
133
+
134
+ protected
135
+
136
+ # Forcibly shutdown any threads that are still alive.
137
+ def forcibly_stop_workers
138
+ return unless @workers.size > 0
139
+ GRPC.logger.info("forcibly terminating #{@workers.size} worker(s)")
140
+ @workers.each do |t|
141
+ next unless t.alive?
142
+ begin
143
+ t.exit
144
+ rescue StandardError => e
145
+ GRPC.logger.warn('error while terminating a worker')
146
+ GRPC.logger.warn(e)
147
+ end
148
+ end
149
+ end
150
+
151
+ # removes the threads from workers, and signal when all the
152
+ # threads are complete.
153
+ def remove_current_thread
154
+ @stop_mutex.synchronize do
155
+ @workers.delete(Thread.current)
156
+ @stop_cond.signal if @workers.size.zero?
157
+ end
158
+ end
159
+
160
+ def loop_execute_jobs
161
+ loop do
162
+ begin
163
+ blk, args = @jobs.pop
164
+ blk.call(*args)
165
+ rescue StandardError => e
166
+ GRPC.logger.warn('Error in worker thread')
167
+ GRPC.logger.warn(e)
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ # RpcServer hosts a number of services and makes them available on the
174
+ # network.
175
+ class RpcServer
176
+ include Core::CallOps
177
+ include Core::TimeConsts
178
+ extend ::Forwardable
179
+
180
+ def_delegators :@server, :add_http2_port
181
+
182
+ # Default thread pool size is 3
183
+ DEFAULT_POOL_SIZE = 3
184
+
185
+ # Default max_waiting_requests size is 20
186
+ DEFAULT_MAX_WAITING_REQUESTS = 20
187
+
188
+ # Default poll period is 1s
189
+ DEFAULT_POLL_PERIOD = 1
190
+
191
+ # Signal check period is 0.25s
192
+ SIGNAL_CHECK_PERIOD = 0.25
193
+
194
+ # setup_cq is used by #initialize to constuct a Core::CompletionQueue from
195
+ # its arguments.
196
+ def self.setup_cq(alt_cq)
197
+ return Core::CompletionQueue.new if alt_cq.nil?
198
+ unless alt_cq.is_a? Core::CompletionQueue
199
+ fail(TypeError, '!CompletionQueue')
200
+ end
201
+ alt_cq
202
+ end
203
+
204
+ # setup_srv is used by #initialize to constuct a Core::Server from its
205
+ # arguments.
206
+ def self.setup_srv(alt_srv, cq, **kw)
207
+ return Core::Server.new(cq, kw) if alt_srv.nil?
208
+ fail(TypeError, '!Server') unless alt_srv.is_a? Core::Server
209
+ alt_srv
210
+ end
211
+
212
+ # setup_connect_md_proc is used by #initialize to validate the
213
+ # connect_md_proc.
214
+ def self.setup_connect_md_proc(a_proc)
215
+ return nil if a_proc.nil?
216
+ fail(TypeError, '!Proc') unless a_proc.is_a? Proc
217
+ a_proc
218
+ end
219
+
220
+ # Creates a new RpcServer.
221
+ #
222
+ # The RPC server is configured using keyword arguments.
223
+ #
224
+ # There are some specific keyword args used to configure the RpcServer
225
+ # instance, however other arbitrary are allowed and when present are used
226
+ # to configure the listeninng connection set up by the RpcServer.
227
+ #
228
+ # * server_override: which if passed must be a [GRPC::Core::Server]. When
229
+ # present.
230
+ #
231
+ # * poll_period: when present, the server polls for new events with this
232
+ # period
233
+ #
234
+ # * pool_size: the size of the thread pool the server uses to run its
235
+ # threads
236
+ #
237
+ # * completion_queue_override: when supplied, this will be used as the
238
+ # completion_queue that the server uses to receive network events,
239
+ # otherwise its creates a new instance itself
240
+ #
241
+ # * creds: [GRPC::Core::ServerCredentials]
242
+ # the credentials used to secure the server
243
+ #
244
+ # * max_waiting_requests: the maximum number of requests that are not
245
+ # being handled to allow. When this limit is exceeded, the server responds
246
+ # with not available to new requests
247
+ #
248
+ # * connect_md_proc:
249
+ # when non-nil is a proc for determining metadata to to send back the client
250
+ # on receiving an invocation req. The proc signature is:
251
+ # {key: val, ..} func(method_name, {key: val, ...})
252
+ def initialize(pool_size:DEFAULT_POOL_SIZE,
253
+ max_waiting_requests:DEFAULT_MAX_WAITING_REQUESTS,
254
+ poll_period:DEFAULT_POLL_PERIOD,
255
+ completion_queue_override:nil,
256
+ server_override:nil,
257
+ connect_md_proc:nil,
258
+ **kw)
259
+ @connect_md_proc = RpcServer.setup_connect_md_proc(connect_md_proc)
260
+ @cq = RpcServer.setup_cq(completion_queue_override)
261
+ @max_waiting_requests = max_waiting_requests
262
+ @poll_period = poll_period
263
+ @pool_size = pool_size
264
+ @pool = Pool.new(@pool_size)
265
+ @run_cond = ConditionVariable.new
266
+ @run_mutex = Mutex.new
267
+ @running = false
268
+ @server = RpcServer.setup_srv(server_override, @cq, **kw)
269
+ @stopped = false
270
+ @stop_mutex = Mutex.new
271
+ end
272
+
273
+ # stops a running server
274
+ #
275
+ # the call has no impact if the server is already stopped, otherwise
276
+ # server's current call loop is it's last.
277
+ def stop
278
+ return unless @running
279
+ @stop_mutex.synchronize do
280
+ @stopped = true
281
+ end
282
+ deadline = from_relative_time(@poll_period)
283
+ return if @server.close(@cq, deadline)
284
+ deadline = from_relative_time(@poll_period)
285
+ @server.close(@cq, deadline)
286
+ @pool.stop
287
+ end
288
+
289
+ # determines if the server has been stopped
290
+ def stopped?
291
+ @stop_mutex.synchronize do
292
+ return @stopped
293
+ end
294
+ end
295
+
296
+ # determines if the server is currently running
297
+ def running?
298
+ @running
299
+ end
300
+
301
+ # Is called from other threads to wait for #run to start up the server.
302
+ #
303
+ # If run has not been called, this returns immediately.
304
+ #
305
+ # @param timeout [Numeric] number of seconds to wait
306
+ # @result [true, false] true if the server is running, false otherwise
307
+ def wait_till_running(timeout = 0.1)
308
+ end_time, sleep_period = Time.now + timeout, (1.0 * timeout) / 100
309
+ while Time.now < end_time
310
+ @run_mutex.synchronize { @run_cond.wait(@run_mutex) } unless running?
311
+ sleep(sleep_period)
312
+ end
313
+ running?
314
+ end
315
+
316
+ # Runs the server in its own thread, then waits for signal INT or TERM on
317
+ # the current thread to terminate it.
318
+ def run_till_terminated
319
+ GRPC.trap_signals
320
+ t = Thread.new { run }
321
+ wait_till_running
322
+ loop do
323
+ sleep SIGNAL_CHECK_PERIOD
324
+ break unless GRPC.handle_signals
325
+ end
326
+ stop
327
+ t.join
328
+ end
329
+
330
+ # handle registration of classes
331
+ #
332
+ # service is either a class that includes GRPC::GenericService and whose
333
+ # #new function can be called without argument or any instance of such a
334
+ # class.
335
+ #
336
+ # E.g, after
337
+ #
338
+ # class Divider
339
+ # include GRPC::GenericService
340
+ # rpc :div DivArgs, DivReply # single request, single response
341
+ # def initialize(optional_arg='default option') # no args
342
+ # ...
343
+ # end
344
+ #
345
+ # srv = GRPC::RpcServer.new(...)
346
+ #
347
+ # # Either of these works
348
+ #
349
+ # srv.handle(Divider)
350
+ #
351
+ # # or
352
+ #
353
+ # srv.handle(Divider.new('replace optional arg'))
354
+ #
355
+ # It raises RuntimeError:
356
+ # - if service is not valid service class or object
357
+ # - its handler methods are already registered
358
+ # - if the server is already running
359
+ #
360
+ # @param service [Object|Class] a service class or object as described
361
+ # above
362
+ def handle(service)
363
+ fail 'cannot add services if the server is running' if running?
364
+ fail 'cannot add services if the server is stopped' if stopped?
365
+ cls = service.is_a?(Class) ? service : service.class
366
+ assert_valid_service_class(cls)
367
+ add_rpc_descs_for(service)
368
+ end
369
+
370
+ # runs the server
371
+ #
372
+ # - if no rpc_descs are registered, this exits immediately, otherwise it
373
+ # continues running permanently and does not return until program exit.
374
+ #
375
+ # - #running? returns true after this is called, until #stop cause the
376
+ # the server to stop.
377
+ def run
378
+ if rpc_descs.size.zero?
379
+ GRPC.logger.warn('did not run as no services were present')
380
+ return
381
+ end
382
+ @run_mutex.synchronize do
383
+ @running = true
384
+ @run_cond.signal
385
+ end
386
+ @pool.start
387
+ @server.start
388
+ loop_handle_server_calls
389
+ end
390
+
391
+ # Sends UNAVAILABLE if there are too many unprocessed jobs
392
+ def available?(an_rpc)
393
+ jobs_count, max = @pool.jobs_waiting, @max_waiting_requests
394
+ GRPC.logger.info("waiting: #{jobs_count}, max: #{max}")
395
+ return an_rpc if @pool.jobs_waiting <= @max_waiting_requests
396
+ GRPC.logger.warn("NOT AVAILABLE: too many jobs_waiting: #{an_rpc}")
397
+ noop = proc { |x| x }
398
+ c = ActiveCall.new(an_rpc.call, @cq, noop, noop, an_rpc.deadline)
399
+ c.send_status(StatusCodes::UNAVAILABLE, '')
400
+ nil
401
+ end
402
+
403
+ # Sends UNIMPLEMENTED if the method is not implemented by this server
404
+ def implemented?(an_rpc)
405
+ mth = an_rpc.method.to_sym
406
+ return an_rpc if rpc_descs.key?(mth)
407
+ GRPC.logger.warn("UNIMPLEMENTED: #{an_rpc}")
408
+ noop = proc { |x| x }
409
+ c = ActiveCall.new(an_rpc.call, @cq, noop, noop, an_rpc.deadline)
410
+ c.send_status(StatusCodes::UNIMPLEMENTED, '')
411
+ nil
412
+ end
413
+
414
+ # handles calls to the server
415
+ def loop_handle_server_calls
416
+ fail 'not running' unless @running
417
+ loop_tag = Object.new
418
+ until stopped?
419
+ begin
420
+ an_rpc = @server.request_call(@cq, loop_tag, INFINITE_FUTURE)
421
+ break if (!an_rpc.nil?) && an_rpc.call.nil?
422
+
423
+ active_call = new_active_server_call(an_rpc)
424
+ unless active_call.nil?
425
+ @pool.schedule(active_call) do |ac|
426
+ c, mth = ac
427
+ rpc_descs[mth].run_server_method(c, rpc_handlers[mth])
428
+ end
429
+ end
430
+ rescue Core::CallError, RuntimeError => e
431
+ # these might happen for various reasonse. The correct behaviour of
432
+ # the server is to log them and continue, if it's not shutting down.
433
+ GRPC.logger.warn("server call failed: #{e}") unless stopped?
434
+ next
435
+ end
436
+ end
437
+ @running = false
438
+ GRPC.logger.info("stopped: #{self}")
439
+ end
440
+
441
+ def new_active_server_call(an_rpc)
442
+ return nil if an_rpc.nil? || an_rpc.call.nil?
443
+
444
+ # allow the metadata to be accessed from the call
445
+ handle_call_tag = Object.new
446
+ an_rpc.call.metadata = an_rpc.metadata # attaches md to call for handlers
447
+ GRPC.logger.debug("call md is #{an_rpc.metadata}")
448
+ connect_md = nil
449
+ unless @connect_md_proc.nil?
450
+ connect_md = @connect_md_proc.call(an_rpc.method, an_rpc.metadata)
451
+ end
452
+ an_rpc.call.run_batch(@cq, handle_call_tag, INFINITE_FUTURE,
453
+ SEND_INITIAL_METADATA => connect_md)
454
+ return nil unless available?(an_rpc)
455
+ return nil unless implemented?(an_rpc)
456
+
457
+ # Create the ActiveCall
458
+ GRPC.logger.info("deadline is #{an_rpc.deadline}; (now=#{Time.now})")
459
+ rpc_desc = rpc_descs[an_rpc.method.to_sym]
460
+ c = ActiveCall.new(an_rpc.call, @cq,
461
+ rpc_desc.marshal_proc, rpc_desc.unmarshal_proc(:input),
462
+ an_rpc.deadline)
463
+ mth = an_rpc.method.to_sym
464
+ [c, mth]
465
+ end
466
+
467
+ protected
468
+
469
+ def rpc_descs
470
+ @rpc_descs ||= {}
471
+ end
472
+
473
+ def rpc_handlers
474
+ @rpc_handlers ||= {}
475
+ end
476
+
477
+ def assert_valid_service_class(cls)
478
+ unless cls.include?(GenericService)
479
+ fail "#{cls} must 'include GenericService'"
480
+ end
481
+ if cls.rpc_descs.size.zero?
482
+ fail "#{cls} should specify some rpc descriptions"
483
+ end
484
+ cls.assert_rpc_descs_have_methods
485
+ end
486
+
487
+ def add_rpc_descs_for(service)
488
+ cls = service.is_a?(Class) ? service : service.class
489
+ specs, handlers = rpc_descs, rpc_handlers
490
+ cls.rpc_descs.each_pair do |name, spec|
491
+ route = "/#{cls.service_name}/#{name}".to_sym
492
+ fail "already registered: rpc #{route} from #{spec}" if specs.key? route
493
+ specs[route] = spec
494
+ rpc_name = GenericService.underscore(name.to_s).to_sym
495
+ if service.is_a?(Class)
496
+ handlers[route] = cls.new.method(rpc_name)
497
+ else
498
+ handlers[route] = service.method(rpc_name)
499
+ end
500
+ GRPC.logger.info("handling #{route} with #{handlers[route]}")
501
+ end
502
+ end
503
+ end
504
+ end