gritz-native 0.3.0 → 0.4.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: 11284795837121fb007d3158c2fa704281946741d262e2101cf0ab3b134183e5
4
- data.tar.gz: b1c1a7c414f1b3f435af88183f8da23dd7aaabcff4591355dbfdd138deeffcb0
3
+ metadata.gz: c84c8135c9f1374962f4dd07181d16d2568498ceb55c4eb03c7d0dce59d475d8
4
+ data.tar.gz: c4dbff62a8188ea82f4c49eb4a7b470a4fee5cbf9199489f88bbab74b8ee08fb
5
5
  SHA512:
6
- metadata.gz: 832837820eb0ecb86bef7fe3cf84514f52a22b92933fb08bf9fd8d3cb48fee690585ba42c853bee984bdd801fb93c7fdaa96e5a873ffee93b976b417f99f08b0
7
- data.tar.gz: f7b4a6ba6ba8ad367db6d6d486fc041471f73a7fa72663ed90ee71ed1445122d287c92c2ceaef986b9b7572445f3c03960e4a65f01af4588be904b2c67d251a8
6
+ metadata.gz: c61a25e22d150d219828f0a07f9dc39069fb792b3c8b5fefd82b6af6637071d7849df836736bcfe600da23eff7d8f39d604f8b5602966768e92dcc3915f71fb1
7
+ data.tar.gz: 1d4279464949f4bfacaf45405bc9c670de00b25673d3a5dc6ec941de86c24bd9cb475b6db5790000425fb6c74883042515f65b645b5fefb875e33ebfb739723b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ - Connect `Gritz::Client` to all four native RPC forms with shared worker-local channels and deferred credentials.
6
+ - Cancel unfinished response streams and decode downstream status, trailers and protobuf rich error details into typed Gritz errors.
7
+ - Pass retry and load-balancing service configuration to the native channel.
8
+ - Run installed worker telemetry with real-server test helpers and close it after the final RPC observations.
9
+
3
10
  ## 0.3.0
4
11
 
5
12
  - Serve TLS and mTLS connections and expose authenticated client certificates to handlers.
data/README.md CHANGED
@@ -10,6 +10,10 @@ require "gritz/native"
10
10
 
