gruf 2.13.1 → 2.17.0

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: 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
  ##