gruf 2.4.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1af99f62622489376979d133e0c11fe33957a3937941193187508158bfb73398
4
- data.tar.gz: 4b280b7a654de95dc0fc2d7ce8562baace9a9bd46861ccd59178682814a43e23
3
+ metadata.gz: b1289ff979cec29b1c475fc7fb9292d3eab752229e5147b59f18c107fb34531d
4
+ data.tar.gz: 55acd4dee980cd8274edbbc7308edc63e09b6a4b19e4a87a6d1d1e72b11dd46f
5
5
  SHA512:
6
- metadata.gz: 75f137a4d62bbbc3169338517c12c9b2bb3bb8a60def87ee7f0ee096cad9f8752660ed7a4367f118d7dfa05c87901a1fd543ee728a9ac273816b2014feb0f6fa
7
- data.tar.gz: 3337ab3f9da018e445e245beaaa2d4af7307d31d9182f758e4e6adcbb348c31fd7862bafce81e2eabca7ba02d7b7b678bf0abaa782ffedc8b77a6adb0339a59d
6
+ metadata.gz: 99959b15a10222393883ac16b80c8550ba35190aa9a9858764687e4349d04db9f696d37fe9ed7fd7bf4db0df8e1841791760eb49de7633392720fe9f8175d733
7
+ data.tar.gz: 22f6f6f2b9db9bea6768b1f3089c6502d02323e012efdad113becd430a94af4136d8d6f216d4e73079e66d59490ee379c6ba962d1c3f9f9649243fa0b382b57d
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@ Changelog for the gruf gem. This includes internal history before the gem was ma
2
2
 
3
3
  ### Pending release
4
4
 