11
11
  The entry point loads `gritz-core` and registers `Gritz::Transport::Native`. The configuration value is `transport :native`. Use [gritz](https://github.com/gritzrpc/gritz) for the default combination and executable. The Fiber adapter `gritz-async` is planned separately.
12
12
 
13
+ The adapter also connects `Gritz::Client.define` to all four RPC forms, sharing channels within each worker and creating fresh connections after fork. It cancels unfinished streams, decodes rich downstream errors, and passes retry/load-balancing configuration to C-core. See the [client guide](https://github.com/gritzrpc/gritz-core/blob/main/docs/guides/clients.md).
14
+
15
+ The [three-service integration report](https://github.com/gritzrpc/gritz-otel/blob/main/docs/reports/T4-08-client-chain.md) records worker-local channels, propagated deadlines, connected server/client spans and real OTLP/HTTP metrics.
16
+
13
17
  ```ruby
14
18
  Gritz::Testing::Server.start(controllers: [GreeterController]) do |server|
15
19
  stub = Helloworld::Greeter::Stub.new(server.address, :this_channel_is_insecure)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gritz
4
4
  module Native
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
data/lib/gritz/native.rb CHANGED
@@ -12,4 +12,7 @@ require_relative "transport/native/call"
12
12
  require_relative "transport/native/bridge"
13
13
  require_relative "transport/native/server"
14
14
  require_relative "transport/native/health"
15
+ require_relative "transport/native/client"
15
16
  require_relative "testing/server"
17
+
18
+ Gritz::Client.adapter = Gritz::Transport::Native::Client
@@ -39,10 +39,11 @@ module Gritz
39
39
  begin
40
40
  @config.preload! if @config.preload_app?
41
41
  router = Router.new(controllers: @config.controllers, strict: @config.strict_routes, logger: @logger)
42
- dispatcher = Dispatcher.new(router:, middleware: @config.middleware, logger: @logger)
43
- @transport = Transport::Native.new(config: @config, dispatcher:, logger: @logger)
44
42
  @lifecycle_started = true
45
43
  @config.run_hooks(:on_worker_boot, 0)
44
+ @recorder = @config.metrics_recorder_factory&.call(worker: 0)
45
+ dispatcher = Dispatcher.new(router:, middleware: @config.middleware, logger: @logger, metrics: @recorder)
46
+ @transport = Transport::Native.new(config: @config, dispatcher:, logger: @logger)
46
47
  @port = transport.bind
47
48
  @address = @config.bind.sub(/:\d+\z/, ":#{port}")
48
49
  transport.start
@@ -63,16 +64,30 @@ module Gritz
63
64
  return if @stopped
64
65
 
65
66
  @stopped = true
67
+ deadline = monotonic + @config.shutdown_timeout
66
68
  begin
67
69
  @health_stop&.push(true)
68
- if @health_thread && !@health_thread.join(@config.shutdown_timeout)
70
+ if @health_thread && !@health_thread.join([deadline - monotonic, 0].max)
69
71
  @health_thread.kill.join
70
72
  end
71
- transport&.stop(deadline: Time.now + @config.shutdown_timeout)
73
+ transport&.stop(deadline: Time.now + [deadline - monotonic, 0].max)
72
74
  ensure
73
- @config.run_hooks(:on_worker_shutdown, 0) if @lifecycle_started
75
+ begin
76
+ @config.run_hooks(:on_worker_shutdown, 0) if @lifecycle_started
77
+ ensure
78
+ begin
79
+ @recorder&.observe_rejected(transport.stats.fetch(:rejected_total, 0)) if transport
80
+ @recorder&.observe_worker({ pid: Process.pid, index: 0, state: "stopped" })
81
+ ensure
82
+ @recorder&.close(timeout: [deadline - monotonic, 0].max)
83
+ end
84
+ end
74
85
  end
75
86
  end
87
+
88
+ private
89
+
90
+ def monotonic = Process.clock_gettime(Process::CLOCK_MONOTONIC)
76
91
  end
77
92
  end
78
93
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "client_invocation"
4
+
5
+ module Gritz
6
+ module Transport
7
+ class Native
8
+ # Builds official gRPC connections only on the first process-local client call.
9
+ # @api private
10
+ class Client
11
+ Connection = Struct.new(:target, :credentials, :args, :channel, keyword_init: true)
12
+
13
+ def self.connect(target:, credentials:, args:)
14
+ resolved = credentials.respond_to?(:call) ? credentials.call : credentials
15
+ resolved = :this_channel_is_insecure if resolved == :insecure
16
+ channel_credentials = resolved.is_a?(GRPC::Core::CallCredentials) ? GRPC::Core::ChannelCredentials.new : resolved
17
+ channel = GRPC::ClientStub.setup_channel(nil, target, channel_credentials, args.dup)
18
+ Connection.new(target:, credentials: resolved, args:, channel:)
19
+ end
20
+
21
+ def self.invoke(stub_class:, connection:, method:, request:, options:, around:, &)
22
+ stub = stub_class.new(connection.target, connection.credentials,
23
+ channel_override: connection.channel, channel_args: connection.args.dup)
24
+ stub.extend(ClientInvocation)
25
+ stub.instance_variable_set(:@gritz_around, around)
26
+ stub.public_send(method, request, options, &)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gritz
4
+ module Transport
5
+ class Native
6
+ # Captures the generated stub's route and keeps middleware around native completion.
7
+ # @api private
8
+ module ClientInvocation
9
+ { request_response: :unary, client_streamer: :client_streaming,
10
+ server_streamer: :server_streaming, bidi_streamer: :bidi }.each do |native_method, kind|
11
+ define_method(native_method) do |method, request, marshal, unmarshal, **options, &block|
12
+ terminal = lambda do |context, &_reply|
13
+ execute_gritz_client(context) do |response|
14
+ input = context.method.client_streaming? ? context.each_request : context.request
15
+ super(method, input, marshal, unmarshal, **context.options.merge(return_op: true), &response)
16
+ end
17
+ end
18
+ @gritz_around.call(terminal, method:, kind:, request:, options:, &block)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def execute_gritz_client(context)
25
+ context.check_deadline!
26
+ context.check_cancelled!
27
+ response = if context.method.server_streaming?
28
+ lambda do |message|
29
+ context.check_deadline!
30
+ context.check_cancelled!
31
+ context.response_block.call(message)
32
+ end
33
+ end
34
+ operation = yield(response)
35
+ context.operation = operation
36
+ result = operation.execute
37
+ completed = true
38
+ result
39
+ rescue GRPC::BadStatus => e
40
+ raise decode_gritz_error(e)
41
+ ensure
42
+ if operation && !completed
43
+ context.store[:gritz_cancelled] = true
44
+ cancel_gritz_operation(operation)
45
+ end
46
+ end
47
+
48
+ def cancel_gritz_operation(operation)
49
+ operation.cancel
50
+ rescue GRPC::Core::CallError
51
+ # A received non-OK status may already have closed the native call.
52
+ nil
53
+ end
54
+
55
+ def decode_gritz_error(error)
56
+ status = begin
57
+ error.to_rpc_status
58
+ rescue Google::Protobuf::ParseError, TypeError, ArgumentError
59
+ nil
60
+ end
61
+ details = if status && status.code == error.code
62
+ status.details.map { |any| decode_gritz_detail(any) }
63
+ else
64
+ []
65
+ end
66
+ code = error.code.is_a?(Integer) && (1...Errors::CODES.length).cover?(error.code) ? error.code : 2
67
+ klass = Errors.for_code(code)
68
+ klass.new(error.details, details:, metadata: error.metadata.dup, remote: true)
69
+ end
70
+
71
+ def decode_gritz_detail(any)
72
+ descriptor = Google::Protobuf::DescriptorPool.generated_pool.lookup(any.type_name)
73
+ descriptor.respond_to?(:msgclass) ? any.unpack(descriptor.msgclass) : any
74
+ rescue Google::Protobuf::ParseError, TypeError, ArgumentError
75
+ any
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gritz-native
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yudai Takada
@@ -35,14 +35,14 @@ dependencies:
35
35
  requirements:
36
36
  - - '='
37
37
  - !ruby/object:Gem::Version
38
- version: 0.3.0
38
+ version: 0.4.0
39
39
  type: :runtime
40
40
  prerelease: false
41
41
  version_requirements: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - '='
44
44
  - !ruby/object:Gem::Version
45
- version: 0.3.0
45
+ version: 0.4.0
46
46
  - !ruby/object:Gem::Dependency
47
47
  name: grpc
48
48
  requirement: !ruby/object:Gem::Requirement
@@ -81,6 +81,8 @@ files:
81
81
  - lib/gritz/transport/native.rb
82
82
  - lib/gritz/transport/native/bridge.rb
83
83
  - lib/gritz/transport/native/call.rb
84
+ - lib/gritz/transport/native/client.rb
85
+ - lib/gritz/transport/native/client_invocation.rb
84
86
  - lib/gritz/transport/native/health.rb
85
87
  - lib/gritz/transport/native/server.rb
86
88
  homepage: https://github.com/gritzrpc/gritz-native