gruf 2.13.1 → 2.17.0

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: 0fdf24359533f0c174160c022698b4c3dff0358700a340f470abf68149619380
4
- data.tar.gz: f5a07f92989697f5ca2625f21ab150dad8b0e9c848ace26833e11541bf83f2e8
3
+ metadata.gz: feddcb49d9325078dfb0d7367f5e82dfea0d46a82e56784b10484c873ab375a5
4
+ data.tar.gz: b4614f08c2b6d8f62edfc1f34be0dffc7af4825b680e49aaf5c6160315314d32
5
5
  SHA512:
6
- metadata.gz: 1108582d41b071f520d393e9c4946fe90fe88c93cc26dbbb7c6980b580c586d27ddb69fee0b5ff7a89e2accd4aabd25f39635f0b04833a41eb369ce0b98bf6c0
7
- data.tar.gz: 8082bb3b4f30ed4d45d7928fc7c34aaa2b13204d3adca682d9fab51641a72dac3692227edfe789062b2ef1b3e0b78b8cdb495e97c9cfe952bf59a9ee0370f408
6
+ metadata.gz: 3e67b5fdaf175a0c8b839532d6024415f12c1fdbd91f44fdec5cfa1ac0d7c33e821b740a192f251664f6ff09ef5dc96d903c480a474d2f53d4b876189023550c
7
+ data.tar.gz: 4061112bf7ea91180fb5ea16d5d135cc3a1a7dab7d94ba479d462f3acca3aa183373f64eb5d8e7d9ad0442a457ed5b25e806e407f27c9d3c6ee0658d153b654c
data/CHANGELOG.md CHANGED
@@ -2,11 +2,59 @@ Changelog for the gruf gem. This includes internal history before the gem was ma
2
2
 
3
3
  ### Pending release
4
4
 
