ruvoy 0.1.0-aarch64-linux → 0.2.0-aarch64-linux

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: 240c3296eb26fb27f1010ac292658b666b8d61e0e8e3b68468f07e9b4d0ca989
4
- data.tar.gz: 72249773a9def101672c13f389ac5e8935003c6a854dd3c8710fe0e9b580fc1c
3
+ metadata.gz: 570d5fa414a87e4f204964ad1dac4b45527948bb3fed5473559cc8698cb80689
4
+ data.tar.gz: 2d49878f9c9add538a8560d93988cc9c1fcbc838839f79f12e4a2f064823c739
5
5
  SHA512:
6
- metadata.gz: 944d5a8a115b5019806e824e40d21c187e2000a97cc56ed821d995bc3edbc707bf1bc8968ba1050f9b143f961678fa3381b5da3a9fdc5b316d4d16ce6d1b8deb
7
- data.tar.gz: 974e24d42aa2c3d9c4fac85d876bde1bf9b71919ee19b679d9daf51d4107d589b31224e0912d360e833c722305c26ea2da5a39deb08ab28416f43f59afd432ad
6
+ metadata.gz: 61fd6ebec19502a5160627811938fdcca59c4fdeceed34eaea0f9cc1869d85aa9a6280ea98ae1a984bd6b2733a083c643976fd10c210c93c4aee304ba5d8bd6c
7
+ data.tar.gz: 0ca15aefdd13ae67dd6c90ab67e4fd98f6d6fd3a0c72bc0da7f2983b32cd16a4c2a585daf5d0bddd7b8465695e0cdb5fcc67627da729aefc13e58a76719f5837
data/README.md CHANGED
@@ -19,17 +19,18 @@ speaks Rack, and owned Rust messages bridge the two.
19
19
  ## Install
20
20
 
21
21
  ```console
22
- $ gem install ruvoy
22
+ $ gem install ruvoy ruvoy-envoy
23
23
  ```
24
24
 
25
- The gem carries the dynamic module and the Envoy binary, so there is nothing
26
- else to install and no version to match by hand.
25
+ The `ruvoy` gem carries the dynamic module. Envoy comes from the `ruvoy-envoy`
26
+ gem or the machine: ruvoy looks for `RUVOY_ENVOY`, then `ruvoy-envoy`, then
27
+ `envoy` on `PATH`.
27
28
 
28
29
  | | |
29
30
  |---|---|
30
31
  | Ruby | 4.0.x. The module links against the Ruby it was built for, so the gem is published per ABI. |
31
32
  | Platform | Linux `x86_64` and `aarch64`, matching the platforms Envoy publishes binaries for. |
32
- | Envoy | Bundled (1.39.1, dynamic module ABI 0.1.0). Not installed separately. |
33
+ | Envoy | 1.39 (dynamic module ABI 0.1.0), from the `ruvoy-envoy` gem or the machine. |
33
34
 
34
35
  ## Quickstart
35
36
 
@@ -54,7 +55,8 @@ Usage: ruvoy [options] [config.ru]
54
55
 
55
56
  To run it under an Envoy you configure yourself, `--print-config` prints the
56
57
  listener Ruvoy would have used; the module is `ruvoy_fiber` and its filter
