gruf 2.4.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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