5
+ ### 2.17.0
6
+
7
+ * [#179] Add Ruby 3.2 support
8
+ * [#178] Introduce read-write lock for code reloading; cover controller class resolution with code reloading.
9
+ * [#180] Support multiple databases connection reset in `Gruf::Interceptors::ActiveRecord::ConnectionReset`.
10
+
11
+ ### 2.16.2
12
+
13
+ * [#175] Fix code reload thread-safety. Calls to `Zeitwerk::Loader#setup` are now made in a thread-safe manner.
14
+
15
+ ### 2.16.1
16
+
17
+ * Fix issue where default gRPC health check was loaded even if unused or not desired; now only loaded when requested
18
+
19
+ ### 2.16.0
20
+
21
+ - Add opt-in ability to serve the official [gRPC health check](https://github.com/grpc/grpc/blob/master/src/ruby/pb/grpc/health/v1/health_services_pb.rb)
22
+ automatically via `health_check_enabled` configuration option (or `GRUF_HEALTH_CHECK_ENABLED` environment
23
+ variable).
24
+ - Add `health_check_hook` configuration option to implement a custom response for the above gRPC built-in health check
25
+ - [#156] Allow passing a specific list of services to run via the gruf binstub
26
+ - [#163] Add `context` hash attribute to `Gruf::Controllers::Request` to allow interceptors to pass information down
27
+ to a gruf controller
28
+ - Drop Ruby 2.6 support (EOL'ed on March 31st, 2022)
29
+
30
+ ### 2.15.1
31
+
32
+ - Fix issue where GRPC_SERVER_POOL_KEEP_ALIVE and GRPC_SERVER_POLL_PERIOD when set via ENV are not cast to int
33
+
34
+ ### 2.15.0
35
+
36
+ - NOTE: This changes the way that gruf controllers are autoloaded. See [UPGRADING.md] for more details.
37
+ - Autoload Gruf Controllers with zeitwerk, allowing for code reloading in development environments
38
+ - Add `GRUF_CONTROLLERS_PATH` ENV to allow ENV-based runtime configuration of path to gruf controller files
39
+ - Move `ServiceBinder` from instance to static class for performance improvements
40
+ - Use zeitwerk for autoloading
41
+ - Update gem metadata
42
+ - Update `Gruf:Configuration#environment` to use `ENV.fetch`
43
+
44
+ ### 2.14.1
45
+
46
+ - Fix issue where the server object hits thread contention in certain race conditions
47
+
48
+ ### 2.14.0
49
+
50
+ - Set default client host to 0.0.0.0:9001 (same as default server host)
51
+ - Add support for Ruby 3.1
52
+
5
53
  ### 2.13.1
6
54
 
7
- - Fix issue with race condition in server starts where servers may fail to bind connections and never reach
55
+ - Fix issue with race condition in server starts where servers may fail to bind connections and never reach
8
56
  serving state (fixes #147)
9
-
57
+
10
58
  ### 2.13.0
11
59
 
12
60
  - Remove server mutex handling in deference to core grpc signal handling
@@ -18,7 +66,7 @@ Changelog for the gruf gem. This includes internal history before the gem was ma
18
66
 
19
67
  ### 2.11.0
20
68
 
21
- - Restrict grpc gem to <= 1.41.0 due to regressions in grpc 1.42.x
69
+ - Restrict grpc gem to <= 1.41.0 due to regressions in grpc 1.42.x
22
70
  - Fallback to stdout logger at INFO if no logger is setup
23
71
  - Better handling of namespace collisions with Rails
24
72
  - Add `GRPC_SERVER_HOST` and `GRPC_SERVER_PORT` for ENV configuration of the server host+port
@@ -58,7 +106,7 @@ Changelog for the gruf gem. This includes internal history before the gem was ma
58
106
 
59
107
  ### 2.8.0
60
108
 
61
- - Pass the controller request object into the request logging formatters [#92]
109
+ - Pass the controller request object into the request logging formatters [#92]
62
110
 
63
111
  ### 2.7.1
64
112
 
@@ -71,12 +119,12 @@ Changelog for the gruf gem. This includes internal history before the gem was ma
71
119
  ### 2.6.1
72
120
 
73
121
  - Add frozen_string_literal: true to files, update rubocop to 0.68
74
-
122
+
75
123
  ### 2.6.0
76
124
 
77
125
  - Drop Ruby 2.2 support
78
- - Abstract gruf controller's send to make it usable in filters
79
- - Adjusts configuration reset into a Railtie for Rails systems to ensure proper OOE
126
+ - Abstract gruf controller's send to make it usable in filters
127
+ - Adjusts configuration reset into a Railtie for Rails systems to ensure proper OOE
80
128
  - Bump rubocop to 0.64, address violations, update activesupport/concurrent-ruby dependencies to have a min version
81
129
 
82
130
  ### 2.5.2
data/README.md CHANGED
@@ -17,7 +17,7 @@ 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-3.0.
20
+ gruf currently has active support for gRPC 1.10.x+. gruf is compatible and tested with Ruby 2.6-3.2.
21
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) or [dry-rb](https://dry-rb.org/), for instance).
23
23
 
data/gruf.gemspec CHANGED
@@ -15,8 +15,7 @@
15
15
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
16
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
17
  #
18
- $LOAD_PATH.push File.expand_path('lib', __dir__)
19
- require 'gruf/version'
18
+ require_relative 'lib/gruf/version'
20
19
 
21
20
  Gem::Specification.new do |spec|
22
21
  spec.name = 'gruf'
@@ -33,17 +32,19 @@ Gem::Specification.new do |spec|
33
32
  spec.executables << 'gruf'
34
33
  spec.require_paths = ['lib']
35
34
 
36
- spec.required_ruby_version = '>= 2.6', '< 3.1'
35
+ spec.required_ruby_version = '>= 2.7', '< 3.3'
37
36
 
38
- spec.metadata['rubygems_mfa_required'] = 'true'
37
+ spec.metadata = {
38
+ 'bug_tracker_uri' => 'https://github.com/bigcommerce/gruf/issues',
39
+ 'changelog_uri' => 'https://github.com/bigcommerce/gruf/CHANGELOG.md',
40
+ 'homepage_uri' => 'https://github.com/bigcommerce/gruf',
41
+ 'rubygems_mfa_required' => 'true',
42
+ 'source_code_uri' => 'https://github.com/bigcommerce/gruf',
43
+ 'wiki_uri' => 'https://github.com/bigcommerce/gruf/wiki'
44
+ }
39
45
 
40
46
  spec.add_development_dependency 'bundler-audit', '>= 0.6'
41
- # rubocop:disable Gemspec/RubyVersionGlobalsUsage
42
- spec.add_development_dependency(
43
- 'factory_bot',
44
- (Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5') ? '>= 6.1' : '~> 5.2')
45
- )
46
- # rubocop:enable Gemspec/RubyVersionGlobalsUsage
47
+ spec.add_development_dependency 'factory_bot', '>= 6.1'
47
48
  spec.add_development_dependency 'ffaker', '>= 2.15'
48
49
  spec.add_development_dependency 'pry', '~> 0.12'
49
50
  spec.add_development_dependency 'pry-byebug', '>= 3.9'
@@ -64,4 +65,5 @@ Gem::Specification.new do |spec|
64
65
  spec.add_runtime_dependency 'json', '>= 2.3'
65
66
  spec.add_runtime_dependency 'slop', '>= 4.6'
66
67
  spec.add_runtime_dependency 'thwait', '>= 0.1'
68
+ spec.add_runtime_dependency 'zeitwerk', '>= 2'
67
69
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2022-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ ##
20
+ # Module for accessing Gruf zeitwerk-based autoloaders
21
+ #
22
+ module Autoloaders
23
+ class << self
24
+ include Enumerable
25
+
26
+ ##
27
+ # Initialize the autoloaders with a given controllers path
28
+ #
29
+ # @param [String] controllers_path The path to Gruf Controllers
30
+ #
31
+ def load!(controllers_path:)
32
+ controllers(controllers_path: controllers_path)
33
+ end
34
+
35
+ ##
36
+ # Enumerate across the managed set of autoloaders
37
+ #
38
+ def each
39
+ yield controllers
40
+ end
41
+
42
+ ##
43
+ # Reload all files managed by the autoloader
44
+ #
45
+ def reload
46
+ each(&:reload)
47
+ end
48
+
49
+ ##
50
+ # Lazily instantiate and memoize the Gruf Controllers autoloader in a thread-safe manner
51
+ #
52
+ # @return [::Gruf::Controllers::Autoloader]
53
+ #
54
+ # rubocop:disable ThreadSafety/InstanceVariableInClassMethod
55
+ def controllers(controllers_path: nil)
56
+ controllers_mutex do
57
+ @controllers ||= ::Gruf::Controllers::Autoloader.new(path: controllers_path || ::Gruf.controllers_path)
58
+ end
59
+ end
60
+ # rubocop:enable ThreadSafety/InstanceVariableInClassMethod
61
+
62
+ ##
63
+ # Handle mutations to the controllers autoloader in a thread-safe manner
64
+ #
65
+ # rubocop:disable ThreadSafety/InstanceVariableInClassMethod
66
+ def controllers_mutex(&block)
67
+ @controllers_mutex ||= begin
68
+ require 'monitor'
69
+ Monitor.new
70
+ end
71
+ @controllers_mutex.synchronize(&block)
72
+ end
73
+ # rubocop:enable ThreadSafety/InstanceVariableInClassMethod
74
+ end
75
+ end
76
+ end
@@ -23,8 +23,14 @@ module Gruf
23
23
  # Handles execution of the gruf binstub, along with command-line arguments
24
24
  #
25
25
  class Executor
26
+ class NoServicesBoundError < StandardError; end
27
+
26
28
  ##
27
29
  # @param [Hash|ARGV]
30
+ # @param [::Gruf::Server|NilClass] server
31
+ # @param [Array<Class>|NilClass] services
32
+ # @param [Gruf::Hooks::Executor|NilClass] hook_executor
33
+ # @param [Logger|NilClass] logger
28
34
  #
29
35
  def initialize(
30
36
  args = ARGV,
@@ -35,7 +41,7 @@ module Gruf
35
41
  )
36
42
  @args = args
37
43
  setup! # ensure we set some defaults from CLI here so we can allow configuration
38
- @services = services || Gruf.services
44
+ @services = services.is_a?(Array) ? services : []
39
45
  @hook_executor = hook_executor || Gruf::Hooks::Executor.new(hooks: Gruf.hooks&.prepare)
40
46
  @server = server || Gruf::Server.new(Gruf.server_options)
41
47
  @logger = logger || Gruf.logger || ::Logger.new($stderr)
@@ -46,8 +52,11 @@ module Gruf
46
52
  #
47
53
  def run
48
54
  exception = nil
55
+
56
+ # allow lazy registering globally as late as possible, this allows more flexible binstub injections
57
+ register_services!
58
+
49
59
  begin
50
- @services.each { |s| @server.add_service(s) }
51
60
  @hook_executor.call(:before_server_start, server: @server)
52
61
  @server.start!
53
62
  rescue StandardError => e
@@ -67,14 +76,15 @@ module Gruf
67
76
  # Setup options for CLI execution and configure Gruf based on inputs
68
77
  #
69
78
  def setup!
70
- opts = parse_options
79
+ @options = parse_options
71
80
 
72
- Gruf.server_binding_url = opts[:host] if opts[:host]
73
- if opts.suppress_default_interceptors?
81
+ Gruf.server_binding_url = @options[:host] if @options[:host]
82
+ if @options.suppress_default_interceptors?
74
83
  Gruf.interceptors.remove(Gruf::Interceptors::ActiveRecord::ConnectionReset)
75
84
  Gruf.interceptors.remove(Gruf::Interceptors::Instrumentation::OutputMetadataTimer)
76
85
  end
77
- Gruf.backtrace_on_error = true if opts.backtrace_on_error?
86
+ Gruf.backtrace_on_error = true if @options.backtrace_on_error?
87
+ Gruf.health_check_enabled = true if @options.health_check?
78
88
  end
79
89
 
80
90
  ##
@@ -89,6 +99,8 @@ module Gruf
89
99
  exit(0)
90
100
  end
91
101
  o.string '--host', 'Specify the binding url for the gRPC service'
102
+ o.string '--services', 'Optional. Run gruf with only the passed gRPC service classes (comma-separated)'
103
+ o.bool '--health-check', 'Serve the default gRPC health check (defaults to false). '
92
104
  o.bool '--suppress-default-interceptors', 'Do not use the default interceptors'
93
105
  o.bool '--backtrace-on-error', 'Push backtraces on exceptions to the error serializer'
94
106
  o.null '-v', '--version', 'print gruf version' do
@@ -97,6 +109,83 @@ module Gruf
97
109
  end
98
110
  end
99
111
  end
112
+
113
+ ##
114
+ # Register services; note that this happens after gruf is initialized, and right before the server is run.
115
+ # This will interpret the services to run in the following precedence:
116
+ # 1. initializer arguments to the executor
117
+ # 2. ARGV options (the --services option)
118
+ # 3. services set to the global gruf configuration (Gruf.services)
119
+ #
120
+ def register_services!
121
+ # wait to load controllers until last possible second to allow late configuration
122
+ ::Gruf.autoloaders.load!(controllers_path: Gruf.controllers_path)
123
+
124
+ services = determine_services(@services)
125
+ services = bind_health_check!(services) if health_check_enabled?
126
+
127
+ services.map! { |s| s.is_a?(Class) ? s : s.constantize }
128
+
129
+ if services.any?
130
+ services.each { |s| @server.add_service(s) }
131
+ return
132
+ end
133
+
134
+ raise NoServicesBoundError
135
+ rescue NoServicesBoundError
136
+ @logger.fatal 'FATAL ERROR: No services bound to this gruf process; please bind a service to a Gruf ' \
137
+ 'controller to start the server successfully'
138
+ exit(1)
139
+ rescue NameError => e
140
+ @logger.fatal 'FATAL ERROR: Could not start server; passed services to run are not loaded or valid ' \
141
+ "constants: #{e.message}"
142
+ exit(1)
143
+ rescue StandardError => e
144
+ @logger.fatal "FATAL ERROR: Could not start server: #{e.message}"
145
+ exit(1)
146
+ end
147
+
148
+ ##
149
+ # @return [Boolean]
150
+ #
151
+ def health_check_enabled?
152
+ ::Gruf.health_check_enabled
153
+ end
154
+
155
+ ##
156
+ # Load the health check if enabled into the services array
157
+ #
158
+ # @param [Array<Class>] services
159
+ # @return [Array<Class>]
160
+ #
161
+ def bind_health_check!(services)
162
+ # do this here to trigger autoloading the controller in zeitwerk, since we don't explicitly load this
163
+ # controller. This binds the service and makes sure the method handlers are setup.
164
+ require 'gruf/controllers/health_controller'
165
+ # if we're already bound to the services array (say someone explicitly passes the health check in, skip)
166
+ return services if services.include?(::Grpc::Health::V1::Health::Service)
167
+
168
+ # otherwise, manually add the grpc service
169
+ services << ::Grpc::Health::V1::Health::Service
170
+ services
171
+ end
172
+
173
+ ##
174
+ # Determine how we load services (initializer -> ARGV -> Gruf.services)
175
+ #
176
+ # @return [Array<Class>]
177
+ #
178
+ def determine_services(services = [])
179
+ # first check initializer arguments
180
+ return services if services.any?
181
+
182
+ # next check CLI arguments
183
+ services = @options[:services].to_s.split(',').map(&:strip).uniq
184
+ # finally, if none, use global gruf autoloaded services
185
+ services = (::Gruf.services || []) unless services.any?
186
+
187
+ services
188
+ end
100
189
  end
101
190
  end
102
191
  end
@@ -37,33 +37,5 @@ module Gruf
37
37
  super
38
38
  end
39
39
  end
40
-
41
- ##
42
- # See https://github.com/grpc/grpc-go/blob/master/codes/codes.go for a detailed summary of each error type
43
- #
44
- module Errors
45
- class Base < Gruf::Client::Error; end
46
- class Error < Base; end
47
- class Validation < Base; end
48
-
49
- class Ok < Base; end
50
-
51
- class InvalidArgument < Validation; end
52
- class NotFound < Validation; end
53
- class AlreadyExists < Validation; end
54
- class OutOfRange < Validation; end
55
-
56
- class Cancelled < Error; end
57
- class DataLoss < Error; end
58
- class DeadlineExceeded < Error; end
59
- class FailedPrecondition < Error; end
60
- class Internal < Error; end
61
- class PermissionDenied < Error; end
62
- class ResourceExhausted < Error; end
63
- class Unauthenticated < Error; end
64
- class Unavailable < Error; end
65
- class Unimplemented < Error; end
66
- class Unknown < Error; end
67
- end
68
40
  end
69
41
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ class Client < SimpleDelegator
20
+ ##
21
+ # See https://github.com/grpc/grpc-go/blob/master/codes/codes.go for a detailed summary of each error type
22
+ #
23
+ module Errors
24
+ class Base < Gruf::Client::Error; end
25
+ class Error < Base; end
26
+ class Validation < Base; end
27
+
28
+ class Ok < Base; end
29
+
30
+ class InvalidArgument < Validation; end
31
+ class NotFound < Validation; end
32
+ class AlreadyExists < Validation; end
33
+ class OutOfRange < Validation; end
34
+
35
+ class Cancelled < Error; end
36
+ class DataLoss < Error; end
37
+ class DeadlineExceeded < Error; end
38
+ class FailedPrecondition < Error; end
39
+ class Internal < Error; end
40
+ class PermissionDenied < Error; end
41
+ class ResourceExhausted < Error; end
42
+ class Unauthenticated < Error; end
43
+ class Unavailable < Error; end
44
+ class Unimplemented < Error; end
45
+ class Unknown < Error; end
46
+ end
47
+ end
48
+ end
data/lib/gruf/client.rb CHANGED
@@ -15,9 +15,6 @@
15
15
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
16
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
17
  #
18
- require_relative 'client/error'
19
- require_relative 'client/error_factory'
20
-
21
18
  module Gruf
22
19
  ##
23
20
  # Abstracts out the calling interface for interacting with gRPC clients. Streamlines calling and provides
@@ -77,6 +77,13 @@ module Gruf
77
77
  # @return [Integer] Internal cache expiry period (in seconds) for the SynchronizedClient
78
78
  # @!attribute rpc_server_options
79
79
  # @return [Hash] A hash of RPC options for GRPC server configuration
80
+ # @!attribute health_check_enabled
81
+ # @return [Boolean] If true, will load and register `Gruf::Controllers::HealthController` with the default gRPC
82
+ # health check to the loaded gRPC server
83
+ # @!attribute health_check_hook
84
+ # @return [NilClass]
85
+ # @return [Proc] If set, will call this in the gRPC health check. It is required to return a
86
+ # `::Grpc::Health::V1::HealthCheckResponse` object in this proc to indicate the health of the server.
80
87
  VALID_CONFIG_KEYS = {
81
88
  root_path: '',
82
89
  server_binding_url: '0.0.0.0:9001',
@@ -84,7 +91,7 @@ module Gruf
84
91
  interceptors: nil,
85
92
  hooks: nil,
86
93
  default_channel_credentials: nil,
87
- default_client_host: '',
94
+ default_client_host: '0.0.0.0:9001',
88
95
  use_ssl: false,
89
96
  ssl_crt_file: '',
90
97
  ssl_key_file: '',
@@ -101,6 +108,8 @@ module Gruf
101
108
  use_exception_message: true,
102
109
  internal_error_message: 'Internal Server Error',
103
110
  event_listener_proc: nil,
111
+ health_check_enabled: false,
112
+ health_check_hook: nil,
104
113
  synchronized_client_internal_cache_expiry: 60,
105
114
  rpc_server_options: {
106
115
  pool_size: GRPC::RpcServer::DEFAULT_POOL_SIZE,
@@ -165,14 +174,15 @@ module Gruf
165
174
  determine_loggers
166
175
  self.ssl_crt_file = "#{root_path}config/ssl/#{environment}.crt"
167
176
  self.ssl_key_file = "#{root_path}config/ssl/#{environment}.key"
168
- self.controllers_path = root_path.to_s.empty? ? 'app/rpc' : "#{root_path}/app/rpc"
177
+ cp = ::ENV.fetch('GRUF_CONTROLLERS_PATH', 'app/rpc').to_s
178
+ self.controllers_path = root_path.to_s.empty? ? cp : "#{root_path}/#{cp}"
169
179
  self.backtrace_on_error = ::ENV.fetch('GRPC_BACKTRACE_ON_ERROR', 0).to_i.positive?
170
180
  self.rpc_server_options = {
171
181
  max_waiting_requests: ::ENV.fetch('GRPC_SERVER_MAX_WAITING_REQUESTS',
172
182
  GRPC::RpcServer::DEFAULT_MAX_WAITING_REQUESTS).to_i,
173
183
  pool_size: ::ENV.fetch('GRPC_SERVER_POOL_SIZE', GRPC::RpcServer::DEFAULT_POOL_SIZE).to_i,
174
- pool_keep_alive: ::ENV.fetch('GRPC_SERVER_POOL_KEEP_ALIVE', GRPC::Pool::DEFAULT_KEEP_ALIVE),
175
- poll_period: ::ENV.fetch('GRPC_SERVER_POLL_PERIOD', GRPC::RpcServer::DEFAULT_POLL_PERIOD),
184
+ pool_keep_alive: ::ENV.fetch('GRPC_SERVER_POOL_KEEP_ALIVE', GRPC::Pool::DEFAULT_KEEP_ALIVE).to_i,
185
+ poll_period: ::ENV.fetch('GRPC_SERVER_POLL_PERIOD', GRPC::RpcServer::DEFAULT_POLL_PERIOD).to_i,
176
186
  connect_md_proc: nil,
177
187
  server_args: {}
178
188
  }
@@ -180,9 +190,17 @@ module Gruf
180
190
  interceptors.use(::Gruf::Interceptors::ActiveRecord::ConnectionReset)
181
191
  interceptors.use(::Gruf::Interceptors::Instrumentation::OutputMetadataTimer)
182
192
  end
193
+ self.health_check_enabled = ::ENV.fetch('GRUF_HEALTH_CHECK_ENABLED', 0).to_i.positive?
183
194
  options
184
195
  end
185
196
 
197
+ ##
198
+ # @return [Boolean]
199
+ #
200
+ def development?
201
+ environment == 'development'
202
+ end
203
+
186
204
  private
187
205
 
188
206
  ##
@@ -194,7 +212,7 @@ module Gruf
194
212
  if defined?(::Rails)
195
213
  ::Rails.env.to_s
196
214
  else
197
- (ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development').to_s
215
+ ENV.fetch('RACK_ENV') { ENV.fetch('RAILS_ENV', 'development') }.to_s
198
216
  end
199
217
  end
200
218
 
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ require 'concurrent/atomic/read_write_lock'
19
+
20
+ module Gruf
21
+ module Controllers
22
+ ##
23
+ # Handles autoloading of Gruf controllers in the application path. This allows for code reloading on Gruf
24
+ # controllers.
25
+ #
26
+ class Autoloader
27
+ include ::Gruf::Loggable
28
+
29
+ # @!attribute [r] path
30
+ # @return [String] The path for this autoloader
31
+ attr_reader :path
32
+
33
+ ##
34
+ # @param [String] path
35
+ # @param [Boolean] reloading
36
+ # @param [String] tag
37
+ #
38
+ def initialize(path:, reloading: nil, tag: nil)
39
+ super()
40
+ @path = path
41
+ @loader = ::Zeitwerk::Loader.new
42
+ @loader.tag = tag || 'gruf-controllers'
43
+ @setup = false
44
+ @reloading_enabled = reloading || ::Gruf.development?
45
+ setup!
46
+ end
47
+
48
+ ##
49
+ # Reload all files managed by the autoloader, if reloading is enabled
50
+ #
51
+ def reload
52
+ return unless @reloading_enabled
53
+
54
+ reload_lock.with_write_lock do
55
+ @loader.reload
56
+ end
57
+ end
58
+
59
+ def with_fresh_controller(controller_name)
60
+ return yield(controller_name.constantize) unless @reloading_enabled
61
+
62
+ ::Gruf::Autoloaders.reload
63
+ reload_lock.with_read_lock do
64
+ yield(controller_name.constantize)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ ##
71
+ # @return [Boolean]
72
+ #
73
+ def setup!
74
+ return true if @setup
75
+
76
+ return false unless File.directory?(@path)
77
+
78
+ @loader.enable_reloading if @reloading_enabled
79
+ @loader.ignore("#{@path}/**/*_pb.rb")
80
+ @loader.push_dir(@path)
81
+ @loader.setup
82
+ # always eager load RPC files, so that the service binder can bind the Gruf::Controller instantiation
83
+ # to the gRPC Service classes
84
+ @loader.eager_load
85
+ @setup = true
86
+ end
87
+
88
+ ##
89
+ # Handle thread-safe access to the loader
90
+ #
91
+ def reload_lock
92
+ @reload_lock ||= Concurrent::ReadWriteLock.new
93
+ end
94
+ end
95
+ end
96
+ end
@@ -15,9 +15,6 @@
15
15
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
16
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
17
  #
18
- require_relative 'request'
19
- require_relative 'service_binder'
20
-
21
18
  module Gruf
22
19
  module Controllers
23
20
  ##
@@ -67,11 +64,12 @@ module Gruf
67
64
  #
68
65
  def self.bind(service)
69
66
  service_class = service.name.constantize
70
- Gruf.services << service_class
67
+ ::Gruf.logger.debug "[gruf] Binding #{service_class} to #{name}"
68
+ ::Gruf.services << service_class
71
69
  # rubocop:disable ThreadSafety/InstanceVariableInClassMethod
72
70
  @bound_service = service_class
73
71
  # rubocop:enable ThreadSafety/InstanceVariableInClassMethod
74
- ServiceBinder.new(service_class).bind!(self)
72
+ ServiceBinder.bind!(service: service_class, controller: self)
75
73
  end
76
74
 
77
75
  ##