ruvoy 0.1.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 +7 -0
- data/LICENSE +202 -0
- data/README.md +268 -0
- data/exe/ruvoy +6 -0
- data/lib/ruvoy/4.0.0/libruvoy_fiber.so +0 -0
- data/lib/ruvoy/cli.rb +112 -0
- data/lib/ruvoy/envoy_config.rb +80 -0
- data/lib/ruvoy/paths.rb +89 -0
- data/lib/ruvoy/version.rb +7 -0
- metadata +87 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 240c3296eb26fb27f1010ac292658b666b8d61e0e8e3b68468f07e9b4d0ca989
|
|
4
|
+
data.tar.gz: 72249773a9def101672c13f389ac5e8935003c6a854dd3c8710fe0e9b580fc1c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 944d5a8a115b5019806e824e40d21c187e2000a97cc56ed821d995bc3edbc707bf1bc8968ba1050f9b143f961678fa3381b5da3a9fdc5b316d4d16ce6d1b8deb
|
|
7
|
+
data.tar.gz: 974e24d42aa2c3d9c4fac85d876bde1bf9b71919ee19b679d9daf51d4107d589b31224e0912d360e833c722305c26ea2da5a39deb08ab28416f43f59afd432ad
|
data/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
data/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/ruvoy-logo.png" alt="Ruvoy" width="360">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center"><strong>Ruby, carried by Envoy.</strong></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img alt="Rust 2024" src="https://img.shields.io/badge/Rust-2024-000000?logo=rust&logoColor=white">
|
|
9
|
+
<img alt="Ruby 4.0.5" src="https://img.shields.io/badge/Ruby-4.0.5-CC342D?logo=ruby&logoColor=white">
|
|
10
|
+
<img alt="Envoy 1.39.1" src="https://img.shields.io/badge/Envoy-1.39.1-AC6199?logo=envoyproxy&logoColor=white">
|
|
11
|
+
<a href="LICENSE"><img alt="Apache License 2.0" src="https://img.shields.io/badge/License-Apache--2.0-blue.svg"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">English | <a href="README.zh-CN.md">简体中文</a> | <a href="README.ja.md">日本語</a></p>
|
|
15
|
+
|
|
16
|
+
Ruvoy is a Ruby application runtime inside Envoy. Envoy speaks HTTP, Ruby
|
|
17
|
+
speaks Rack, and owned Rust messages bridge the two.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```console
|
|
22
|
+
$ gem install ruvoy
|
|
23
|
+
```
|
|
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.
|
|
27
|
+
|
|
28
|
+
| | |
|
|
29
|
+
|---|---|
|
|
30
|
+
| Ruby | 4.0.x. The module links against the Ruby it was built for, so the gem is published per ABI. |
|
|
31
|
+
| 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
|
+
|
|
34
|
+
## Quickstart
|
|
35
|
+
|
|
36
|
+
```console
|
|
37
|
+
$ ruvoy examples/hello/config.ru
|
|
38
|
+
$ curl localhost:8080/
|
|
39
|
+
hello from ruby 4.0.5
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`ruvoy` takes a rackup and serves it. It generates the Envoy configuration and
|
|
43
|
+
replaces itself with Envoy, so signals and exit status belong to the proxy and
|
|
44
|
+
a container needs no process supervisor.
|
|
45
|
+
|
|
46
|
+
```console
|
|
47
|
+
$ ruvoy --help
|
|
48
|
+
Usage: ruvoy [options] [config.ru]
|
|
49
|
+
-p, --port PORT Listen on PORT (default 8080)
|
|
50
|
+
-a, --address ADDRESS Bind to ADDRESS (default 127.0.0.1)
|
|
51
|
+
--admin-port PORT Expose Envoy's admin interface on PORT
|
|
52
|
+
--print-config Print the generated Envoy configuration and exit
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
To run it under an Envoy you configure yourself, `--print-config` prints the
|
|
56
|
+
listener Ruvoy would have used; the module is `ruvoy_fiber` and its filter
|
|
57
|
+
config is the path to the rackup.
|
|
58
|
+
|
|
59
|
+
## Status
|
|
60
|
+
|
|
61
|
+
The data path is implemented and covered by suites that run against a real
|
|
62
|
+
Envoy in CI. Configuration reload has been driven through a running Envoy, and
|
|
63
|
+
a one-hour soak of 1.8 million requests left the heap, file descriptors and
|
|
64
|
+
threads flat. Interfaces may change.
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
- **Envoy-native HTTP** — HTTP/1.1, HTTP/2, connections, timeouts, and
|
|
69
|
+
downstream lifecycle remain under Envoy's control.
|
|
70
|
+
- **Rack applications** — load an ordinary `config.ru` and receive a Rack
|
|
71
|
+
environment built from the Envoy request.
|
|
72
|
+
- **Fiber concurrency** — scheduler-aware Ruby I/O overlaps on a dedicated
|
|
73
|
+
CRuby owner thread without entering Ruby from Envoy workers.
|
|
74
|
+
- **Owned-data boundary** — only Rust-owned requests and responses cross
|
|
75
|
+
threads; Ruby objects never do.
|
|
76
|
+
- **Streaming responses** — response bodies are delivered chunk by chunk as the
|
|
77
|
+
application produces them, with downstream backpressure applied to the Ruby
|
|
78
|
+
producer instead of buffering.
|
|
79
|
+
- **Streaming requests** — the application is called as soon as the headers
|
|
80
|
+
arrive and reads the body as it lands, with a client the application cannot
|
|
81
|
+
keep up with slowed down instead of buffered.
|
|
82
|
+
- **Bounded admission** — a request-count budget rejects excess work before it
|
|
83
|
+
reaches Ruby.
|
|
84
|
+
- **No upstream HTTP hop** — Rack calls do not require a loopback connection to
|
|
85
|
+
a separate Ruby application server.
|
|
86
|
+
|
|
87
|
+
## With and without Ruvoy
|
|
88
|
+
|
|
89
|
+
| | Without Ruvoy | With Ruvoy |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| Request path | Envoy → upstream HTTP → Ruby server | Envoy → owned Rust message → Ruby |
|
|
92
|
+
| Ruby host | Separate server process or processes | Dedicated CRuby owner thread inside Envoy |
|
|
93
|
+
| Protocol handoff | Request is serialized onto another HTTP connection | Request crosses the worker boundary as owned data |
|
|
94
|
+
| I/O concurrency | Server threads or workers | Async fibers on the Ruby owner thread |
|
|
95
|
+
| Failure boundary | Envoy and Ruby are separate processes | Envoy and Ruby share one process |
|
|
96
|
+
| CPU-bound Ruby | Multiple Ruby workers can use multiple cores | One CRuby VM remains limited by the GVL |
|
|
97
|
+
|
|
98
|
+
Ruvoy is designed for scheduler-aware I/O and short CPU-light Rack work. It
|
|
99
|
+
does not make CPU-bound Ruby execute in parallel.
|
|
100
|
+
|
|
101
|
+
## Architecture
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
client
|
|
105
|
+
→ Envoy worker
|
|
106
|
+
→ owned Rust request
|
|
107
|
+
→ CRuby owner thread
|
|
108
|
+
→ Async Fiber
|
|
109
|
+
→ owned Rust response
|
|
110
|
+
→ original Envoy worker
|
|
111
|
+
→ client
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Envoy workers never acquire a Ruby handle or enter the VM. The Ruby owner
|
|
115
|
+
thread runs the Rack application, while the result is committed back onto the
|
|
116
|
+
worker that owns the downstream stream.
|
|
117
|
+
|
|
118
|
+
## Current scope
|
|
119
|
+
|
|
120
|
+
Ruvoy constructs the Rack environment, provides binary `rack.input`, preserves
|
|
121
|
+
repeated response headers, consumes enumerable response bodies, and calls
|
|
122
|
+
`close` when the body supports it.
|
|
123
|
+
|
|
124
|
+
Response bodies stream incrementally: headers are sent once and each chunk is
|
|
125
|
+
forwarded as the application yields it, so the first bytes reach the client
|
|
126
|
+
before the body is complete. A slow client applies backpressure to the Ruby
|
|
127
|
+
producer rather than accumulating in memory, and a client that disconnects
|
|
128
|
+
stops the enumeration and closes the body.
|
|
129
|
+
|
|
130
|
+
Request bodies stream as well: the application is called once the headers
|
|
131
|
+
arrive and reads through `rack.input` as the body lands, so uploading and
|
|
132
|
+
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
|
|
134
|
+
(`rack.hijack`) is not supported; the connection always belongs to Envoy.
|
|
135
|
+
|
|
136
|
+
A response body that fails after its headers are already on the wire can only
|
|
137
|
+
stop: a dynamic module cannot reset a stream it has started answering, so the
|
|
138
|
+
client receives a short body rather than an error. Applications that can fail
|
|
139
|
+
partway through should send their own length or checksum.
|
|
140
|
+
|
|
141
|
+
## Scaling
|
|
142
|
+
|
|
143
|
+
Each Ruvoy process runs exactly one CRuby VM, so Ruby work in one process is
|
|
144
|
+
limited to one core by the GVL. Scaling out means more processes, not more
|
|
145
|
+
threads: run one Ruvoy per replica and scale horizontally as with any
|
|
146
|
+
single-VM Ruby server.
|
|
147
|
+
|
|
148
|
+
## Benchmarks
|
|
149
|
+
|
|
150
|
+
### Environment
|
|
151
|
+
|
|
152
|
+
| Component | Configuration |
|
|
153
|
+
|---|---|
|
|
154
|
+
| Machine | 4 vCPU, 32 GiB memory, x86_64 |
|
|
155
|
+
| System | Linux |
|
|
156
|
+
| Envoy | `1.39.0`, concurrency `1` |
|
|
157
|
+
| Ruby | `4.0.5` |
|
|
158
|
+
| Rust | `1.97.1` |
|
|
159
|
+
| Rack | `3.2.6` |
|
|
160
|
+
| Load generator | oha `1.15.0` |
|
|
161
|
+
|
|
162
|
+
### Mixed stress
|
|
163
|
+
|
|
164
|
+
This run tested admission control, recovery, client cancellation, and resource
|
|
165
|
+
stability. It used three complete cycles:
|
|
166
|
+
|
|
167
|
+
- steady traffic: 45,000 HTTP/2 no-op requests, 6,000 HTTP/1.1 requests with a
|
|
168
|
+
200 ms wait, and 1,200 HTTP/1.1 requests with a 256 KiB body per cycle;
|
|
169
|
+
- request-count overload: 15 seconds with at most 256 admitted requests;
|
|
170
|
+
- aggregate-body overload: 15 seconds with a 64 MiB admitted-body budget;
|
|
171
|
+
- client cancellation: 128 deliberately cancelled requests;
|
|
172
|
+
- recovery: 20,000 no-op and 2,000 waiting requests per cycle.
|
|
173
|
+
|
|
174
|
+
| Result | Value |
|
|
175
|
+
|---|---:|
|
|
176
|
+
| Total requests | `2,503,763` |
|
|
177
|
+
| HTTP 200 | `251,400` |
|
|
178
|
+
| Expected overload HTTP 503 | `2,252,363` |
|
|
179
|
+
| Transport errors | `0` |
|
|
180
|
+
| Steady and recovery failures | `0` |
|
|
181
|
+
| Control-listener maximum latency | `35.615 ms` |
|
|
182
|
+
| File descriptors after every cycle | `59` |
|
|
183
|
+
| Ruby live-heap change, recovered cycle 1 → 3 | `+2 slots` |
|
|
184
|
+
| Peak RSS during body overload | `489,332 KiB` |
|
|
185
|
+
|
|
186
|
+
Every steady and recovery request returned HTTP 200. All HTTP 503 responses
|
|
187
|
+
were generated during deliberate overload and are reported as rejected work,
|
|
188
|
+
not successful throughput.
|
|
189
|
+
|
|
190
|
+
The load generator ran on the same machine, and the server did not saturate
|
|
191
|
+
the host CPU. This result therefore supports resilience and bounded-overload
|
|
192
|
+
claims only; it is not a maximum-throughput measurement.
|
|
193
|
+
|
|
194
|
+
### Server comparison
|
|
195
|
+
|
|
196
|
+
A fixed-commit comparison of Ruvoy, direct Falcon `0.55.6`, direct Puma
|
|
197
|
+
`7.2.0`, and Envoy → Puma, all serving the same Rack fixture:
|
|
198
|
+
|
|
199
|
+
- oha ran on a separate 2 vCPU host over a local network (RTT ≈ 0.03 ms), so
|
|
200
|
+
the load generator never competed with the servers for CPU.
|
|
201
|
+
- Each measurement is 10 s warm-up plus 30 s under load; 7 rounds per cell
|
|
202
|
+
with the architecture order rotated every round.
|
|
203
|
+
- A round only counts if every request returned HTTP 200 with zero transport
|
|
204
|
+
errors. Reported values are medians with relative standard deviation.
|
|
205
|
+
- Every architecture runs one Ruby execution context: Puma in single mode
|
|
206
|
+
with 100 threads, one Falcon instance, one Ruvoy owner thread, and Envoy
|
|
207
|
+
`concurrency 1`.
|
|
208
|
+
- Judgement was fixed before the run: an advantage is claimed only when both
|
|
209
|
+
throughput and p99 improve by more than the larger of 5% and the observed
|
|
210
|
+
round-to-round variance.
|
|
211
|
+
|
|
212
|
+
No-op Rack application, HTTP/1.1, 100 connections, plaintext:
|
|
213
|
+
|
|
214
|
+
| Server | RPS median (RSD) | p50 ms | p99 ms | CPU | RSS MiB |
|
|
215
|
+
|---|---:|---:|---:|---:|---:|
|
|
216
|
+
| Ruvoy | `31,270` (1.9%) | `3.06` | `5.01` | 189% | 94 |
|
|
217
|
+
| Falcon | `12,716` (0.5%) | `7.67` | `9.63` | 99% | 83 |
|
|
218
|
+
| Puma | `10,410` (5.5%) | `8.84` | `20.96` | 87% | 127 |
|
|
219
|
+
| Envoy → Puma | `9,754` (4.0%) | `9.92` | `17.63` | 159% | 185 |
|
|
220
|
+
|
|
221
|
+
The same workload with TLS 1.3, terminated by Envoy for Ruvoy and
|
|
222
|
+
Envoy → Puma and in-process by Falcon and Puma:
|
|
223
|
+
|
|
224
|
+
| Server | RPS median (RSD) | p50 ms | p99 ms | CPU | RSS MiB |
|
|
225
|
+
|---|---:|---:|---:|---:|---:|
|
|
226
|
+
| Ruvoy | `32,065` (2.9%) | `2.98` | `4.85` | 192% | 95 |
|
|
227
|
+
| Falcon | `11,401` (3.0%) | `8.73` | `10.61` | 99% | 91 |
|
|
228
|
+
| Envoy → Puma | `9,674` (4.4%) | `9.97` | `17.44` | 162% | 191 |
|
|
229
|
+
| Puma | `8,728` (6.7%) | `9.87` | `25.52` | 87% | 148 |
|
|
230
|
+
|
|
231
|
+
Falcon is the comparison that isolates the architecture: both are
|
|
232
|
+
fiber-per-request, so the difference is carrying requests as owned data
|
|
233
|
+
inside one process instead of over a loopback HTTP hop. Against Falcon,
|
|
234
|
+
Ruvoy holds `+146%` throughput with `−48%` p99 in plaintext and `+181%`
|
|
235
|
+
with `−54%` under TLS — both past the pre-registered noise threshold.
|
|
236
|
+
Against Envoy → Puma the margin is `+221%` and `+231%`.
|
|
237
|
+
|
|
238
|
+
Four honest qualifications:
|
|
239
|
+
|
|
240
|
+
- The no-op scenario measures per-request framework overhead, so these are
|
|
241
|
+
ceiling numbers. As real application time grows, the relative margin
|
|
242
|
+
shrinks: with a 200 ms simulated wait at 100 connections, all four servers
|
|
243
|
+
sit at the same ~495 RPS concurrency limit.
|
|
244
|
+
- Ruvoy's CPU column includes the Envoy worker handling HTTP in the same
|
|
245
|
+
process (~1.9 cores versus Falcon's ~1.0). Per core the margin is roughly
|
|
246
|
+
`+29%`; the rest comes from pairing the Ruby thread with a proxy-grade
|
|
247
|
+
HTTP front end.
|
|
248
|
+
- Every number here comes from an application that yields while it waits. A
|
|
249
|
+
driver written as a C extension releases the interpreter lock but not the
|
|
250
|
+
fiber, so a single such call stalls every other request on the thread. The
|
|
251
|
+
`wait50` and `block50` scenarios measure that difference directly; they are
|
|
252
|
+
not part of the campaign above.
|
|
253
|
+
- A 1 MiB-response scenario was also run and produced no ranking: above
|
|
254
|
+
~1,000 such responses per second all four architectures converged on the
|
|
255
|
+
network path's throughput limit with 59–96% variance, so that scenario
|
|
256
|
+
measures the link, not the servers.
|
|
257
|
+
|
|
258
|
+
Not measured: CPU-bound Rack, real database or HTTP-client drivers, and
|
|
259
|
+
multi-process deployment.
|
|
260
|
+
|
|
261
|
+
Measurements were produced by `scripts/run-benchmark.sh`, which enforces the
|
|
262
|
+
protocol above — remote load generation, rotation, warm-up, per-round
|
|
263
|
+
validation, and the pre-registered judgement — and writes raw oha output,
|
|
264
|
+
CPU and RSS samples, and the generated summary for every run.
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
Apache License 2.0. See [LICENSE](LICENSE).
|
data/exe/ruvoy
ADDED
|
Binary file
|
data/lib/ruvoy/cli.rb
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require "ruvoy/envoy_config"
|
|
6
|
+
require "ruvoy/paths"
|
|
7
|
+
require "ruvoy/version"
|
|
8
|
+
|
|
9
|
+
module Ruvoy
|
|
10
|
+
# Turns `ruvoy config.ru` into a running Envoy that serves it.
|
|
11
|
+
class CLI
|
|
12
|
+
DEFAULT_RACKUP = "config.ru"
|
|
13
|
+
DEFAULT_ADDRESS = "127.0.0.1"
|
|
14
|
+
DEFAULT_PORT = 8080
|
|
15
|
+
|
|
16
|
+
Options = Struct.new(:rackup, :address, :port, :admin_port, :print_config, keyword_init: true)
|
|
17
|
+
|
|
18
|
+
def initialize(argv, output: $stdout, error: $stderr)
|
|
19
|
+
@argv = argv
|
|
20
|
+
@output = output
|
|
21
|
+
@error = error
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run
|
|
25
|
+
options = catch(:finished) { parse }
|
|
26
|
+
return 0 unless options.is_a?(Options)
|
|
27
|
+
|
|
28
|
+
rackup = File.expand_path(options.rackup)
|
|
29
|
+
unless File.file?(rackup)
|
|
30
|
+
@error.puts("ruvoy: no such rackup: #{options.rackup}")
|
|
31
|
+
return 1
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
yaml = EnvoyConfig.to_yaml(
|
|
35
|
+
rackup: rackup,
|
|
36
|
+
address: options.address,
|
|
37
|
+
port: options.port,
|
|
38
|
+
admin_port: options.admin_port
|
|
39
|
+
)
|
|
40
|
+
if options.print_config
|
|
41
|
+
@output.puts(yaml)
|
|
42
|
+
return 0
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
launch(yaml)
|
|
46
|
+
rescue Paths::NotFound => error
|
|
47
|
+
@error.puts("ruvoy: #{error.message}")
|
|
48
|
+
1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def launch(yaml)
|
|
54
|
+
config = Tempfile.new([ "ruvoy", ".yaml" ])
|
|
55
|
+
config.write(yaml)
|
|
56
|
+
config.close
|
|
57
|
+
# Replacing this process rather than supervising a child keeps signals,
|
|
58
|
+
# exit status and container lifecycle in Envoy's hands.
|
|
59
|
+
Kernel.exec(environment, Paths.envoy_binary, "-c", config.path)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def environment
|
|
63
|
+
{
|
|
64
|
+
"ENVOY_DYNAMIC_MODULES_SEARCH_PATH" => Paths.module_directory,
|
|
65
|
+
# Envoy dlopens the module with no Ruby process to inherit a search
|
|
66
|
+
# path from, so libruby has to be findable here.
|
|
67
|
+
"LD_LIBRARY_PATH" => [ Paths.ruby_library_directory, ENV["LD_LIBRARY_PATH"] ]
|
|
68
|
+
.compact.reject(&:empty?).join(File::PATH_SEPARATOR)
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def parse
|
|
73
|
+
options = Options.new(
|
|
74
|
+
rackup: DEFAULT_RACKUP,
|
|
75
|
+
address: DEFAULT_ADDRESS,
|
|
76
|
+
port: DEFAULT_PORT,
|
|
77
|
+
admin_port: nil,
|
|
78
|
+
print_config: false
|
|
79
|
+
)
|
|
80
|
+
parser = build_parser(options)
|
|
81
|
+
parser.parse!(@argv)
|
|
82
|
+
options.rackup = @argv.shift unless @argv.empty?
|
|
83
|
+
options
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def build_parser(options)
|
|
87
|
+
OptionParser.new do |parser|
|
|
88
|
+
parser.banner = "Usage: ruvoy [options] [#{DEFAULT_RACKUP}]"
|
|
89
|
+
parser.on("-p", "--port PORT", Integer, "Listen on PORT (default #{DEFAULT_PORT})") do |port|
|
|
90
|
+
options.port = port
|
|
91
|
+
end
|
|
92
|
+
parser.on("-a", "--address ADDRESS", "Bind to ADDRESS (default #{DEFAULT_ADDRESS})") do |address|
|
|
93
|
+
options.address = address
|
|
94
|
+
end
|
|
95
|
+
parser.on("--admin-port PORT", Integer, "Expose Envoy's admin interface on PORT") do |port|
|
|
96
|
+
options.admin_port = port
|
|
97
|
+
end
|
|
98
|
+
parser.on("--print-config", "Print the generated Envoy configuration and exit") do
|
|
99
|
+
options.print_config = true
|
|
100
|
+
end
|
|
101
|
+
parser.on("-v", "--version", "Print the version and exit") do
|
|
102
|
+
@output.puts(VERSION)
|
|
103
|
+
throw :finished
|
|
104
|
+
end
|
|
105
|
+
parser.on("-h", "--help", "Print this message and exit") do
|
|
106
|
+
@output.puts(parser)
|
|
107
|
+
throw :finished
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Ruvoy
|
|
6
|
+
# Builds the Envoy bootstrap that serves one Rack application.
|
|
7
|
+
#
|
|
8
|
+
# The timeouts are not defaults worth inheriting: a Ruby fiber that never
|
|
9
|
+
# finishes writes nothing to the stream, so without an explicit bound the
|
|
10
|
+
# request would hang for Envoy's five-minute default.
|
|
11
|
+
module EnvoyConfig
|
|
12
|
+
STREAM_IDLE_TIMEOUT = "15s"
|
|
13
|
+
REQUEST_TIMEOUT = "30s"
|
|
14
|
+
CONNECTION_BUFFER_LIMIT_BYTES = 4_194_304
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def build(rackup:, address:, port:, admin_port: nil)
|
|
19
|
+
config = { "static_resources" => { "listeners" => [ listener(rackup, address, port) ] } }
|
|
20
|
+
config["admin"] = admin(address, admin_port) if admin_port
|
|
21
|
+
config
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_yaml(**options)
|
|
25
|
+
YAML.dump(build(**options))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def listener(rackup, address, port)
|
|
29
|
+
{
|
|
30
|
+
"name" => "ruvoy",
|
|
31
|
+
"per_connection_buffer_limit_bytes" => CONNECTION_BUFFER_LIMIT_BYTES,
|
|
32
|
+
"address" => { "socket_address" => { "address" => address, "port_value" => port } },
|
|
33
|
+
"filter_chains" => [ { "filters" => [ connection_manager(rackup) ] } ]
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def connection_manager(rackup)
|
|
38
|
+
{
|
|
39
|
+
"name" => "envoy.filters.network.http_connection_manager",
|
|
40
|
+
"typed_config" => {
|
|
41
|
+
"@type" => "type.googleapis.com/envoy.extensions.filters.network." \
|
|
42
|
+
"http_connection_manager.v3.HttpConnectionManager",
|
|
43
|
+
"stat_prefix" => "ruvoy",
|
|
44
|
+
"codec_type" => "AUTO",
|
|
45
|
+
"stream_idle_timeout" => STREAM_IDLE_TIMEOUT,
|
|
46
|
+
"request_timeout" => REQUEST_TIMEOUT,
|
|
47
|
+
"route_config" => {
|
|
48
|
+
"name" => "local_route",
|
|
49
|
+
"virtual_hosts" => [ { "name" => "local_service", "domains" => [ "*" ] } ]
|
|
50
|
+
},
|
|
51
|
+
"http_filters" => [ rack_filter(rackup) ]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Terminal: the Rack application answers the request, so no router follows.
|
|
57
|
+
def rack_filter(rackup)
|
|
58
|
+
{
|
|
59
|
+
"name" => "envoy.extensions.filters.http.dynamic_modules",
|
|
60
|
+
"typed_config" => {
|
|
61
|
+
"@type" => "type.googleapis.com/envoy.extensions.filters.http." \
|
|
62
|
+
"dynamic_modules.v3.DynamicModuleFilter",
|
|
63
|
+
"dynamic_module_config" => { "name" => "ruvoy_fiber", "do_not_close" => true },
|
|
64
|
+
"filter_name" => "fiber_rack",
|
|
65
|
+
"terminal_filter" => true,
|
|
66
|
+
"filter_config" => {
|
|
67
|
+
"@type" => "type.googleapis.com/google.protobuf.StringValue",
|
|
68
|
+
"value" => rackup
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def admin(address, port)
|
|
75
|
+
{
|
|
76
|
+
"address" => { "socket_address" => { "address" => address, "port_value" => port } }
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/ruvoy/paths.rb
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module Ruvoy
|
|
6
|
+
# Locates the three things a run needs: the Ruby the module was built
|
|
7
|
+
# against, the module itself, and Envoy.
|
|
8
|
+
#
|
|
9
|
+
# An installed gem carries the module and Envoy; a source checkout has
|
|
10
|
+
# neither in those places, so each lookup falls back to where a build leaves
|
|
11
|
+
# them. Both paths matter — the checkout is how this project tests itself.
|
|
12
|
+
module Paths
|
|
13
|
+
class NotFound < StandardError; end
|
|
14
|
+
|
|
15
|
+
ROOT = File.expand_path("../..", __dir__)
|
|
16
|
+
MODULE_BASENAME = "libruvoy_fiber.so"
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# Envoy resolves a module by name against this directory.
|
|
20
|
+
def module_directory
|
|
21
|
+
from_env = ENV["RUVOY_MODULE_DIR"]
|
|
22
|
+
return require_directory(from_env, "RUVOY_MODULE_DIR") if from_env
|
|
23
|
+
|
|
24
|
+
candidates = [ packaged_module_directory, File.join(ROOT, "build", "modules") ]
|
|
25
|
+
found = candidates.find { |directory| File.exist?(File.join(directory, MODULE_BASENAME)) }
|
|
26
|
+
return found if found
|
|
27
|
+
|
|
28
|
+
raise NotFound, "no #{MODULE_BASENAME} in: #{candidates.join(", ")}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def envoy_binary
|
|
32
|
+
from_env = ENV["RUVOY_ENVOY"]
|
|
33
|
+
return require_executable(from_env, "RUVOY_ENVOY") if from_env
|
|
34
|
+
|
|
35
|
+
packaged = packaged_envoy
|
|
36
|
+
return packaged if packaged
|
|
37
|
+
|
|
38
|
+
on_path = which("envoy")
|
|
39
|
+
return on_path if on_path
|
|
40
|
+
|
|
41
|
+
raise NotFound,
|
|
42
|
+
"no Envoy found: install the ruvoy-envoy gem, put one on PATH, " \
|
|
43
|
+
"or point RUVOY_ENVOY at it"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Present only where the ruvoy-envoy gem is installed, which is optional:
|
|
47
|
+
# an Envoy the machine already has serves just as well.
|
|
48
|
+
def packaged_envoy
|
|
49
|
+
require "ruvoy/envoy"
|
|
50
|
+
Envoy::BINARY if File.executable?(Envoy::BINARY)
|
|
51
|
+
rescue LoadError
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The module links against libruby, and Envoy loads it with no Ruby
|
|
56
|
+
# process to inherit a search path from.
|
|
57
|
+
def ruby_library_directory
|
|
58
|
+
RbConfig::CONFIG.fetch("libdir")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Precompiled gems carry one module per Ruby ABI, since the module links
|
|
62
|
+
# against the libruby of the Ruby it was built for.
|
|
63
|
+
def packaged_module_directory
|
|
64
|
+
File.join(ROOT, "lib", "ruvoy", RbConfig::CONFIG.fetch("ruby_version"))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def require_directory(path, source)
|
|
70
|
+
return path if File.exist?(File.join(path, MODULE_BASENAME))
|
|
71
|
+
|
|
72
|
+
raise NotFound, "#{source} is #{path}, which holds no #{MODULE_BASENAME}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def require_executable(path, source)
|
|
76
|
+
return path if File.executable?(path)
|
|
77
|
+
|
|
78
|
+
raise NotFound, "#{source} is #{path}, which is not executable"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def which(command)
|
|
82
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).filter_map do |directory|
|
|
83
|
+
candidate = File.join(directory, command)
|
|
84
|
+
candidate if File.executable?(candidate) && !File.directory?(candidate)
|
|
85
|
+
end.first
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruvoy
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: aarch64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- Zixin Zhou
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: async
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.45'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.45'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rack
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.2'
|
|
40
|
+
description: |
|
|
41
|
+
Ruvoy is a Ruby application runtime inside Envoy. It loads an ordinary
|
|
42
|
+
rackup into a CRuby VM embedded in a dynamic module and answers requests as
|
|
43
|
+
a terminal HTTP filter, so a request reaches Rack as owned data rather than
|
|
44
|
+
over a second HTTP connection to a separate application server. HTTP/1.1,
|
|
45
|
+
HTTP/2, TLS, timeouts and the downstream connection lifecycle stay with
|
|
46
|
+
Envoy. Scheduler-aware Ruby I/O overlaps on fibers; CPU-bound Ruby does not
|
|
47
|
+
run in parallel, since the process holds one VM.
|
|
48
|
+
email:
|
|
49
|
+
- zhouzixin@apache.org
|
|
50
|
+
executables:
|
|
51
|
+
- ruvoy
|
|
52
|
+
extensions: []
|
|
53
|
+
extra_rdoc_files: []
|
|
54
|
+
files:
|
|
55
|
+
- LICENSE
|
|
56
|
+
- README.md
|
|
57
|
+
- exe/ruvoy
|
|
58
|
+
- lib/ruvoy/4.0.0/libruvoy_fiber.so
|
|
59
|
+
- lib/ruvoy/cli.rb
|
|
60
|
+
- lib/ruvoy/envoy_config.rb
|
|
61
|
+
- lib/ruvoy/paths.rb
|
|
62
|
+
- lib/ruvoy/version.rb
|
|
63
|
+
homepage: https://github.com/CodePrometheus/ruvoy
|
|
64
|
+
licenses:
|
|
65
|
+
- Apache-2.0
|
|
66
|
+
metadata:
|
|
67
|
+
source_code_uri: https://github.com/CodePrometheus/ruvoy
|
|
68
|
+
bug_tracker_uri: https://github.com/CodePrometheus/ruvoy/issues
|
|
69
|
+
rubygems_mfa_required: 'true'
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 4.0.0
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubygems_version: 4.0.10
|
|
85
|
+
specification_version: 4
|
|
86
|
+
summary: A Rack application server that runs inside Envoy as a dynamic module
|
|
87
|
+
test_files: []
|