5
+ ### 2.4.1
6
+
7
+ - Safer configuration of GRPC::RpcServer. From now on, use `Gruf.rpc_server_options` for the params
8
+ to be sent to GPRC::RpcServer. Also provide sane defaults for params for GRPC::RpcServer. [#55]
9
+ - Added ability to monitor `RESOURCE_EXHAUSTED` and `UNIMPLEMENTED`. By setting `event_listener_proc` in
10
+ the Gruf configuration, you will receive a callback when these events occur. The parameter to your
11
+ callback will be a symbol (`:thread_pool_exhausted` or `:unimplemented`). Others may be added in the future.
12
+
5
13
  ### 2.4.0
6
14
 
7
15
  - Added a hash of error log levels to RequestLogging interceptor, mapping error code to level of logging to use. To
data/README.md CHANGED
@@ -17,8 +17,8 @@ up fast and efficiently at scale. Some of its features include:
17
17
  still preserving gRPC BadStatus codes
18
18
  * Server and client execution timings in responses
19
19
 
20
- gruf currently has active support for gRPC 1.10.x+. gruf is compatible and tested with Ruby 2.2-2.5.
21
- gruf is also not [Rails](https://github.com/rails/rails)-specific, and can be used in any Ruby framework
20
+ gruf currently has active support for gRPC 1.10.x+. gruf is compatible and tested with Ruby 2.2-2.5.
21
+ gruf is also not [Rails](https://github.com/rails/rails)-specific, and can be used in any Ruby framework
22
22
  (such as [Grape](https://github.com/ruby-grape/grape), for instance).
23
23
 
24
24
  ## Installation
@@ -86,13 +86,13 @@ class MyInterceptor < Gruf::Interceptors::ClientInterceptor
86
86
  end
87
87
 
88
88
  ::Gruf::Client.new(
89
- service: ::Demo::ThingService,
89
+ service: ::Demo::ThingService,
90
90
  client_options: [
91
91
  interceptors: [MyInterceptor.new]
92
92
  ])
93
93
  ```
94
94
 
95
- The `interceptors` option in `client_options` can accept either a `GRPC::ClientInterceptor` class or a
95
+ The `interceptors` option in `client_options` can accept either a `GRPC::ClientInterceptor` class or a
96
96
  `Gruf::Interceptors::ClientInterceptor`, since the latter just extends the former. The gruf client interceptors
97
97
  take an optional alternative approach: rather than having separate methods for each request type, it provides a default
98
98
  `call` method that passes in a `RequestContext` object, which has the following attributes:
@@ -178,8 +178,8 @@ Gruf comes baked in with a few command-line options for the binstub:
178
178
  | --suppress-default-interceptors | Do not use the default interceptors for the server |
179
179
  | --backtrace-on-error | Push backtraces on exceptions to the error serializer |
180
180
 
181
- These options will override whatever is passed in the Gruf configure block or
182
- initializer.
181
+ These options will override whatever is passed in the Gruf configure block or
182
+ initializer.
183
183
 
184
184
  ### Basic Authentication
185
185
 
@@ -255,6 +255,18 @@ Gruf.configure do |c|
255
255
  end
256
256
  ```
257
257
 
258
+ ### GRPC::RpcServer configuration
259
+ To customize parameters for the underlying GRPC::RpcServer, such as the size of the gRPC thread pool,
260
+ you can pass them in via Gruf.rpc\_server\_options.
261
+
262
+ ```ruby
263
+ Gruf.configure do |c|
264
+ # The size of the underlying thread pool. No more concurrent requests can be made
265
+ # than the size of the thread pool.
266
+ c.rpc_server_options[:pool_size] = 100
267
+ end
268
+ ```
269
+
258
270
  ## Server Interceptors
259
271
 
260
272
  gruf supports interceptors around the grpc server calls, allowing you to perform actions around your service
@@ -395,6 +407,77 @@ It's important to maintain a safe blacklist should you decide to log parameters;
395
407
  parameter sanitization on its own. We also recommend blacklisting parameters that may contain
396
408
  very large values (such as binary or json data).
397
409
 
410
+ ## Testing with RSpec
411
+
412
+ ### Controllers
413
+
414
+ In order to test your controller, you first need to mock a GRPC ActiveCall. You can create the following file under `/spec/support/` path of your project:
415
+ ```
416
+ require 'grpc'
417
+
418
+ module Rpc
419
+ module Test
420
+ class Call
421
+ attr_reader :metadata
422
+
423
+ def initialize(md = nil)
424
+ @metadata = md || { 'authorization' => "Basic #{Base64.encode64('grpc:magic')}" }
425
+ end
426
+
427
+ def output_metadata
428
+ @output_metadata ||= {}
429
+ end
430
+ end
431
+ end
432
+ end
433
+ ```
434
+
435
+ Imagine you have the following controller to test:
436
+ ```
437
+ class ThingController < ::Gruf::Controllers::Base
438
+ bind ::Rpc::ThingService::Service
439
+
440
+ def get_thing
441
+ thing = Rpc::Thing.new(id: message_id, name: 'Foo')
442
+ Rpc::GetThingResponse.new(thing: thing)
443
+ end
444
+
445
+ private
446
+
447
+ def message_id
448
+ request.message.id
449
+ end
450
+ end
451
+ ```
452
+
453
+ You can stub it in the specs this way:
454
+ ```
455
+ describe ThingController do
456
+ let(:rpc_service) { ::Rpc::ThingService::Service }
457
+ let(:rpc_desc) { Rpc::ThingService::Service.rpc_descs.values.first }
458
+ let(:message) { Rpc::GetThingRequest.new(id: 1) }
459
+ let(:controller) do
460
+ described_class.new(
461
+ method_key: :get_thing,
462
+ service: rpc_service,
463
+ active_call: Rpc::Test::Call.new,
464
+ message: message,
465
+ rpc_desc: rpc_desc
466
+ )
467
+ end
468
+
469
+ describe '.call' do
470
+ context 'with :get_thing as an argument' do
471
+ let(:result) { controller.call(:get_thing) }
472
+
473
+ it 'returns an instance of Rpc::GetThingResponse' do
474
+ expect(result).to be_instance_of(Rpc::GetThingResponse)
475
+ end
476
+ end
477
+ end
478
+ end
479
+ ```
480
+
398
481
  ## Plugins
399
482
 
400
483
  You can build your own hooks and middleware for gruf; here's a list of known open source gems for
data/gruf.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.licenses = ['MIT']
26
26
 
27
27
  spec.summary = 'gRPC Ruby Framework'
28
- spec.description = spec.summary
28
+ spec.description = 'gRPC Ruby Framework for building complex gRPC applications at scale'
29
29
  spec.homepage = 'https://github.com/bigcommerce/gruf'
30
30
 
31
31
  spec.files = Dir['README.md', 'CHANGELOG.md', 'CODE_OF_CONDUCT.md', 'lib/**/*', 'gruf.gemspec']
data/lib/gruf.rb CHANGED
@@ -32,6 +32,7 @@ require_relative 'gruf/timer'
32
32
  require_relative 'gruf/response'
33
33
  require_relative 'gruf/error'
34
34
  require_relative 'gruf/client'
35
+ require_relative 'gruf/instrumentable_grpc_server'
35
36
  require_relative 'gruf/server'
36
37
 
37
38
  ##
@@ -37,7 +37,16 @@ module Gruf
37
37
  use_default_interceptors: true,
38
38
  backtrace_on_error: false,
39
39
  use_exception_message: true,
40
- internal_error_message: 'Internal Server Error'
40
+ internal_error_message: 'Internal Server Error',
41
+ event_listener_proc: nil,
42
+ rpc_server_options: {
43
+ pool_size: GRPC::RpcServer::DEFAULT_POOL_SIZE,
44
+ max_waiting_requests: GRPC::RpcServer::DEFAULT_MAX_WAITING_REQUESTS,
45
+ poll_period: GRPC::RpcServer::DEFAULT_POLL_PERIOD,
46
+ pool_keep_alive: GRPC::Pool::DEFAULT_KEEP_ALIVE,
47
+ connect_md_proc: nil,
48
+ server_args: {}
49
+ }.freeze
41
50
  }.freeze
42
51
 
43
52
  attr_accessor *VALID_CONFIG_KEYS.keys
data/lib/gruf/error.rb CHANGED
@@ -175,7 +175,8 @@ module Gruf
175
175
  # @return [GRPC::BadStatus]
176
176
  #
177
177
  def grpc_error
178
- @grpc_error = grpc_class.new(message, **@metadata)
178
+ md = @metadata ? @metadata : {}
179
+ @grpc_error = grpc_class.new(message, **md)
179
180
  end
180
181
 
181
182
  private
@@ -0,0 +1,61 @@
1
+ module Gruf
2
+ ##
3
+ # A subclass of GRPC::RpcServer that can be used for enhanced monitoring
4
+ # of thread pool. Note that since we are reaching into the internals of
5
+ # GRPC::RpcServer, we need to watch the evolution of that class.
6
+ #
7
+ class InstrumentableGrpcServer < GRPC::RpcServer
8
+ ##
9
+ # Add an event_listener_proc that, if supplied, will be called
10
+ # when interesting events happen in the server.
11
+ #
12
+ def initialize(pool_size: DEFAULT_POOL_SIZE,
13
+ max_waiting_requests: DEFAULT_MAX_WAITING_REQUESTS,
14
+ poll_period: DEFAULT_POLL_PERIOD,
15
+ pool_keep_alive: Pool::DEFAULT_KEEP_ALIVE,
16
+ connect_md_proc: nil,
17
+ server_args: {},
18
+ interceptors: [],
19
+ event_listener_proc: nil)
20
+ # Call the base class initializer
21
+ super(
22
+ pool_size: pool_size,
23
+ max_waiting_requests: max_waiting_requests,
24
+ poll_period: poll_period,
25
+ pool_keep_alive: pool_keep_alive,
26
+ connect_md_proc: connect_md_proc,
27
+ server_args: server_args,
28
+ interceptors: interceptors
29
+ )
30
+
31
+ # Save event listener for later
32
+ @event_listener_proc = event_listener_proc
33
+ end
34
+
35
+ ##
36
+ # Notify the event listener of something interesting
37
+ #
38
+ def notify(event)
39
+ return unless @event_listener_proc && @event_listener_proc.respond_to?(:call)
40
+ @event_listener_proc.call(event)
41
+ end
42
+
43
+ ##
44
+ # Hook into the thread pool availability check for monitoring
45
+ #
46
+ def available?(an_rpc)
47
+ super.tap do |obj|
48
+ notify(:thread_pool_exhausted) unless obj
49
+ end
50
+ end
51
+
52
+ ##
53
+ # Hook into the method implementation check for monitoring
54
+ #
55
+ def implemented?(an_rpc)
56
+ super.tap do |obj|
57
+ notify(:unimplemented) unless obj
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/gruf/server.rb CHANGED
@@ -31,11 +31,11 @@ module Gruf
31
31
  ##
32
32
  # Initialize the server and load and setup the services
33
33
  #
34
- # @param [Hash] options
34
+ # @param [Hash] opts
35
35
  #
36
- def initialize(options = {})
37
- @options = options || {}
38
- @interceptors = options.fetch(:interceptor_registry, Gruf.interceptors)
36
+ def initialize(opts = {})
37
+ @options = opts || {}
38
+ @interceptors = opts.fetch(:interceptor_registry, Gruf.interceptors)
39
39
  @interceptors = Gruf::Interceptors::Registry.new unless @interceptors.is_a?(Gruf::Interceptors::Registry)
40
40
  @services = []
41
41
  @started = false
@@ -43,7 +43,8 @@ module Gruf
43
43
  @stop_server_cv = ConditionVariable.new
44
44
  @stop_server_mu = Monitor.new
45
45
  @server_mu = Monitor.new
46
- @hostname = options.fetch(:hostname, Gruf.server_binding_url)
46
+ @hostname = opts.fetch(:hostname, Gruf.server_binding_url)
47
+ @event_listener_proc = opts.fetch(:event_listener_proc, Gruf.event_listener_proc)
47
48
  setup
48
49
  end
49
50
 
@@ -53,7 +54,24 @@ module Gruf
53
54
  def server
54
55
  @server_mu.synchronize do
55
56
  @server ||= begin
56
- server = GRPC::RpcServer.new(options)
57
+ # For backward compatibility, we allow these options to be passed directly
58
+ # in the Gruf::Server options, or via Gruf.rpc_server_options.
59
+ server_options = {
60
+ pool_size: options.fetch(:pool_size, Gruf.rpc_server_options[:pool_size]),
61
+ max_waiting_requests: options.fetch(:max_waiting_requests, Gruf.rpc_server_options[:max_waiting_requests]),
62
+ poll_period: options.fetch(:poll_period, Gruf.rpc_server_options[:poll_period]),
63
+ pool_keep_alive: options.fetch(:pool_keep_alive, Gruf.rpc_server_options[:pool_keep_alive]),
64
+ connect_md_proc: options.fetch(:connect_md_proc, Gruf.rpc_server_options[:connect_md_proc]),
65
+ server_args: options.fetch(:server_args, Gruf.rpc_server_options[:server_args])
66
+ }
67
+
68
+ server = if @event_listener_proc
69
+ server_options[:event_listener_proc] = @event_listener_proc
70
+ Gruf::InstrumentableGrpcServer.new(server_options)
71
+ else
72
+ GRPC::RpcServer.new(server_options)
73
+ end
74
+
57
75
  @port = server.add_http2_port(@hostname, ssl_credentials)
58
76
  @services.each { |s| server.handle(s) }
59
77
  server
@@ -122,11 +140,11 @@ module Gruf
122
140
  #
123
141
  # @param [Class] before_class The interceptor that you want to add the new interceptor before
124
142
  # @param [Class] interceptor_class The Interceptor to add to the registry
125
- # @param [Hash] options A hash of options for the interceptor
143
+ # @param [Hash] opts A hash of options for the interceptor
126
144
  #
127
- def insert_interceptor_before(before_class, interceptor_class, options = {})
145
+ def insert_interceptor_before(before_class, interceptor_class, opts = {})
128
146
  raise ServerAlreadyStartedError if @started
129
- @interceptors.insert_before(before_class, interceptor_class, options)
147
+ @interceptors.insert_before(before_class, interceptor_class, opts)
130
148
  end
131
149
 
132
150
  ##
@@ -134,11 +152,11 @@ module Gruf
134
152
  #
135
153
  # @param [Class] after_class The interceptor that you want to add the new interceptor after
136
154
  # @param [Class] interceptor_class The Interceptor to add to the registry
137
- # @param [Hash] options A hash of options for the interceptor
155
+ # @param [Hash] opts A hash of options for the interceptor
138
156
  #
139
- def insert_interceptor_after(after_class, interceptor_class, options = {})
157
+ def insert_interceptor_after(after_class, interceptor_class, opts = {})
140
158
  raise ServerAlreadyStartedError if @started
141
- @interceptors.insert_after(after_class, interceptor_class, options)
159
+ @interceptors.insert_after(after_class, interceptor_class, opts)
142
160
  end
143
161
 
144
162
  ##
data/lib/gruf/version.rb CHANGED
@@ -14,5 +14,5 @@
14
14
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
15
  #
16
16
  module Gruf
17
- VERSION = '2.4.0'.freeze
17
+ VERSION = '2.4.1'.freeze
18
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gruf
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shaun McCormick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-12 00:00:00.000000000 Z
11
+ date: 2018-08-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,7 +108,7 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '4.6'
111
- description: gRPC Ruby Framework
111
+ description: gRPC Ruby Framework for building complex gRPC applications at scale
112
112
  email:
113
113
  - splittingred@gmail.com
114
114
  executables:
@@ -132,6 +132,7 @@ files:
132
132
  - lib/gruf/errors/debug_info.rb
133
133
  - lib/gruf/errors/field.rb
134
134
  - lib/gruf/errors/helpers.rb
135
+ - lib/gruf/instrumentable_grpc_server.rb
135
136
  - lib/gruf/interceptors/active_record/connection_reset.rb
136
137
  - lib/gruf/interceptors/authentication/basic.rb
137
138
  - lib/gruf/interceptors/base.rb