grpc 0.5.0
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/.gitignore +15 -0
- data/.rspec +1 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +52 -0
- data/Gemfile +4 -0
- data/README.md +82 -0
- data/Rakefile +54 -0
- data/bin/apis/google/protobuf/empty.rb +44 -0
- data/bin/apis/pubsub_demo.rb +267 -0
- data/bin/apis/tech/pubsub/proto/pubsub.rb +174 -0
- data/bin/apis/tech/pubsub/proto/pubsub_services.rb +103 -0
- data/bin/interop/README.md +8 -0
- data/bin/interop/interop_client.rb +334 -0
- data/bin/interop/interop_server.rb +192 -0
- data/bin/interop/test/cpp/interop/empty.rb +44 -0
- data/bin/interop/test/cpp/interop/messages.rb +89 -0
- data/bin/interop/test/cpp/interop/test.rb +43 -0
- data/bin/interop/test/cpp/interop/test_services.rb +60 -0
- data/bin/math.proto +80 -0
- data/bin/math.rb +61 -0
- data/bin/math_client.rb +147 -0
- data/bin/math_server.rb +190 -0
- data/bin/math_services.rb +56 -0
- data/bin/noproto_client.rb +108 -0
- data/bin/noproto_server.rb +112 -0
- data/ext/grpc/extconf.rb +76 -0
- data/ext/grpc/rb_byte_buffer.c +241 -0
- data/ext/grpc/rb_byte_buffer.h +54 -0
- data/ext/grpc/rb_call.c +569 -0
- data/ext/grpc/rb_call.h +59 -0
- data/ext/grpc/rb_channel.c +264 -0
- data/ext/grpc/rb_channel.h +49 -0
- data/ext/grpc/rb_channel_args.c +154 -0
- data/ext/grpc/rb_channel_args.h +52 -0
- data/ext/grpc/rb_completion_queue.c +185 -0
- data/ext/grpc/rb_completion_queue.h +50 -0
- data/ext/grpc/rb_credentials.c +281 -0
- data/ext/grpc/rb_credentials.h +50 -0
- data/ext/grpc/rb_event.c +361 -0
- data/ext/grpc/rb_event.h +53 -0
- data/ext/grpc/rb_grpc.c +274 -0
- data/ext/grpc/rb_grpc.h +74 -0
- data/ext/grpc/rb_metadata.c +215 -0
- data/ext/grpc/rb_metadata.h +53 -0
- data/ext/grpc/rb_server.c +278 -0
- data/ext/grpc/rb_server.h +50 -0
- data/ext/grpc/rb_server_credentials.c +210 -0
- data/ext/grpc/rb_server_credentials.h +50 -0
- data/grpc.gemspec +41 -0
- data/lib/grpc.rb +39 -0
- data/lib/grpc/core/event.rb +44 -0
- data/lib/grpc/core/time_consts.rb +71 -0
- data/lib/grpc/errors.rb +61 -0
- data/lib/grpc/generic/active_call.rb +536 -0
- data/lib/grpc/generic/bidi_call.rb +221 -0
- data/lib/grpc/generic/client_stub.rb +413 -0
- data/lib/grpc/generic/rpc_desc.rb +150 -0
- data/lib/grpc/generic/rpc_server.rb +404 -0
- data/lib/grpc/generic/service.rb +235 -0
- data/lib/grpc/logconfig.rb +40 -0
- data/lib/grpc/version.rb +33 -0
- data/spec/alloc_spec.rb +44 -0
- data/spec/byte_buffer_spec.rb +67 -0
- data/spec/call_spec.rb +163 -0
- data/spec/channel_spec.rb +181 -0
- data/spec/client_server_spec.rb +372 -0
- data/spec/completion_queue_spec.rb +74 -0
- data/spec/credentials_spec.rb +71 -0
- data/spec/event_spec.rb +53 -0
- data/spec/generic/active_call_spec.rb +373 -0
- data/spec/generic/client_stub_spec.rb +519 -0
- data/spec/generic/rpc_desc_spec.rb +357 -0
- data/spec/generic/rpc_server_pool_spec.rb +139 -0
- data/spec/generic/rpc_server_spec.rb +404 -0
- data/spec/generic/service_spec.rb +342 -0
- data/spec/metadata_spec.rb +64 -0
- data/spec/server_credentials_spec.rb +69 -0
- data/spec/server_spec.rb +212 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/testdata/README +1 -0
- data/spec/testdata/ca.pem +15 -0
- data/spec/testdata/server1.key +16 -0
- data/spec/testdata/server1.pem +16 -0
- data/spec/time_consts_spec.rb +89 -0
- metadata +353 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
# Copyright 2015, 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')
|
84
|
+
rescue BadStatus => e
|
85
|
+
# this is raised by handlers that want GRPC to send an application
|
86
|
+
# error code and detail message.
|
87
|
+
logger.debug("app err: #{active_call}, status:#{e.code}:#{e.details}")
|
88
|
+
send_status(active_call, e.code, e.details)
|
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
|
+
logger.warn("failed call: #{active_call}\n#{e}")
|
93
|
+
rescue OutOfTime
|
94
|
+
# This is raised when active_call#method.call exceeeds the deadline
|
95
|
+
# event. Send a status of deadline exceeded
|
96
|
+
logger.warn("late call: #{active_call}")
|
97
|
+
send_status(active_call, DEADLINE_EXCEEDED, 'late')
|
98
|
+
rescue Core::EventError => e
|
99
|
+
# This is raised by GRPC internals but should rarely, if ever happen.
|
100
|
+
# Log it, but don't notify the other endpoint..
|
101
|
+
logger.warn("failed call: #{active_call}\n#{e}")
|
102
|
+
rescue StandardError => e
|
103
|
+
# This will usuaally be an unhandled error in the handling code.
|
104
|
+
# Send back a UNKNOWN status to the client
|
105
|
+
logger.warn("failed handler: #{active_call}; sending status:UNKNOWN")
|
106
|
+
logger.warn(e)
|
107
|
+
send_status(active_call, UNKNOWN, 'no reason given')
|
108
|
+
end
|
109
|
+
|
110
|
+
def assert_arity_matches(mth)
|
111
|
+
if request_response? || server_streamer?
|
112
|
+
if mth.arity != 2
|
113
|
+
fail arity_error(mth, 2, "should be #{mth.name}(req, call)")
|
114
|
+
end
|
115
|
+
else
|
116
|
+
if mth.arity != 1
|
117
|
+
fail arity_error(mth, 1, "should be #{mth.name}(call)")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def request_response?
|
123
|
+
!input.is_a?(Stream) && !output.is_a?(Stream)
|
124
|
+
end
|
125
|
+
|
126
|
+
def client_streamer?
|
127
|
+
input.is_a?(Stream) && !output.is_a?(Stream)
|
128
|
+
end
|
129
|
+
|
130
|
+
def server_streamer?
|
131
|
+
!input.is_a?(Stream) && output.is_a?(Stream)
|
132
|
+
end
|
133
|
+
|
134
|
+
def bidi_streamer?
|
135
|
+
input.is_a?(Stream) && output.is_a?(Stream)
|
136
|
+
end
|
137
|
+
|
138
|
+
def arity_error(mth, want, msg)
|
139
|
+
"##{mth.name}: bad arg count; got:#{mth.arity}, want:#{want}, #{msg}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def send_status(active_client, code, details)
|
143
|
+
details = 'Not sure why' if details.nil?
|
144
|
+
active_client.send_status(code, details)
|
145
|
+
rescue StandardError => e
|
146
|
+
logger.warn("Could not send status #{code}:#{details}")
|
147
|
+
logger.warn(e)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,404 @@
|
|
1
|
+
# Copyright 2015, 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
|
+
require 'xray/thread_dump_signal_handler'
|
35
|
+
|
36
|
+
# GRPC contains the General RPC module.
|
37
|
+
module GRPC
|
38
|
+
# RpcServer hosts a number of services and makes them available on the
|
39
|
+
# network.
|
40
|
+
class RpcServer
|
41
|
+
include Core::CompletionType
|
42
|
+
include Core::TimeConsts
|
43
|
+
extend ::Forwardable
|
44
|
+
|
45
|
+
def_delegators :@server, :add_http2_port
|
46
|
+
|
47
|
+
# Default thread pool size is 3
|
48
|
+
DEFAULT_POOL_SIZE = 3
|
49
|
+
|
50
|
+
# Default max_waiting_requests size is 20
|
51
|
+
DEFAULT_MAX_WAITING_REQUESTS = 20
|
52
|
+
|
53
|
+
# Creates a new RpcServer.
|
54
|
+
#
|
55
|
+
# The RPC server is configured using keyword arguments.
|
56
|
+
#
|
57
|
+
# There are some specific keyword args used to configure the RpcServer
|
58
|
+
# instance, however other arbitrary are allowed and when present are used
|
59
|
+
# to configure the listeninng connection set up by the RpcServer.
|
60
|
+
#
|
61
|
+
# * server_override: which if passed must be a [GRPC::Core::Server]. When
|
62
|
+
# present.
|
63
|
+
#
|
64
|
+
# * poll_period: when present, the server polls for new events with this
|
65
|
+
# period
|
66
|
+
#
|
67
|
+
# * pool_size: the size of the thread pool the server uses to run its
|
68
|
+
# threads
|
69
|
+
#
|
70
|
+
# * completion_queue_override: when supplied, this will be used as the
|
71
|
+
# completion_queue that the server uses to receive network events,
|
72
|
+
# otherwise its creates a new instance itself
|
73
|
+
#
|
74
|
+
# * creds: [GRPC::Core::ServerCredentials]
|
75
|
+
# the credentials used to secure the server
|
76
|
+
#
|
77
|
+
# * max_waiting_requests: the maximum number of requests that are not
|
78
|
+
# being handled to allow. When this limit is exceeded, the server responds
|
79
|
+
# with not available to new requests
|
80
|
+
def initialize(pool_size:DEFAULT_POOL_SIZE,
|
81
|
+
max_waiting_requests:DEFAULT_MAX_WAITING_REQUESTS,
|
82
|
+
poll_period:INFINITE_FUTURE,
|
83
|
+
completion_queue_override:nil,
|
84
|
+
creds:nil,
|
85
|
+
server_override:nil,
|
86
|
+
**kw)
|
87
|
+
if completion_queue_override.nil?
|
88
|
+
cq = Core::CompletionQueue.new
|
89
|
+
else
|
90
|
+
cq = completion_queue_override
|
91
|
+
unless cq.is_a? Core::CompletionQueue
|
92
|
+
fail(ArgumentError, 'not a CompletionQueue')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
@cq = cq
|
96
|
+
|
97
|
+
if server_override.nil?
|
98
|
+
if creds.nil?
|
99
|
+
srv = Core::Server.new(@cq, kw)
|
100
|
+
elsif !creds.is_a? Core::ServerCredentials
|
101
|
+
fail(ArgumentError, 'not a ServerCredentials')
|
102
|
+
else
|
103
|
+
srv = Core::Server.new(@cq, kw, creds)
|
104
|
+
end
|
105
|
+
else
|
106
|
+
srv = server_override
|
107
|
+
fail(ArgumentError, 'not a Server') unless srv.is_a? Core::Server
|
108
|
+
end
|
109
|
+
@server = srv
|
110
|
+
|
111
|
+
@pool_size = pool_size
|
112
|
+
@max_waiting_requests = max_waiting_requests
|
113
|
+
@poll_period = poll_period
|
114
|
+
@run_mutex = Mutex.new
|
115
|
+
@run_cond = ConditionVariable.new
|
116
|
+
@pool = Pool.new(@pool_size)
|
117
|
+
end
|
118
|
+
|
119
|
+
# stops a running server
|
120
|
+
#
|
121
|
+
# the call has no impact if the server is already stopped, otherwise
|
122
|
+
# server's current call loop is it's last.
|
123
|
+
def stop
|
124
|
+
return unless @running
|
125
|
+
@stopped = true
|
126
|
+
@pool.stop
|
127
|
+
end
|
128
|
+
|
129
|
+
# determines if the server is currently running
|
130
|
+
def running?
|
131
|
+
@running ||= false
|
132
|
+
end
|
133
|
+
|
134
|
+
# Is called from other threads to wait for #run to start up the server.
|
135
|
+
#
|
136
|
+
# If run has not been called, this returns immediately.
|
137
|
+
#
|
138
|
+
# @param timeout [Numeric] number of seconds to wait
|
139
|
+
# @result [true, false] true if the server is running, false otherwise
|
140
|
+
def wait_till_running(timeout = 0.1)
|
141
|
+
end_time, sleep_period = Time.now + timeout, (1.0 * timeout) / 100
|
142
|
+
while Time.now < end_time
|
143
|
+
@run_mutex.synchronize { @run_cond.wait(@run_mutex) } unless running?
|
144
|
+
sleep(sleep_period)
|
145
|
+
end
|
146
|
+
running?
|
147
|
+
end
|
148
|
+
|
149
|
+
# determines if the server is currently stopped
|
150
|
+
def stopped?
|
151
|
+
@stopped ||= false
|
152
|
+
end
|
153
|
+
|
154
|
+
# handle registration of classes
|
155
|
+
#
|
156
|
+
# service is either a class that includes GRPC::GenericService and whose
|
157
|
+
# #new function can be called without argument or any instance of such a
|
158
|
+
# class.
|
159
|
+
#
|
160
|
+
# E.g, after
|
161
|
+
#
|
162
|
+
# class Divider
|
163
|
+
# include GRPC::GenericService
|
164
|
+
# rpc :div DivArgs, DivReply # single request, single response
|
165
|
+
# def initialize(optional_arg='default option') # no args
|
166
|
+
# ...
|
167
|
+
# end
|
168
|
+
#
|
169
|
+
# srv = GRPC::RpcServer.new(...)
|
170
|
+
#
|
171
|
+
# # Either of these works
|
172
|
+
#
|
173
|
+
# srv.handle(Divider)
|
174
|
+
#
|
175
|
+
# # or
|
176
|
+
#
|
177
|
+
# srv.handle(Divider.new('replace optional arg'))
|
178
|
+
#
|
179
|
+
# It raises RuntimeError:
|
180
|
+
# - if service is not valid service class or object
|
181
|
+
# - its handler methods are already registered
|
182
|
+
# - if the server is already running
|
183
|
+
#
|
184
|
+
# @param service [Object|Class] a service class or object as described
|
185
|
+
# above
|
186
|
+
def handle(service)
|
187
|
+
fail 'cannot add services if the server is running' if running?
|
188
|
+
fail 'cannot add services if the server is stopped' if stopped?
|
189
|
+
cls = service.is_a?(Class) ? service : service.class
|
190
|
+
assert_valid_service_class(cls)
|
191
|
+
add_rpc_descs_for(service)
|
192
|
+
end
|
193
|
+
|
194
|
+
# runs the server
|
195
|
+
#
|
196
|
+
# - if no rpc_descs are registered, this exits immediately, otherwise it
|
197
|
+
# continues running permanently and does not return until program exit.
|
198
|
+
#
|
199
|
+
# - #running? returns true after this is called, until #stop cause the
|
200
|
+
# the server to stop.
|
201
|
+
def run
|
202
|
+
if rpc_descs.size == 0
|
203
|
+
logger.warn('did not run as no services were present')
|
204
|
+
return
|
205
|
+
end
|
206
|
+
@run_mutex.synchronize do
|
207
|
+
@running = true
|
208
|
+
@run_cond.signal
|
209
|
+
end
|
210
|
+
@pool.start
|
211
|
+
@server.start
|
212
|
+
server_tag = Object.new
|
213
|
+
until stopped?
|
214
|
+
@server.request_call(server_tag)
|
215
|
+
ev = @cq.pluck(server_tag, @poll_period)
|
216
|
+
next if ev.nil?
|
217
|
+
if ev.type != SERVER_RPC_NEW
|
218
|
+
logger.warn("bad evt: got:#{ev.type}, want:#{SERVER_RPC_NEW}")
|
219
|
+
ev.close
|
220
|
+
next
|
221
|
+
end
|
222
|
+
c = new_active_server_call(ev.call, ev.result)
|
223
|
+
unless c.nil?
|
224
|
+
mth = ev.result.method.to_sym
|
225
|
+
ev.close
|
226
|
+
@pool.schedule(c) do |call|
|
227
|
+
rpc_descs[mth].run_server_method(call, rpc_handlers[mth])
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
@running = false
|
232
|
+
end
|
233
|
+
|
234
|
+
def new_active_server_call(call, new_server_rpc)
|
235
|
+
# Accept the call. This is necessary even if a status is to be sent
|
236
|
+
# back immediately
|
237
|
+
finished_tag = Object.new
|
238
|
+
call_queue = Core::CompletionQueue.new
|
239
|
+
call.metadata = new_server_rpc.metadata # store the metadata
|
240
|
+
call.server_accept(call_queue, finished_tag)
|
241
|
+
call.server_end_initial_metadata
|
242
|
+
|
243
|
+
# Send UNAVAILABLE if there are too many unprocessed jobs
|
244
|
+
jobs_count, max = @pool.jobs_waiting, @max_waiting_requests
|
245
|
+
logger.info("waiting: #{jobs_count}, max: #{max}")
|
246
|
+
if @pool.jobs_waiting > @max_waiting_requests
|
247
|
+
logger.warn("NOT AVAILABLE: too many jobs_waiting: #{new_server_rpc}")
|
248
|
+
noop = proc { |x| x }
|
249
|
+
c = ActiveCall.new(call, call_queue, noop, noop,
|
250
|
+
new_server_rpc.deadline,
|
251
|
+
finished_tag: finished_tag)
|
252
|
+
c.send_status(StatusCodes::UNAVAILABLE, '')
|
253
|
+
return nil
|
254
|
+
end
|
255
|
+
|
256
|
+
# Send NOT_FOUND if the method does not exist
|
257
|
+
mth = new_server_rpc.method.to_sym
|
258
|
+
unless rpc_descs.key?(mth)
|
259
|
+
logger.warn("NOT_FOUND: #{new_server_rpc}")
|
260
|
+
noop = proc { |x| x }
|
261
|
+
c = ActiveCall.new(call, call_queue, noop, noop,
|
262
|
+
new_server_rpc.deadline,
|
263
|
+
finished_tag: finished_tag)
|
264
|
+
c.send_status(StatusCodes::NOT_FOUND, '')
|
265
|
+
return nil
|
266
|
+
end
|
267
|
+
|
268
|
+
# Create the ActiveCall
|
269
|
+
rpc_desc = rpc_descs[mth]
|
270
|
+
logger.info("deadline is #{new_server_rpc.deadline}; (now=#{Time.now})")
|
271
|
+
ActiveCall.new(call, call_queue,
|
272
|
+
rpc_desc.marshal_proc, rpc_desc.unmarshal_proc(:input),
|
273
|
+
new_server_rpc.deadline, finished_tag: finished_tag)
|
274
|
+
end
|
275
|
+
|
276
|
+
# Pool is a simple thread pool for running server requests.
|
277
|
+
class Pool
|
278
|
+
def initialize(size)
|
279
|
+
fail 'pool size must be positive' unless size > 0
|
280
|
+
@jobs = Queue.new
|
281
|
+
@size = size
|
282
|
+
@stopped = false
|
283
|
+
@stop_mutex = Mutex.new
|
284
|
+
@stop_cond = ConditionVariable.new
|
285
|
+
@workers = []
|
286
|
+
end
|
287
|
+
|
288
|
+
# Returns the number of jobs waiting
|
289
|
+
def jobs_waiting
|
290
|
+
@jobs.size
|
291
|
+
end
|
292
|
+
|
293
|
+
# Runs the given block on the queue with the provided args.
|
294
|
+
#
|
295
|
+
# @param args the args passed blk when it is called
|
296
|
+
# @param blk the block to call
|
297
|
+
def schedule(*args, &blk)
|
298
|
+
fail 'already stopped' if @stopped
|
299
|
+
return if blk.nil?
|
300
|
+
logger.info('schedule another job')
|
301
|
+
@jobs << [blk, args]
|
302
|
+
end
|
303
|
+
|
304
|
+
# Starts running the jobs in the thread pool.
|
305
|
+
def start
|
306
|
+
fail 'already stopped' if @stopped
|
307
|
+
until @workers.size == @size.to_i
|
308
|
+
next_thread = Thread.new do
|
309
|
+
catch(:exit) do # allows { throw :exit } to kill a thread
|
310
|
+
loop do
|
311
|
+
begin
|
312
|
+
blk, args = @jobs.pop
|
313
|
+
blk.call(*args)
|
314
|
+
rescue StandardError => e
|
315
|
+
logger.warn('Error in worker thread')
|
316
|
+
logger.warn(e)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# removes the threads from workers, and signal when all the
|
322
|
+
# threads are complete.
|
323
|
+
@stop_mutex.synchronize do
|
324
|
+
@workers.delete(Thread.current)
|
325
|
+
@stop_cond.signal if @workers.size == 0
|
326
|
+
end
|
327
|
+
end
|
328
|
+
@workers << next_thread
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# Stops the jobs in the pool
|
333
|
+
def stop
|
334
|
+
logger.info('stopping, will wait for all the workers to exit')
|
335
|
+
@workers.size.times { schedule { throw :exit } }
|
336
|
+
@stopped = true
|
337
|
+
|
338
|
+
# TODO: allow configuration of the keepalive period
|
339
|
+
keep_alive = 5
|
340
|
+
@stop_mutex.synchronize do
|
341
|
+
@stop_cond.wait(@stop_mutex, keep_alive) if @workers.size > 0
|
342
|
+
end
|
343
|
+
|
344
|
+
# Forcibly shutdown any threads that are still alive.
|
345
|
+
if @workers.size > 0
|
346
|
+
logger.warn("forcibly terminating #{@workers.size} worker(s)")
|
347
|
+
@workers.each do |t|
|
348
|
+
next unless t.alive?
|
349
|
+
begin
|
350
|
+
t.exit
|
351
|
+
rescue StandardError => e
|
352
|
+
logger.warn('error while terminating a worker')
|
353
|
+
logger.warn(e)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
logger.info('stopped, all workers are shutdown')
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
protected
|
363
|
+
|
364
|
+
def rpc_descs
|
365
|
+
@rpc_descs ||= {}
|
366
|
+
end
|
367
|
+
|
368
|
+
def rpc_handlers
|
369
|
+
@rpc_handlers ||= {}
|
370
|
+
end
|
371
|
+
|
372
|
+
private
|
373
|
+
|
374
|
+
def assert_valid_service_class(cls)
|
375
|
+
unless cls.include?(GenericService)
|
376
|
+
fail "#{cls} should 'include GenericService'"
|
377
|
+
end
|
378
|
+
if cls.rpc_descs.size == 0
|
379
|
+
fail "#{cls} should specify some rpc descriptions"
|
380
|
+
end
|
381
|
+
cls.assert_rpc_descs_have_methods
|
382
|
+
end
|
383
|
+
|
384
|
+
def add_rpc_descs_for(service)
|
385
|
+
cls = service.is_a?(Class) ? service : service.class
|
386
|
+
specs = rpc_descs
|
387
|
+
handlers = rpc_handlers
|
388
|
+
cls.rpc_descs.each_pair do |name, spec|
|
389
|
+
route = "/#{cls.service_name}/#{name}".to_sym
|
390
|
+
if specs.key? route
|
391
|
+
fail "Cannot add rpc #{route} from #{spec}, already registered"
|
392
|
+
else
|
393
|
+
specs[route] = spec
|
394
|
+
if service.is_a?(Class)
|
395
|
+
handlers[route] = cls.new.method(name.to_s.underscore.to_sym)
|
396
|
+
else
|
397
|
+
handlers[route] = service.method(name.to_s.underscore.to_sym)
|
398
|
+
end
|
399
|
+
logger.info("handling #{route} with #{handlers[route]}")
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|