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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +4 -0
- data/lib/gritz/native/version.rb +1 -1
- data/lib/gritz/native.rb +3 -0
- data/lib/gritz/testing/server.rb +20 -5
- data/lib/gritz/transport/native/client.rb +31 -0
- data/lib/gritz/transport/native/client_invocation.rb +80 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c84c8135c9f1374962f4dd07181d16d2568498ceb55c4eb03c7d0dce59d475d8
|
|
4
|
+
data.tar.gz: c4dbff62a8188ea82f4c49eb4a7b470a4fee5cbf9199489f88bbab74b8ee08fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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)
|
data/lib/gritz/native/version.rb
CHANGED
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
|
data/lib/gritz/testing/server.rb
CHANGED
|
@@ -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(
|
|
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 +
|
|
73
|
+
transport&.stop(deadline: Time.now + [deadline - monotonic, 0].max)
|
|
72
74
|
ensure
|
|
73
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|