async-grpc 0.8.0 → 0.9.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
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +43 -0
- data/context/index.yaml +2 -0
- data/lib/async/grpc/client.rb +7 -3
- data/lib/async/grpc/dispatcher.rb +184 -72
- data/lib/async/grpc/error.rb +1 -0
- data/lib/async/grpc/version.rb +1 -1
- data/lib/async/grpc.rb +1 -0
- data/license.md +1 -0
- data/readme.md +11 -7
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +6 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 031cde64ff322b5e4d247cec205cdbe17219d455240866df154fca3f370e675c
|
|
4
|
+
data.tar.gz: d926dbe26788e37bbcb219cce9224c40f5c282bb0ed84c42fcbb6118472da69f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a5cb6a72c70046df6b85f287d96f04b2ab12a807945025b5c38b924f6dcb5480b012444b42b4e857e63f1b535c5c6db9cd9c6fe6ad9f04c6f7976065cdcfbf5
|
|
7
|
+
data.tar.gz: dc65cc27dfcb838ce8b77f805012333edeb0813707e9d646a805c9776f9e7fae9d2dca78cd3a5dc29ceb27b99ff435cb9f84adcbeba131362aeb40ef7dc37020
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/context/getting-started.md
CHANGED
|
@@ -126,6 +126,49 @@ Async do
|
|
|
126
126
|
end
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
### Instrumentation
|
|
130
|
+
|
|
131
|
+
Subclass the dispatcher and override {ruby Async::GRPC::Dispatcher#emit_completion} to record completion metrics or logs.
|
|
132
|
+
The hook runs once per request after the final gRPC status is assigned, including routing failures and completed streaming calls:
|
|
133
|
+
|
|
134
|
+
``` ruby
|
|
135
|
+
class InstrumentedDispatcher < Async::GRPC::Dispatcher
|
|
136
|
+
protected
|
|
137
|
+
|
|
138
|
+
def emit_completion(call, error = nil)
|
|
139
|
+
Console.info(self, "Request completed!", path: call.request.path, status: call.status)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
A cancelled request has a status of `Protocol::GRPC::Status::CANCELLED` and no `error`. Exceptions raised by the hook are logged and ignored.
|
|
145
|
+
|
|
146
|
+
Translate expected application errors at the service boundary by raising {ruby Protocol::GRPC::Error}:
|
|
147
|
+
|
|
148
|
+
``` ruby
|
|
149
|
+
class ApplicationService < Async::GRPC::Service
|
|
150
|
+
def find_record(input, output, call)
|
|
151
|
+
output.write(Record.find(input.read.id))
|
|
152
|
+
rescue ActiveRecord::RecordNotFound => error
|
|
153
|
+
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::NOT_FOUND, error.message)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Override {ruby Async::GRPC::Dispatcher#invoke_service} when you need middleware around service execution:
|
|
159
|
+
|
|
160
|
+
``` ruby
|
|
161
|
+
class ApplicationDispatcher < Async::GRPC::Dispatcher
|
|
162
|
+
protected
|
|
163
|
+
|
|
164
|
+
def invoke_service(service, handler_method, input, output, call)
|
|
165
|
+
RequestContext.for(call.request) do
|
|
166
|
+
super
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
129
172
|
## Complete Example
|
|
130
173
|
|
|
131
174
|
``` ruby
|
data/context/index.yaml
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
---
|
|
4
4
|
description: Client and server implementation for gRPC using Async.
|
|
5
5
|
metadata:
|
|
6
|
+
bug_tracker_uri: https://github.com/socketry/async-grpc/issues
|
|
7
|
+
changelog_uri: https://github.com/socketry/async-grpc/blob/main/releases.md
|
|
6
8
|
documentation_uri: https://socketry.github.io/async-grpc/
|
|
7
9
|
source_code_uri: https://github.com/socketry/async-grpc.git
|
|
8
10
|
files:
|
data/lib/async/grpc/client.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
|
+
# Copyright, 2026, by Alex Watt.
|
|
5
6
|
|
|
6
7
|
require "async"
|
|
7
8
|
require "async/http/client"
|
|
@@ -10,7 +11,6 @@ require "async/http/endpoint"
|
|
|
10
11
|
require "protocol/http"
|
|
11
12
|
require "protocol/grpc"
|
|
12
13
|
require "protocol/grpc/interface"
|
|
13
|
-
require "protocol/grpc/methods"
|
|
14
14
|
require "protocol/grpc/body/readable_body"
|
|
15
15
|
require "protocol/grpc/body/writable_body"
|
|
16
16
|
require "protocol/grpc/metadata"
|
|
@@ -123,7 +123,7 @@ module Async
|
|
|
123
123
|
raise ArgumentError, "Unknown method: #{method}" unless rpc
|
|
124
124
|
|
|
125
125
|
path = service.path(method)
|
|
126
|
-
headers = Protocol::GRPC::
|
|
126
|
+
headers = Protocol::GRPC::Metadata.build(
|
|
127
127
|
metadata: metadata,
|
|
128
128
|
timeout: timeout,
|
|
129
129
|
content_type: "application/grpc+proto"
|
|
@@ -277,6 +277,7 @@ module Async
|
|
|
277
277
|
# @parameter initial [Object | Array | Nil] Optional initial message(s) to write before waiting for the response
|
|
278
278
|
# @yields {|input, output| ...} Block to handle bidirectional streaming
|
|
279
279
|
# @returns [Protocol::GRPC::Body::ReadableBody] Readable body for streaming messages
|
|
280
|
+
# Without a block, `initial` is sent as the complete request stream and the caller owns the returned readable body.
|
|
280
281
|
# @raises [Protocol::GRPC::Error] If the gRPC call fails
|
|
281
282
|
def bidirectional_call(path, headers, request_class, response_class, encoding, initial: nil, &block)
|
|
282
283
|
body = Protocol::GRPC::Body::WritableBody.new(
|
|
@@ -284,6 +285,7 @@ module Async
|
|
|
284
285
|
message_class: request_class
|
|
285
286
|
)
|
|
286
287
|
Array(initial).each{|message| body.write(message)}
|
|
288
|
+
body.close_write unless block_given?
|
|
287
289
|
|
|
288
290
|
http_request = Protocol::HTTP::Request["POST", path, headers, body]
|
|
289
291
|
response = call(http_request)
|
|
@@ -298,6 +300,8 @@ module Async
|
|
|
298
300
|
)
|
|
299
301
|
|
|
300
302
|
unless block_given?
|
|
303
|
+
# The caller owns the readable body and its underlying response:
|
|
304
|
+
response = nil
|
|
301
305
|
return readable_body
|
|
302
306
|
end
|
|
303
307
|
|
|
@@ -328,7 +332,7 @@ module Async
|
|
|
328
332
|
return if status == Protocol::GRPC::Status::OK
|
|
329
333
|
|
|
330
334
|
message = Protocol::GRPC::Metadata.extract_message(response.headers)
|
|
331
|
-
metadata = Protocol::GRPC::
|
|
335
|
+
metadata = Protocol::GRPC::Metadata.extract(response.headers)
|
|
332
336
|
|
|
333
337
|
remote_error = RemoteError.for(message, metadata)
|
|
334
338
|
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
4
|
# Copyright, 2025-2026, by Samuel Williams.
|
|
5
|
+
# Copyright, 2026, by Alex Watt.
|
|
5
6
|
|
|
6
7
|
require "async"
|
|
7
8
|
|
|
8
9
|
require_relative "error"
|
|
9
10
|
|
|
10
11
|
require "protocol/grpc/middleware"
|
|
11
|
-
require "protocol/grpc/methods"
|
|
12
12
|
require "protocol/grpc/call"
|
|
13
|
+
require "protocol/grpc/route"
|
|
13
14
|
require "protocol/grpc/body/readable_body"
|
|
14
15
|
require "protocol/grpc/body/writable_body"
|
|
15
16
|
require "protocol/grpc/metadata"
|
|
@@ -45,11 +46,37 @@ module Async
|
|
|
45
46
|
|
|
46
47
|
protected
|
|
47
48
|
|
|
48
|
-
#
|
|
49
|
+
# Called once after the final gRPC status has been assigned.
|
|
49
50
|
#
|
|
50
|
-
# Override this method to
|
|
51
|
+
# Override this method to record request metrics or logs. The hook also runs for
|
|
52
|
+
# routing/setup failures and after streaming calls finish. A cancelled call has
|
|
53
|
+
# a status of `Protocol::GRPC::Status::CANCELLED` and no error.
|
|
51
54
|
#
|
|
52
|
-
#
|
|
55
|
+
# Exceptions raised by this hook are ignored.
|
|
56
|
+
#
|
|
57
|
+
# @parameter call [Protocol::GRPC::Call] The call context, including the request and the response.
|
|
58
|
+
# @parameter error [Exception | Nil] The error which caused the call to fail, if any.
|
|
59
|
+
# @returns [void]
|
|
60
|
+
# @example Record the final gRPC status code
|
|
61
|
+
# def emit_completion(call, error = nil)
|
|
62
|
+
# service_name, method_name = Protocol::GRPC::Route.parse(call.request.path)
|
|
63
|
+
# metrics.increment("grpc.request", tags: {service: service_name, method: method_name, status: call.status})
|
|
64
|
+
# end
|
|
65
|
+
def emit_completion(call, error = nil)
|
|
66
|
+
# Implementation-defined.
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Invoke a service handler.
|
|
70
|
+
#
|
|
71
|
+
# Override this method to establish request context or wrap handlers with
|
|
72
|
+
# application middleware. Overrides should call `super` to invoke the handler.
|
|
73
|
+
# Stream cleanup, trailer handling, and status assignment are performed by the
|
|
74
|
+
# dispatcher after this method returns or raises.
|
|
75
|
+
#
|
|
76
|
+
# This hook only surrounds service execution. Use {emit_completion} to observe
|
|
77
|
+
# every request, including routing/setup failures. A deadline expiring during
|
|
78
|
+
# service execution raises {DeadlineExceededError} through this hook before it is
|
|
79
|
+
# converted to `DEADLINE_EXCEEDED`.
|
|
53
80
|
#
|
|
54
81
|
# @parameter service [Async::GRPC::Service] The service containing the handler.
|
|
55
82
|
# @parameter handler_method [Symbol] The service method to invoke.
|
|
@@ -65,98 +92,183 @@ module Async
|
|
|
65
92
|
# end
|
|
66
93
|
# end
|
|
67
94
|
def invoke_service(service, handler_method, input, output, call)
|
|
95
|
+
service.send(handler_method, input, output, call)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Map an internal dispatcher error to the gRPC status and message reported to the client.
|
|
99
|
+
#
|
|
100
|
+
# @parameter error [Exception] The error which caused the call to fail.
|
|
101
|
+
# @returns [Tuple(Integer, String | Nil)] The gRPC status code and message.
|
|
102
|
+
private def status_for(error)
|
|
103
|
+
case error
|
|
104
|
+
when DeadlineExceededError
|
|
105
|
+
return Protocol::GRPC::Status::DEADLINE_EXCEEDED, "Deadline exceeded!"
|
|
106
|
+
when Protocol::GRPC::Error
|
|
107
|
+
return error.status_code, error.message
|
|
108
|
+
else
|
|
109
|
+
# A `nil` message defers to the message of the error itself:
|
|
110
|
+
return Protocol::GRPC::Status::INTERNAL, nil
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Invoke the service with deadline enforcement and completion reporting.
|
|
115
|
+
#
|
|
116
|
+
# @parameter parent [Async::Task] The task used to enforce the call deadline.
|
|
117
|
+
def dispatch_to_service(service, handler_method, input, output, call, parent: Async::Task.current)
|
|
118
|
+
error = nil
|
|
119
|
+
cancellation = nil
|
|
120
|
+
|
|
68
121
|
begin
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
122
|
+
if deadline = call.deadline
|
|
123
|
+
parent.with_timeout(deadline.remaining, DeadlineExceededError) do
|
|
124
|
+
invoke_service(service, handler_method, input, output, call)
|
|
125
|
+
end
|
|
126
|
+
else
|
|
127
|
+
invoke_service(service, handler_method, input, output, call)
|
|
128
|
+
end
|
|
129
|
+
rescue => error
|
|
130
|
+
prepare_trailers(output, call)
|
|
131
|
+
assign_status(call, error)
|
|
132
|
+
rescue Async::Stop => cancellation
|
|
133
|
+
prepare_trailers(output, call)
|
|
73
134
|
|
|
74
|
-
#
|
|
75
|
-
|
|
135
|
+
# Cancellation has a fixed status and does not require error mapping:
|
|
136
|
+
if headers = call.response&.headers
|
|
137
|
+
Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::CANCELLED)
|
|
138
|
+
end
|
|
76
139
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
140
|
+
raise
|
|
141
|
+
else
|
|
142
|
+
prepare_trailers(output, call)
|
|
143
|
+
|
|
144
|
+
# Add status (if not already set by handler):
|
|
145
|
+
if headers = call.response&.headers
|
|
146
|
+
unless headers.key?("grpc-status")
|
|
147
|
+
Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::OK)
|
|
148
|
+
end
|
|
80
149
|
end
|
|
150
|
+
ensure
|
|
151
|
+
finalize_streams(input, output, call, error, cancellation)
|
|
81
152
|
end
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Mark response headers as trailers when data was written.
|
|
156
|
+
def prepare_trailers(output, call)
|
|
157
|
+
if output.count > 0
|
|
158
|
+
call.response.headers.trailer!
|
|
89
159
|
end
|
|
90
160
|
end
|
|
91
161
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
162
|
+
# Close the input and output streams.
|
|
163
|
+
def close_streams(input, output, call)
|
|
164
|
+
# Close input stream:
|
|
165
|
+
input.close
|
|
166
|
+
ensure
|
|
167
|
+
# Close output stream:
|
|
168
|
+
unless output.closed?
|
|
169
|
+
output.close_write
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Close streams and report completion while preserving an active service error or cancellation.
|
|
174
|
+
private def finalize_streams(input, output, call, error, cancellation)
|
|
175
|
+
close_streams(input, output, call)
|
|
176
|
+
rescue => close_error
|
|
177
|
+
if error || cancellation
|
|
178
|
+
Console.warn(self, "Error during stream cleanup!", exception: close_error)
|
|
97
179
|
else
|
|
98
|
-
|
|
180
|
+
error = close_error
|
|
181
|
+
assign_status(call, error)
|
|
182
|
+
raise
|
|
99
183
|
end
|
|
100
|
-
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
184
|
+
ensure
|
|
185
|
+
# Cancellation raises `Async::Stop`, not `StandardError`:
|
|
186
|
+
report_completion(call, error)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Assign the status for the given error to the response.
|
|
190
|
+
def assign_status(call, error)
|
|
106
191
|
if headers = call.response&.headers
|
|
107
|
-
|
|
192
|
+
begin
|
|
193
|
+
status, message = status_for(error)
|
|
194
|
+
rescue => internal_error
|
|
195
|
+
Console.warn(self, "Error during status mapping!", exception: internal_error)
|
|
196
|
+
|
|
197
|
+
status = Protocol::GRPC::Status::INTERNAL
|
|
198
|
+
message = nil
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
Protocol::GRPC::Metadata.assign_status!(headers, status: status, message: message, error: error)
|
|
108
202
|
end
|
|
109
203
|
end
|
|
110
204
|
|
|
205
|
+
# Report completion without allowing instrumentation to affect the request.
|
|
206
|
+
private def report_completion(call, error = nil)
|
|
207
|
+
emit_completion(call, error)
|
|
208
|
+
rescue => error
|
|
209
|
+
Console.warn(self, "Error during emit completion!", exception: error)
|
|
210
|
+
end
|
|
211
|
+
|
|
111
212
|
# Dispatch the request to the appropriate service.
|
|
112
213
|
# @parameter request [Protocol::HTTP::Request] The HTTP request
|
|
113
214
|
# @returns [Protocol::HTTP::Response] The HTTP response
|
|
114
|
-
# @raises [Protocol::GRPC::Error] If service or method is not found
|
|
115
215
|
def dispatch(request)
|
|
116
|
-
# Parse service and method from path:
|
|
117
|
-
service_name, method_name = Protocol::GRPC::Methods.parse_path(request.path)
|
|
118
|
-
|
|
119
|
-
# Find service:
|
|
120
|
-
service = @services[service_name]
|
|
121
|
-
unless service
|
|
122
|
-
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service not found: #{service_name}")
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Verify service name matches:
|
|
126
|
-
unless service_name == service.service_name
|
|
127
|
-
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service name mismatch: expected #{service.service_name}, got #{service_name}")
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Get RPC descriptions from the service:
|
|
131
|
-
rpc_descriptor = service.rpc_descriptions[method_name]
|
|
132
|
-
unless rpc_descriptor
|
|
133
|
-
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Method not found: #{method_name}")
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
handler_method = rpc_descriptor.method
|
|
137
|
-
request_class = rpc_descriptor.request_class
|
|
138
|
-
response_class = rpc_descriptor.response_class
|
|
139
|
-
|
|
140
|
-
# Verify handler method exists:
|
|
141
|
-
unless service.respond_to?(handler_method, true)
|
|
142
|
-
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Handler method not implemented: #{handler_method}")
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Create protocol-level objects for gRPC handling:
|
|
146
|
-
encoding = request.headers["grpc-encoding"]
|
|
147
|
-
input = Protocol::GRPC::Body::ReadableBody.new(request.body, message_class: request_class, encoding: encoding)
|
|
148
|
-
output = Protocol::GRPC::Body::WritableBody.new(message_class: response_class, encoding: encoding)
|
|
149
|
-
|
|
150
216
|
# Create response headers:
|
|
217
|
+
encoding = request.headers["grpc-encoding"]
|
|
151
218
|
response_headers = Protocol::HTTP::Headers.new([], nil, policy: Protocol::GRPC::HEADER_POLICY)
|
|
152
219
|
response_headers["content-type"] = "application/grpc+proto"
|
|
153
220
|
response_headers["grpc-encoding"] = encoding if encoding
|
|
154
221
|
|
|
155
|
-
#
|
|
156
|
-
response = Protocol::HTTP::Response[200, response_headers,
|
|
222
|
+
# Assign the body after routing so setup failures are trailers-only:
|
|
223
|
+
response = Protocol::HTTP::Response[200, response_headers, nil]
|
|
224
|
+
call = nil
|
|
157
225
|
|
|
158
|
-
|
|
159
|
-
|
|
226
|
+
begin
|
|
227
|
+
# Create the call context:
|
|
228
|
+
call = Protocol::GRPC::Call.for(request, response)
|
|
229
|
+
|
|
230
|
+
# Extract the routing information from the request path:
|
|
231
|
+
service_name, method_name = Protocol::GRPC::Route.parse(request.path)
|
|
232
|
+
|
|
233
|
+
# Find service:
|
|
234
|
+
service = @services[service_name]
|
|
235
|
+
unless service
|
|
236
|
+
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service not found: #{service_name}")
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Verify service name matches:
|
|
240
|
+
unless service_name == service.service_name
|
|
241
|
+
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service name mismatch: expected #{service.service_name}, got #{service_name}")
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Get RPC descriptions from the service:
|
|
245
|
+
rpc_descriptor = service.rpc_descriptions[method_name]
|
|
246
|
+
unless rpc_descriptor
|
|
247
|
+
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Method not found: #{method_name}")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
handler_method = rpc_descriptor.method
|
|
251
|
+
request_class = rpc_descriptor.request_class
|
|
252
|
+
response_class = rpc_descriptor.response_class
|
|
253
|
+
|
|
254
|
+
# Verify handler method exists:
|
|
255
|
+
unless service.respond_to?(handler_method, true)
|
|
256
|
+
raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Handler method not implemented: #{handler_method}")
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Create protocol-level objects for gRPC handling:
|
|
260
|
+
input = Protocol::GRPC::Body::ReadableBody.new(request.body, message_class: request_class, encoding: encoding)
|
|
261
|
+
output = Protocol::GRPC::Body::WritableBody.new(message_class: response_class, encoding: encoding)
|
|
262
|
+
response.body = output
|
|
263
|
+
rescue => error
|
|
264
|
+
# Routing/setup failures. {Protocol::GRPC::Call.for} can fail before the call context exists:
|
|
265
|
+
call ||= Protocol::GRPC::Call.new(request, response)
|
|
266
|
+
|
|
267
|
+
assign_status(call, error)
|
|
268
|
+
report_completion(call, error)
|
|
269
|
+
|
|
270
|
+
return response
|
|
271
|
+
end
|
|
160
272
|
|
|
161
273
|
if rpc_descriptor.streaming?
|
|
162
274
|
Async do |task|
|
data/lib/async/grpc/error.rb
CHANGED
data/lib/async/grpc/version.rb
CHANGED
data/lib/async/grpc.rb
CHANGED
data/license.md
CHANGED
data/readme.md
CHANGED
|
@@ -24,6 +24,10 @@ Please see the [project documentation](https://socketry.github.io/async-grpc/) f
|
|
|
24
24
|
|
|
25
25
|
Please see the [project releases](https://socketry.github.io/async-grpc/releases/index) for all releases.
|
|
26
26
|
|
|
27
|
+
### v0.9.0
|
|
28
|
+
|
|
29
|
+
- Added `Async::GRPC::Dispatcher#emit_completion` for once-per-request completion instrumentation, including routing failures, cancellations, and the final gRPC status.
|
|
30
|
+
|
|
27
31
|
### v0.8.0
|
|
28
32
|
|
|
29
33
|
- Added `Async::GRPC::Error` as the base class for async gRPC errors, including `RemoteError` and the new `DeadlineExceededError` used to distinguish call deadline expiry from unrelated `Async::TimeoutError` failures.
|
|
@@ -70,26 +74,26 @@ Please see the [project releases](https://socketry.github.io/async-grpc/releases
|
|
|
70
74
|
|
|
71
75
|
We welcome contributions to this project.
|
|
72
76
|
|
|
73
|
-
1. Fork
|
|
77
|
+
1. Fork the repository.
|
|
74
78
|
2. Create your feature branch (`git checkout -b my-new-feature`).
|
|
75
|
-
3. Commit your changes (`git commit -am 'Add some feature'`).
|
|
79
|
+
3. Commit your changes (`git commit -am 'Add some feature.'`).
|
|
76
80
|
4. Push to the branch (`git push origin my-new-feature`).
|
|
77
|
-
5. Create new
|
|
81
|
+
5. Create a new pull request.
|
|
78
82
|
|
|
79
83
|
### Running Tests
|
|
80
84
|
|
|
81
85
|
To run the test suite:
|
|
82
86
|
|
|
83
|
-
```
|
|
84
|
-
bundle exec sus
|
|
87
|
+
``` bash
|
|
88
|
+
$ bundle exec sus
|
|
85
89
|
```
|
|
86
90
|
|
|
87
91
|
### Making Releases
|
|
88
92
|
|
|
89
93
|
To make a new release:
|
|
90
94
|
|
|
91
|
-
```
|
|
92
|
-
bundle exec bake gem:release:patch # or minor or major
|
|
95
|
+
``` bash
|
|
96
|
+
$ bundle exec bake gem:release:patch # or minor or major
|
|
93
97
|
```
|
|
94
98
|
|
|
95
99
|
### Developer Certificate of Origin
|
data/releases.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.9.0
|
|
4
|
+
|
|
5
|
+
- Added `Async::GRPC::Dispatcher#emit_completion` for once-per-request completion instrumentation, including routing failures, cancellations, and the final gRPC status.
|
|
6
|
+
|
|
3
7
|
## v0.8.0
|
|
4
8
|
|
|
5
9
|
- Added `Async::GRPC::Error` as the base class for async gRPC errors, including `RemoteError` and the new `DeadlineExceededError` used to distinguish call deadline expiry from unrelated `Async::TimeoutError` failures.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: async-grpc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
8
|
+
- Alex Watt
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain:
|
|
10
11
|
- |
|
|
@@ -72,14 +73,14 @@ dependencies:
|
|
|
72
73
|
requirements:
|
|
73
74
|
- - "~>"
|
|
74
75
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: 0.
|
|
76
|
+
version: 0.14.0
|
|
76
77
|
type: :runtime
|
|
77
78
|
prerelease: false
|
|
78
79
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
80
|
requirements:
|
|
80
81
|
- - "~>"
|
|
81
82
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: 0.
|
|
83
|
+
version: 0.14.0
|
|
83
84
|
- !ruby/object:Gem::Dependency
|
|
84
85
|
name: protocol-http
|
|
85
86
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -116,6 +117,8 @@ homepage: https://github.com/socketry/async-grpc
|
|
|
116
117
|
licenses:
|
|
117
118
|
- MIT
|
|
118
119
|
metadata:
|
|
120
|
+
bug_tracker_uri: https://github.com/socketry/async-grpc/issues
|
|
121
|
+
changelog_uri: https://github.com/socketry/async-grpc/blob/main/releases.md
|
|
119
122
|
documentation_uri: https://socketry.github.io/async-grpc/
|
|
120
123
|
source_code_uri: https://github.com/socketry/async-grpc.git
|
|
121
124
|
rdoc_options: []
|
metadata.gz.sig
CHANGED
|
Binary file
|