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.
- checksums.yaml +7 -0
- data/etc/roots.pem +5114 -0
- data/grpc_c.32.ruby +0 -0
- data/grpc_c.64.ruby +0 -0
- data/src/ruby/bin/apis/google/protobuf/empty.rb +44 -0
- data/src/ruby/bin/apis/pubsub_demo.rb +256 -0
- data/src/ruby/bin/apis/tech/pubsub/proto/pubsub.rb +174 -0
- data/src/ruby/bin/apis/tech/pubsub/proto/pubsub_services.rb +103 -0
- data/src/ruby/bin/grpc_ruby_interop_client +33 -0
- data/src/ruby/bin/grpc_ruby_interop_server +33 -0
- data/src/ruby/bin/interop/interop_client.rb +51 -0
- data/src/ruby/bin/interop/interop_server.rb +50 -0
- data/src/ruby/bin/math.rb +32 -0
- data/src/ruby/bin/math_client.rb +147 -0
- data/src/ruby/bin/math_server.rb +206 -0
- data/src/ruby/bin/math_services.rb +27 -0
- data/src/ruby/bin/noproto_client.rb +108 -0
- data/src/ruby/bin/noproto_server.rb +112 -0
- data/src/ruby/ext/grpc/extconf.rb +129 -0
- data/src/ruby/ext/grpc/rb_byte_buffer.c +70 -0
- data/src/ruby/ext/grpc/rb_byte_buffer.h +47 -0
- data/src/ruby/ext/grpc/rb_call.c +908 -0
- data/src/ruby/ext/grpc/rb_call.h +66 -0
- data/src/ruby/ext/grpc/rb_call_credentials.c +319 -0
- data/src/ruby/ext/grpc/rb_call_credentials.h +46 -0
- data/src/ruby/ext/grpc/rb_channel.c +432 -0
- data/src/ruby/ext/grpc/rb_channel.h +47 -0
- data/src/ruby/ext/grpc/rb_channel_args.c +169 -0
- data/src/ruby/ext/grpc/rb_channel_args.h +53 -0
- data/src/ruby/ext/grpc/rb_channel_credentials.c +268 -0
- data/src/ruby/ext/grpc/rb_channel_credentials.h +47 -0
- data/src/ruby/ext/grpc/rb_completion_queue.c +183 -0
- data/src/ruby/ext/grpc/rb_completion_queue.h +55 -0
- data/src/ruby/ext/grpc/rb_event_thread.c +158 -0
- data/src/ruby/ext/grpc/rb_event_thread.h +37 -0
- data/src/ruby/ext/grpc/rb_grpc.c +336 -0
- data/src/ruby/ext/grpc/rb_grpc.h +85 -0
- data/src/ruby/ext/grpc/rb_grpc_imports.generated.c +560 -0
- data/src/ruby/ext/grpc/rb_grpc_imports.generated.h +843 -0
- data/src/ruby/ext/grpc/rb_loader.c +72 -0
- data/src/ruby/ext/grpc/rb_loader.h +40 -0
- data/src/ruby/ext/grpc/rb_server.c +400 -0
- data/src/ruby/ext/grpc/rb_server.h +47 -0
- data/src/ruby/ext/grpc/rb_server_credentials.c +284 -0
- data/src/ruby/ext/grpc/rb_server_credentials.h +47 -0
- data/src/ruby/lib/grpc.rb +44 -0
- data/src/ruby/lib/grpc/2.0/grpc_c.bundle +0 -0
- data/src/ruby/lib/grpc/2.1/grpc_c.bundle +0 -0
- data/src/ruby/lib/grpc/2.2/grpc_c.bundle +0 -0
- data/src/ruby/lib/grpc/2.3/grpc_c.bundle +0 -0
- data/src/ruby/lib/grpc/core/time_consts.rb +71 -0
- data/src/ruby/lib/grpc/errors.rb +62 -0
- data/src/ruby/lib/grpc/generic/active_call.rb +488 -0
- data/src/ruby/lib/grpc/generic/bidi_call.rb +218 -0
- data/src/ruby/lib/grpc/generic/client_stub.rb +471 -0
- data/src/ruby/lib/grpc/generic/rpc_desc.rb +147 -0
- data/src/ruby/lib/grpc/generic/rpc_server.rb +504 -0
- data/src/ruby/lib/grpc/generic/service.rb +234 -0
- data/src/ruby/lib/grpc/grpc.rb +34 -0
- data/src/ruby/lib/grpc/logconfig.rb +59 -0
- data/src/ruby/lib/grpc/notifier.rb +60 -0
- data/src/ruby/lib/grpc/version.rb +33 -0
- data/src/ruby/pb/README.md +42 -0
- data/src/ruby/pb/generate_proto_ruby.sh +51 -0
- data/src/ruby/pb/grpc/health/checker.rb +75 -0
- data/src/ruby/pb/grpc/health/v1alpha/health.rb +29 -0
- data/src/ruby/pb/grpc/health/v1alpha/health_services.rb +28 -0
- data/src/ruby/pb/test/client.rb +469 -0
- data/src/ruby/pb/test/proto/empty.rb +15 -0
- data/src/ruby/pb/test/proto/messages.rb +80 -0
- data/src/ruby/pb/test/proto/test.rb +14 -0
- data/src/ruby/pb/test/proto/test_services.rb +64 -0
- data/src/ruby/pb/test/server.rb +253 -0
- data/src/ruby/spec/call_credentials_spec.rb +57 -0
- data/src/ruby/spec/call_spec.rb +163 -0
- data/src/ruby/spec/channel_credentials_spec.rb +97 -0
- data/src/ruby/spec/channel_spec.rb +177 -0
- data/src/ruby/spec/client_server_spec.rb +475 -0
- data/src/ruby/spec/completion_queue_spec.rb +42 -0
- data/src/ruby/spec/generic/active_call_spec.rb +373 -0
- data/src/ruby/spec/generic/client_stub_spec.rb +476 -0
- data/src/ruby/spec/generic/rpc_desc_spec.rb +331 -0
- data/src/ruby/spec/generic/rpc_server_pool_spec.rb +138 -0
- data/src/ruby/spec/generic/rpc_server_spec.rb +576 -0
- data/src/ruby/spec/generic/service_spec.rb +345 -0
- data/src/ruby/spec/pb/health/checker_spec.rb +232 -0
- data/src/ruby/spec/server_credentials_spec.rb +94 -0
- data/src/ruby/spec/server_spec.rb +209 -0
- data/src/ruby/spec/spec_helper.rb +69 -0
- data/src/ruby/spec/testdata/README +1 -0
- data/src/ruby/spec/testdata/ca.pem +15 -0
- data/src/ruby/spec/testdata/server1.key +16 -0
- data/src/ruby/spec/testdata/server1.pem +16 -0
- data/src/ruby/spec/time_consts_spec.rb +89 -0
- 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
|