57
- config is the path to the rackup.
58
+ config is the path to the rackup, or a JSON object naming it (see
59
+ [Envoy context and upstream calls](#envoy-context-and-upstream-calls)).
58
60
 
59
61
  ## Status
60
62
 
@@ -83,6 +85,9 @@ threads flat. Interfaces may change.
83
85
  reaches Ruby.
84
86
  - **No upstream HTTP hop** — Rack calls do not require a loopback connection to
85
87
  a separate Ruby application server.
88
+ - **Envoy context and upstream calls** — optionally, the application sees the
89
+ route, connection, TLS and metadata Envoy has for the request, and calls
90
+ Envoy's clusters from inside it.
86
91
 
87
92
  ## With and without Ruvoy
88
93
 
@@ -130,7 +135,10 @@ stops the enumeration and closes the body.
130
135
  Request bodies stream as well: the application is called once the headers
131
136
  arrive and reads through `rack.input` as the body lands, so uploading and
132
137
  processing overlap. What the application has not read yet waits in Envoy, which
133
- stops reading from the client until there is room again. Raw socket hijacking
138
+ stops reading from the client until there is room again. What it has read is
139
+ kept, in memory up to 128 KiB and in a temporary file beyond that, so
140
+ `rack.input` can be rewound and read again, as frameworks written for earlier
141
+ Rack versions expect. Raw socket hijacking
134
142
  (`rack.hijack`) is not supported; the connection always belongs to Envoy.
135
143
 
136
144
  A response body that fails after its headers are already on the wire can only
@@ -138,6 +146,140 @@ stop: a dynamic module cannot reset a stream it has started answering, so the
138
146
  client receives a short body rather than an error. Applications that can fail
139
147
  partway through should send their own length or checksum.
140
148
 
149
+ ## Blocking calls
150
+
151
+ A fiber waits without holding the runtime thread only when the call it waits
152
+ on cooperates with Ruby's fiber scheduler: Ruby's own sockets and IO, `sleep`,
153
+ and the asynchronous APIs many drivers offer do. A C extension call that
154
+ blocks in the kernel instead holds the one thread every request shares, so
155
+ requests queue behind it: fifty-millisecond calls of that kind serve about
156
+ twenty requests a second however many clients wait.
157
+
158
+ `Ruvoy.offload` runs such a call on its own Ruby thread while the calling
159
+ fiber waits:
160
+
161
+ ```ruby
162
+ rows = Ruvoy.offload { connection.sync_exec(sql) }
163
+
164
+ # At most 16 in flight; callers beyond that wait their turn.
165
+ DATABASE = Ruvoy::Offload.new(limit: 16)
166
+ rows = DATABASE.call { connection.sync_exec(sql) }
167
+ ```
168
+
169
+ - It helps extensions that release the GVL while they block, which is how
170
+ blocking C calls are normally written. One that holds the GVL stalls every
171
+ thread, and nothing on the Ruby side helps.
172
+ - A call cannot be cut short. When the caller is interrupted, because its
173
+ client went away or a timeout fired, it still waits for the call to finish
174
+ before it unwinds, so a connection the call is using is not handed back
175
+ while in use.
176
+ - The block runs on another thread, so thread-local state, such as a
177
+ per-thread connection, belongs to that thread rather than to the caller.
178
+ - Prefer a driver's scheduler-aware API where it has one. `offload` costs a
179
+ thread per call and bounds nothing unless given a limit.
180
+
181
+ ## Rails
182
+
183
+ Rails applications run unchanged, with one setting every fiber server needs:
184
+
185
+ ```ruby
186
+ config.active_support.isolation_level = :fiber
187
+ ```
188
+
189
+ Active Record hands out connections per thread by default, and every
190
+ request's fiber runs on the one runtime thread, so without it concurrent
191
+ requests share a single connection. With it, each request in flight holds a
192
+ connection of its own.
193
+
194
+ - Action Cable is not supported: it needs `rack.hijack`.
195
+ - Requests reach Rails through Envoy, which adds `X-Forwarded-Proto` to what
196
+ it passes on.
197
+ - Envoy serves static files ahead of the application, so
198
+ `config.public_file_server.enabled = false` spares Rails a filesystem lookup
199
+ on every request: about a fifth more throughput on small responses here.
200
+ - `test/e2e/rails_app_test.rb` runs a production Rails 8.1 application on
201
+ Ruvoy and on a reference server, compares every answer, and checks
202
+ connections under concurrency and when a client leaves mid-transaction.
203
+
204
+ ## Envoy context and upstream calls
205
+
206
+ Two optional capabilities give the application more of Envoy than a Rack
207
+ request carries. Both stay off unless the filter configuration asks for them,
208
+ and both are available on the fiber runtime only. The configuration is then a
209
+ JSON object:
210
+
211
+ ```json
212
+ {
213
+ "rackup": "/srv/app/config.ru",
214
+ "context": { "metadata_namespaces": ["acme.tenant"] },
215
+ "upstream": { "clusters": ["users"], "timeout_ms": 15000, "max_response_bytes": 1048576 }
216
+ }
217
+ ```
218
+
219
+ ### `ruvoy.context`
220
+
221
+ Envoy's view of the request, copied on the worker when the headers arrive.
222
+ Ruby values are only built when the application reads them.
223
+
224
+ ```ruby
225
+ context = env["ruvoy.context"]
226
+ context.route_name # => "api", or nil for an unnamed route
227
+ context.connection # => {"id"=>7, "source_address"=>"10.0.0.7", "source_port"=>51234, ...}
228
+ context.tls # => nil on plaintext, else {"version"=>"TLSv1.3", "server_name"=>..., "peer_certificate"=>...}
229
+ context.dynamic_metadata # => {"acme.tenant"=>{"id"=>"t-42"}}, from filters ahead of Ruvoy
230
+ context.route_metadata # => the same namespaces, from the matched route
231
+ context.to_h # => all of the above
232
+ ```
233
+
234
+ What Envoy's module interface can report sets some limits:
235
+
236
+ - A route has a name only if the route configuration gives it one. A terminal
237
+ filter still needs a route entry to be named: give it
238
+ `non_forwarding_action: {}`.
239
+ - `server_name` is the SNI the client sent, which Envoy records only when the
240
+ listener has the TLS inspector listener filter.
241
+ - `peer_certificate` means the client presented a certificate. It identifies
242
+ the client only on a listener that requires client certificates signed by a
243
+ trusted CA.
244
+ - Only the first URI and the first DNS subject alternative name are reported.
245
+ - Metadata values are strings, numbers (always Float), booleans and lists of
246
+ those. Nested structures are left out.
247
+
248
+ ### `ruvoy.upstream`
249
+
250
+ Calls a cluster Envoy was configured with and returns a Rack-style triple:
251
+
252
+ ```ruby
253
+ status, headers, body = env["ruvoy.upstream"].call(
254
+ "users", "GET", "/users/42",
255
+ headers: { "accept" => "application/json" }, timeout: 2
256
+ )
257
+ ```
258
+
259
+ The request's worker sends the call through the cluster, so the cluster's
260
+ connection pool, TLS settings and circuit breakers apply, and headers such as
261
+ `x-envoy-retry-on` ask Envoy to retry. The calling fiber waits without holding
262
+ the runtime thread, so other requests keep being served and calls made from
263
+ one request overlap. A repeated response header arrives as an Array.
264
+
265
+ - Only the clusters in `upstream.clusters` can be called; any other raises
266
+ `ArgumentError`.
267
+ - Answers Envoy produces while the call is under way arrive as ordinary
268
+ responses: 504 when it timed out, 503 when the upstream could not be reached
269
+ or hung up.
270
+ - `Ruvoy::UpstreamError` is raised when the call could not be sent: the cluster
271
+ does not exist, or turned the call away at once because it has no healthy
272
+ host or a circuit breaker is open. It is also raised when the request finished
273
+ before the call did.
274
+ - A response body over `max_response_bytes`, or over Envoy's own buffer limit,
275
+ raises `Ruvoy::UpstreamResponseTooLarge`, a subclass of `Ruvoy::UpstreamError`,
276
+ so a caller can tell asking for less apart from trying again later.
277
+ - Responses are buffered whole, so `max_response_bytes` bounds what one call
278
+ can hold in memory.
279
+
280
+ [`examples/extensions`](examples/extensions) runs both against a stand-in
281
+ service, including two calls from one request overlapping.
282
+
141
283
  ## Scaling
142
284
 
143
285
  Each Ruvoy process runs exactly one CRuby VM, so Ruby work in one process is
@@ -258,7 +400,7 @@ Four honest qualifications:
258
400
  Not measured: CPU-bound Rack, real database or HTTP-client drivers, and
259
401
  multi-process deployment.
260
402
 
261
- Measurements were produced by `scripts/run-benchmark.sh`, which enforces the
403
+ Measurements were produced by `bench/run.rb`, which enforces the
262
404
  protocol above — remote load generation, rotation, warm-up, per-round
263
405
  validation, and the pre-registered judgement — and writes raw oha output,
264
406
  CPU and RSS samples, and the generated summary for every run.
Binary file
data/lib/ruvoy/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruvoy
4
- # Kept in step with the Cargo workspace version by scripts/test-cli.sh: the
4
+ # Kept in step with the Cargo workspace version by test/e2e/cli_test.rb: the
5
5
  # gem and the module it carries are the same release.
6
- VERSION = "0.1.0"
6
+ VERSION = "0.2.0"
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruvoy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Zixin Zhou