| Craig Tiller | 6169d5f | 2016-03-31 07:46:18 -0700 | [diff] [blame] | 1 | # Copyright 2015, Google Inc. |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 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 | |
| murgatroid99 | 79108d0 | 2016-04-22 13:41:00 -0700 | [diff] [blame] | 30 | require_relative '../grpc' |
| 31 | require_relative 'active_call' |
| 32 | require_relative 'service' |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 33 | require 'thread' |
| Alexander Polcyn | 9c74487 | 2016-08-12 14:58:10 -0700 | [diff] [blame^] | 34 | require 'concurrent' |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 35 | |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 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 |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 41 | include Core::CallOps |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 42 | include Core::TimeConsts |
| 43 | extend ::Forwardable |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 44 | |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 45 | def_delegators :@server, :add_http2_port |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 46 | |
| Alexander Polcyn | 9c74487 | 2016-08-12 14:58:10 -0700 | [diff] [blame^] | 47 | # Default max size of the thread pool size is 100 |
| 48 | DEFAULT_MAX_POOL_SIZE = 100 |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 49 | |
| Alexander Polcyn | 9c74487 | 2016-08-12 14:58:10 -0700 | [diff] [blame^] | 50 | # Default minimum size of the thread pool is 5 |
| 51 | DEFAULT_MIN_POOL_SIZE = 5 |
| 52 | |
| 53 | # Default max_waiting_requests size is 60 |
| 54 | DEFAULT_MAX_WAITING_REQUESTS = 60 |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 55 | |
| Tim Emiola | 9558460 | 2015-04-16 12:53:07 -0700 | [diff] [blame] | 56 | # Default poll period is 1s |
| 57 | DEFAULT_POLL_PERIOD = 1 |
| 58 | |
| 59 | # Signal check period is 0.25s |
| 60 | SIGNAL_CHECK_PERIOD = 0.25 |
| 61 | |
| Tim Emiola | 3fd2be2 | 2015-04-16 17:43:59 -0700 | [diff] [blame] | 62 | # setup_connect_md_proc is used by #initialize to validate the |
| 63 | # connect_md_proc. |
| 64 | def self.setup_connect_md_proc(a_proc) |
| 65 | return nil if a_proc.nil? |
| 66 | fail(TypeError, '!Proc') unless a_proc.is_a? Proc |
| 67 | a_proc |
| 68 | end |
| 69 | |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 70 | # Creates a new RpcServer. |
| 71 | # |
| 72 | # The RPC server is configured using keyword arguments. |
| 73 | # |
| 74 | # There are some specific keyword args used to configure the RpcServer |
| murgatroid99 | 580a64a | 2016-07-12 13:54:09 -0700 | [diff] [blame] | 75 | # instance. |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 76 | # |
| Alexander Polcyn | 9c74487 | 2016-08-12 14:58:10 -0700 | [diff] [blame^] | 77 | # * pool_size: the maximum size of the thread pool that the server's |
| 78 | # thread pool can reach. |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 79 | # |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 80 | # * max_waiting_requests: the maximum number of requests that are not |
| 81 | # being handled to allow. When this limit is exceeded, the server responds |
| 82 | # with not available to new requests |
| Tim Emiola | 3fd2be2 | 2015-04-16 17:43:59 -0700 | [diff] [blame] | 83 | # |
| murgatroid99 | 580a64a | 2016-07-12 13:54:09 -0700 | [diff] [blame] | 84 | # * poll_period: when present, the server polls for new events with this |
| 85 | # period |
| 86 | # |
| Tim Emiola | 3fd2be2 | 2015-04-16 17:43:59 -0700 | [diff] [blame] | 87 | # * connect_md_proc: |
| 88 | # when non-nil is a proc for determining metadata to to send back the client |
| 89 | # on receiving an invocation req. The proc signature is: |
| 90 | # {key: val, ..} func(method_name, {key: val, ...}) |
| murgatroid99 | b19f181 | 2016-05-16 12:21:39 -0700 | [diff] [blame] | 91 | # |
| 92 | # * server_args: |
| 93 | # A server arguments hash to be passed down to the underlying core server |
| Alexander Polcyn | 9c74487 | 2016-08-12 14:58:10 -0700 | [diff] [blame^] | 94 | def initialize(pool_size:DEFAULT_MAX_POOL_SIZE, |
| 95 | min_pool_size:DEFAULT_MIN_POOL_SIZE, |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 96 | max_waiting_requests:DEFAULT_MAX_WAITING_REQUESTS, |
| Tim Emiola | 9558460 | 2015-04-16 12:53:07 -0700 | [diff] [blame] | 97 | poll_period:DEFAULT_POLL_PERIOD, |
| Tim Emiola | 3fd2be2 | 2015-04-16 17:43:59 -0700 | [diff] [blame] | 98 | connect_md_proc:nil, |
| murgatroid99 | b19f181 | 2016-05-16 12:21:39 -0700 | [diff] [blame] | 99 | server_args:{}) |
| Tim Emiola | 3fd2be2 | 2015-04-16 17:43:59 -0700 | [diff] [blame] | 100 | @connect_md_proc = RpcServer.setup_connect_md_proc(connect_md_proc) |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 101 | @max_waiting_requests = max_waiting_requests |
| 102 | @poll_period = poll_period |
| Alexander Polcyn | 9c74487 | 2016-08-12 14:58:10 -0700 | [diff] [blame^] | 103 | |
| 104 | @pool = Concurrent::ThreadPoolExecutor.new( |
| 105 | min_threads: [min_pool_size, pool_size].min, |
| 106 | max_threads: pool_size, |
| 107 | max_queue: max_waiting_requests, |
| 108 | fallback_policy: :discard) |
| Tim Emiola | 4aba356 | 2015-05-22 17:25:49 -0700 | [diff] [blame] | 109 | @run_cond = ConditionVariable.new |
| 110 | @run_mutex = Mutex.new |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 111 | # running_state can take 4 values: :not_started, :running, :stopping, and |
| 112 | # :stopped. State transitions can only proceed in that order. |
| 113 | @running_state = :not_started |
| murgatroid99 | 5ea4a99 | 2016-06-13 10:36:41 -0700 | [diff] [blame] | 114 | @server = Core::Server.new(server_args) |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 115 | end |
| 116 | |
| 117 | # stops a running server |
| 118 | # |
| 119 | # the call has no impact if the server is already stopped, otherwise |
| 120 | # server's current call loop is it's last. |
| 121 | def stop |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 122 | @run_mutex.synchronize do |
| 123 | fail 'Cannot stop before starting' if @running_state == :not_started |
| 124 | return if @running_state != :running |
| murgatroid99 | 1d68520 | 2016-03-09 17:30:37 -0800 | [diff] [blame] | 125 | transition_running_state(:stopping) |
| Tim Emiola | 4aba356 | 2015-05-22 17:25:49 -0700 | [diff] [blame] | 126 | end |
| Tim Emiola | b1fa5d4 | 2015-06-11 09:35:06 -0700 | [diff] [blame] | 127 | deadline = from_relative_time(@poll_period) |
| murgatroid99 | 5ea4a99 | 2016-06-13 10:36:41 -0700 | [diff] [blame] | 128 | @server.close(deadline) |
| Alexander Polcyn | 9c74487 | 2016-08-12 14:58:10 -0700 | [diff] [blame^] | 129 | @pool.shutdown |
| 130 | @pool.wait_for_termination |
| Tim Emiola | 4aba356 | 2015-05-22 17:25:49 -0700 | [diff] [blame] | 131 | end |
| Tim Emiola | 9558460 | 2015-04-16 12:53:07 -0700 | [diff] [blame] | 132 | |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 133 | def running_state |
| 134 | @run_mutex.synchronize do |
| 135 | return @running_state |
| Tim Emiola | 4aba356 | 2015-05-22 17:25:49 -0700 | [diff] [blame] | 136 | end |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 137 | end |
| 138 | |
| murgatroid99 | 1d68520 | 2016-03-09 17:30:37 -0800 | [diff] [blame] | 139 | # Can only be called while holding @run_mutex |
| 140 | def transition_running_state(target_state) |
| 141 | state_transitions = { |
| 142 | not_started: :running, |
| 143 | running: :stopping, |
| 144 | stopping: :stopped |
| 145 | } |
| 146 | if state_transitions[@running_state] == target_state |
| 147 | @running_state = target_state |
| 148 | else |
| 149 | fail "Bad server state transition: #{@running_state}->#{target_state}" |
| 150 | end |
| 151 | end |
| 152 | |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 153 | def running? |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 154 | running_state == :running |
| 155 | end |
| 156 | |
| 157 | def stopped? |
| 158 | running_state == :stopped |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 159 | end |
| 160 | |
| 161 | # Is called from other threads to wait for #run to start up the server. |
| 162 | # |
| 163 | # If run has not been called, this returns immediately. |
| 164 | # |
| 165 | # @param timeout [Numeric] number of seconds to wait |
| 166 | # @result [true, false] true if the server is running, false otherwise |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 167 | def wait_till_running(timeout = nil) |
| 168 | @run_mutex.synchronize do |
| 169 | @run_cond.wait(@run_mutex, timeout) if @running_state == :not_started |
| 170 | return @running_state == :running |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 171 | end |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 172 | end |
| 173 | |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 174 | # handle registration of classes |
| 175 | # |
| 176 | # service is either a class that includes GRPC::GenericService and whose |
| 177 | # #new function can be called without argument or any instance of such a |
| 178 | # class. |
| 179 | # |
| 180 | # E.g, after |
| 181 | # |
| 182 | # class Divider |
| 183 | # include GRPC::GenericService |
| 184 | # rpc :div DivArgs, DivReply # single request, single response |
| 185 | # def initialize(optional_arg='default option') # no args |
| 186 | # ... |
| 187 | # end |
| 188 | # |
| 189 | # srv = GRPC::RpcServer.new(...) |
| 190 | # |
| 191 | # # Either of these works |
| 192 | # |
| 193 | # srv.handle(Divider) |
| 194 | # |
| 195 | # # or |
| 196 | # |
| 197 | # srv.handle(Divider.new('replace optional arg')) |
| 198 | # |
| 199 | # It raises RuntimeError: |
| 200 | # - if service is not valid service class or object |
| 201 | # - its handler methods are already registered |
| 202 | # - if the server is already running |
| 203 | # |
| 204 | # @param service [Object|Class] a service class or object as described |
| 205 | # above |
| 206 | def handle(service) |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 207 | @run_mutex.synchronize do |
| 208 | unless @running_state == :not_started |
| 209 | fail 'cannot add services if the server has been started' |
| 210 | end |
| 211 | cls = service.is_a?(Class) ? service : service.class |
| 212 | assert_valid_service_class(cls) |
| murgatroid99 | 1d68520 | 2016-03-09 17:30:37 -0800 | [diff] [blame] | 213 | add_rpc_descs_for(service) |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 214 | end |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 215 | end |
| 216 | |
| 217 | # runs the server |
| 218 | # |
| 219 | # - if no rpc_descs are registered, this exits immediately, otherwise it |
| 220 | # continues running permanently and does not return until program exit. |
| 221 | # |
| 222 | # - #running? returns true after this is called, until #stop cause the |
| 223 | # the server to stop. |
| 224 | def run |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 225 | @run_mutex.synchronize do |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 226 | fail 'cannot run without registering services' if rpc_descs.size.zero? |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 227 | @server.start |
| murgatroid99 | 1d68520 | 2016-03-09 17:30:37 -0800 | [diff] [blame] | 228 | transition_running_state(:running) |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 229 | @run_cond.broadcast |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 230 | end |
| Tim Emiola | f9e77b3 | 2015-04-16 14:50:11 -0700 | [diff] [blame] | 231 | loop_handle_server_calls |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 232 | end |
| Tim Emiola | e2860c5 | 2015-01-16 02:58:41 -0800 | [diff] [blame] | 233 | |
| murgatroid99 | 6bbe369 | 2016-05-06 11:43:22 -0700 | [diff] [blame] | 234 | alias_method :run_till_terminated, :run |
| 235 | |
| murgatroid99 | 895c111 | 2016-04-04 11:27:51 -0700 | [diff] [blame] | 236 | # Sends RESOURCE_EXHAUSTED if there are too many unprocessed jobs |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 237 | def available?(an_rpc) |
| Alexander Polcyn | 9c74487 | 2016-08-12 14:58:10 -0700 | [diff] [blame^] | 238 | jobs_count, max = @pool.queue_length, @pool.max_queue |
| Nick Gauthier | f233d96 | 2015-05-20 14:02:50 -0400 | [diff] [blame] | 239 | GRPC.logger.info("waiting: #{jobs_count}, max: #{max}") |
| Alexander Polcyn | 9c74487 | 2016-08-12 14:58:10 -0700 | [diff] [blame^] | 240 | |
| 241 | # remaining capacity for ThreadPoolExecutors is -1 if unbounded |
| 242 | return an_rpc if @pool.remaining_capacity != 0 |
| Nick Gauthier | f233d96 | 2015-05-20 14:02:50 -0400 | [diff] [blame] | 243 | GRPC.logger.warn("NOT AVAILABLE: too many jobs_waiting: #{an_rpc}") |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 244 | noop = proc { |x| x } |
| Alex Polcyn | d9892bd | 2016-07-04 00:52:39 -0700 | [diff] [blame] | 245 | |
| 246 | # Create a new active call that knows that metadata hasn't been |
| 247 | # sent yet |
| murgatroid99 | 5ea4a99 | 2016-06-13 10:36:41 -0700 | [diff] [blame] | 248 | c = ActiveCall.new(an_rpc.call, noop, noop, an_rpc.deadline, |
| Alex Polcyn | d9892bd | 2016-07-04 00:52:39 -0700 | [diff] [blame] | 249 | metadata_received: true, started: false) |
| murgatroid99 | 38281cf | 2016-05-03 10:51:49 -0700 | [diff] [blame] | 250 | c.send_status(GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED, '') |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 251 | nil |
| 252 | end |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 253 | |
| murgatroid99 | 3d6d058 | 2015-07-08 12:47:13 -0700 | [diff] [blame] | 254 | # Sends UNIMPLEMENTED if the method is not implemented by this server |
| murgatroid99 | f88eecd | 2015-07-10 09:58:15 -0700 | [diff] [blame] | 255 | def implemented?(an_rpc) |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 256 | mth = an_rpc.method.to_sym |
| 257 | return an_rpc if rpc_descs.key?(mth) |
| murgatroid99 | 3d6d058 | 2015-07-08 12:47:13 -0700 | [diff] [blame] | 258 | GRPC.logger.warn("UNIMPLEMENTED: #{an_rpc}") |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 259 | noop = proc { |x| x } |
| Alex Polcyn | d9892bd | 2016-07-04 00:52:39 -0700 | [diff] [blame] | 260 | |
| 261 | # Create a new active call that knows that |
| 262 | # metadata hasn't been sent yet |
| murgatroid99 | 5ea4a99 | 2016-06-13 10:36:41 -0700 | [diff] [blame] | 263 | c = ActiveCall.new(an_rpc.call, noop, noop, an_rpc.deadline, |
| Alex Polcyn | d9892bd | 2016-07-04 00:52:39 -0700 | [diff] [blame] | 264 | metadata_received: true, started: false) |
| murgatroid99 | 38281cf | 2016-05-03 10:51:49 -0700 | [diff] [blame] | 265 | c.send_status(GRPC::Core::StatusCodes::UNIMPLEMENTED, '') |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 266 | nil |
| 267 | end |
| 268 | |
| Tim Emiola | f9e77b3 | 2015-04-16 14:50:11 -0700 | [diff] [blame] | 269 | # handles calls to the server |
| 270 | def loop_handle_server_calls |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 271 | fail 'not started' if running_state == :not_started |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 272 | while running_state == :running |
| Tim Emiola | 4aba356 | 2015-05-22 17:25:49 -0700 | [diff] [blame] | 273 | begin |
| murgatroid99 | 5ea4a99 | 2016-06-13 10:36:41 -0700 | [diff] [blame] | 274 | an_rpc = @server.request_call |
| murgatroid99 | 3c09a64 | 2015-10-14 17:25:49 -0700 | [diff] [blame] | 275 | break if (!an_rpc.nil?) && an_rpc.call.nil? |
| Tim Emiola | 7d21c04 | 2015-11-10 13:15:36 -0800 | [diff] [blame] | 276 | active_call = new_active_server_call(an_rpc) |
| 277 | unless active_call.nil? |
| Alexander Polcyn | 9c74487 | 2016-08-12 14:58:10 -0700 | [diff] [blame^] | 278 | @pool.post(active_call) do |ac| |
| Tim Emiola | 7d21c04 | 2015-11-10 13:15:36 -0800 | [diff] [blame] | 279 | c, mth = ac |
| murgatroid99 | 38281cf | 2016-05-03 10:51:49 -0700 | [diff] [blame] | 280 | begin |
| 281 | rpc_descs[mth].run_server_method(c, rpc_handlers[mth]) |
| murgatroid99 | 59dfee8 | 2016-05-03 11:33:25 -0700 | [diff] [blame] | 282 | rescue StandardError |
| 283 | c.send_status(GRPC::Core::StatusCodes::INTERNAL, |
| 284 | 'Server handler failed') |
| murgatroid99 | 38281cf | 2016-05-03 10:51:49 -0700 | [diff] [blame] | 285 | end |
| Tim Emiola | 391664a | 2015-08-31 15:38:43 -0700 | [diff] [blame] | 286 | end |
| 287 | end |
| Tim Emiola | 4aba356 | 2015-05-22 17:25:49 -0700 | [diff] [blame] | 288 | rescue Core::CallError, RuntimeError => e |
| Tim Emiola | b1fa5d4 | 2015-06-11 09:35:06 -0700 | [diff] [blame] | 289 | # these might happen for various reasonse. The correct behaviour of |
| Tim Emiola | 81d950a | 2015-08-28 16:57:28 -0700 | [diff] [blame] | 290 | # the server is to log them and continue, if it's not shutting down. |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 291 | if running_state == :running |
| 292 | GRPC.logger.warn("server call failed: #{e}") |
| 293 | end |
| Tim Emiola | 4aba356 | 2015-05-22 17:25:49 -0700 | [diff] [blame] | 294 | next |
| 295 | end |
| Tim Emiola | f9e77b3 | 2015-04-16 14:50:11 -0700 | [diff] [blame] | 296 | end |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 297 | # @running_state should be :stopping here |
| murgatroid99 | 1d68520 | 2016-03-09 17:30:37 -0800 | [diff] [blame] | 298 | @run_mutex.synchronize { transition_running_state(:stopped) } |
| Tim Emiola | 81d950a | 2015-08-28 16:57:28 -0700 | [diff] [blame] | 299 | GRPC.logger.info("stopped: #{self}") |
| Tim Emiola | f9e77b3 | 2015-04-16 14:50:11 -0700 | [diff] [blame] | 300 | end |
| 301 | |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 302 | def new_active_server_call(an_rpc) |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 303 | return nil if an_rpc.nil? || an_rpc.call.nil? |
| 304 | |
| 305 | # allow the metadata to be accessed from the call |
| Tim Emiola | 3fd2be2 | 2015-04-16 17:43:59 -0700 | [diff] [blame] | 306 | an_rpc.call.metadata = an_rpc.metadata # attaches md to call for handlers |
| Tim Emiola | 7d21c04 | 2015-11-10 13:15:36 -0800 | [diff] [blame] | 307 | GRPC.logger.debug("call md is #{an_rpc.metadata}") |
| Tim Emiola | 3fd2be2 | 2015-04-16 17:43:59 -0700 | [diff] [blame] | 308 | connect_md = nil |
| 309 | unless @connect_md_proc.nil? |
| 310 | connect_md = @connect_md_proc.call(an_rpc.method, an_rpc.metadata) |
| 311 | end |
| Ken Payson | dce1ee6 | 2016-05-20 10:29:34 -0700 | [diff] [blame] | 312 | |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 313 | return nil unless available?(an_rpc) |
| murgatroid99 | f88eecd | 2015-07-10 09:58:15 -0700 | [diff] [blame] | 314 | return nil unless implemented?(an_rpc) |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 315 | |
| Alex Polcyn | d9892bd | 2016-07-04 00:52:39 -0700 | [diff] [blame] | 316 | # Create the ActiveCall. Indicate that metadata hasnt been sent yet. |
| Nick Gauthier | f233d96 | 2015-05-20 14:02:50 -0400 | [diff] [blame] | 317 | GRPC.logger.info("deadline is #{an_rpc.deadline}; (now=#{Time.now})") |
| Tim Emiola | b22a21e | 2015-04-02 21:45:26 -0700 | [diff] [blame] | 318 | rpc_desc = rpc_descs[an_rpc.method.to_sym] |
| Alex Polcyn | d9892bd | 2016-07-04 00:52:39 -0700 | [diff] [blame] | 319 | c = ActiveCall.new(an_rpc.call, |
| 320 | rpc_desc.marshal_proc, |
| 321 | rpc_desc.unmarshal_proc(:input), |
| 322 | an_rpc.deadline, |
| 323 | metadata_received: true, |
| 324 | started: false, |
| 325 | metadata_to_send: connect_md) |
| Tim Emiola | 7d21c04 | 2015-11-10 13:15:36 -0800 | [diff] [blame] | 326 | mth = an_rpc.method.to_sym |
| 327 | [c, mth] |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 328 | end |
| 329 | |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 330 | protected |
| 331 | |
| 332 | def rpc_descs |
| murgatroid99 | 1d68520 | 2016-03-09 17:30:37 -0800 | [diff] [blame] | 333 | @rpc_descs ||= {} |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 334 | end |
| 335 | |
| 336 | def rpc_handlers |
| murgatroid99 | 1d68520 | 2016-03-09 17:30:37 -0800 | [diff] [blame] | 337 | @rpc_handlers ||= {} |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 338 | end |
| 339 | |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 340 | def assert_valid_service_class(cls) |
| 341 | unless cls.include?(GenericService) |
| Tim Emiola | f9e77b3 | 2015-04-16 14:50:11 -0700 | [diff] [blame] | 342 | fail "#{cls} must 'include GenericService'" |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 343 | end |
| murgatroid99 | cf239e7 | 2016-05-06 14:48:21 -0700 | [diff] [blame] | 344 | fail "#{cls} should specify some rpc descriptions" if |
| 345 | cls.rpc_descs.size.zero? |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 346 | end |
| 347 | |
| murgatroid99 | 1d68520 | 2016-03-09 17:30:37 -0800 | [diff] [blame] | 348 | # This should be called while holding @run_mutex |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 349 | def add_rpc_descs_for(service) |
| 350 | cls = service.is_a?(Class) ? service : service.class |
| murgatroid99 | d48d84d | 2016-03-09 11:10:20 -0800 | [diff] [blame] | 351 | specs, handlers = (@rpc_descs ||= {}), (@rpc_handlers ||= {}) |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 352 | cls.rpc_descs.each_pair do |name, spec| |
| 353 | route = "/#{cls.service_name}/#{name}".to_sym |
| Tim Emiola | f9e77b3 | 2015-04-16 14:50:11 -0700 | [diff] [blame] | 354 | fail "already registered: rpc #{route} from #{spec}" if specs.key? route |
| 355 | specs[route] = spec |
| Tim Emiola | bae3a61 | 2015-05-07 14:24:23 -0700 | [diff] [blame] | 356 | rpc_name = GenericService.underscore(name.to_s).to_sym |
| Tim Emiola | f9e77b3 | 2015-04-16 14:50:11 -0700 | [diff] [blame] | 357 | if service.is_a?(Class) |
| Tim Emiola | bae3a61 | 2015-05-07 14:24:23 -0700 | [diff] [blame] | 358 | handlers[route] = cls.new.method(rpc_name) |
| Tim Emiola | 7e7911f | 2015-02-17 18:28:23 -0800 | [diff] [blame] | 359 | else |
| Tim Emiola | bae3a61 | 2015-05-07 14:24:23 -0700 | [diff] [blame] | 360 | handlers[route] = service.method(rpc_name) |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 361 | end |
| Nick Gauthier | f233d96 | 2015-05-20 14:02:50 -0400 | [diff] [blame] | 362 | GRPC.logger.info("handling #{route} with #{handlers[route]}") |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 363 | end |
| 364 | end |
| 365 | end |
| nnoble | 097ef9b | 2014-12-01 17:06:10 -0800 | [diff] [blame] | 366 | end |