raptor 0.24.0 → 0.26.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: c8c3462777a399cad50b6131c497c98ab519850ff1d88d062b681cc05c9d540b
4
- data.tar.gz: 878a2629b500d8873836cda4bd463f0dcb727a33bd301fc5a246cd860a3f0957
3
+ metadata.gz: 01e94b50f111d86ede3add1b2ff22fe674c4b2da50d748fc81728ac56458d9c4
4
+ data.tar.gz: cff8caa5355ce8f7b4e1d7fb7ec1ae17e8fe5332852b57042831d9a10921276f
5
5
  SHA512:
6
- metadata.gz: 0d9b05f416a7024c91dc2861d6f748eadb1c20e01224c3e82a1202247f48220e56f81742fa600f41f1885c60587464b9506c0e2855d9392767016f5199bb0d94
7
- data.tar.gz: 93403bcdbfaef05e6a011cc795ef66b7713dc2ed4266c157afb3f5eb1b53ae60c8f7610e60d2caee75e8b82716e58cac6b771543d6f9d43823cb03e5eed87dd8
6
+ metadata.gz: a9d7fd70b2cea77c2d6b1b232542a9a70d8d4f7a40a88870128af620e5125e24d4578e77885dcbd1df9fc750785547a8a71f87791381358a693a3ee382defdf9
7
+ data.tar.gz: ea9c5cf5e367b17e47bdaeb8e927d5c2214a7d739d6a646b3162c808b3bbc3f82c026e3632a8846f4f978069add42f34cc3727415a8b90a9a321eb20dea93147
@@ -18,6 +18,17 @@ steps:
18
18
  - bundle install
19
19
  - bundle exec rake ci
20
20
 
21
+ - label: ":rails: Rails system tests"
22
+ image: "ruby:4.0"
23
+ env:
24
+ BROWSER_PATH: /usr/bin/chromium
25
+ commands:
26
+ - ruby -v
27
+ - apt-get update && apt-get install -y chromium
28
+ - bundle install
29
+ - bundle exec rails --version
30
+ - bundle exec rake test:rails
31
+
21
32
  - label: ":ruby: Ruby 4.0 +GC.stress"
22
33
  image: "ruby:4.0"
23
34
  env:
@@ -88,7 +99,7 @@ steps:
88
99
  queue: macos
89
100
  commands:
90
101
  - brew install --quiet ruby@4.0
91
- - export PATH="$(brew --prefix ruby@4.0)/bin:$PATH"
102
+ - export PATH="$(brew --prefix ruby@4.0)/bin:$$PATH"
92
103
  - bundle config set --local path .bundle
93
104
  - ruby -v
94
105
  - bundle install
@@ -101,12 +112,27 @@ steps:
101
112
  RUBY_YJIT_ENABLE: "1"
102
113
  commands:
103
114
  - brew install --quiet ruby@4.0
104
- - export PATH="$(brew --prefix ruby@4.0)/bin:$PATH"
115
+ - export PATH="$(brew --prefix ruby@4.0)/bin:$$PATH"
105
116
  - bundle config set --local path .bundle
106
117
  - ruby -v
107
118
  - bundle install
108
119
  - bundle exec rake ci
109
120
 
121
+ - label: ":rails: Rails system tests"
122
+ agents:
123
+ queue: macos
124
+ env:
125
+ BROWSER_PATH: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
126
+ commands:
127
+ - brew install --quiet ruby@4.0
128
+ - export PATH="$(brew --prefix ruby@4.0)/bin:$$PATH"
129
+ - test -x "$$BROWSER_PATH" || brew install --cask --quiet google-chrome
130
+ - bundle config set --local path .bundle
131
+ - ruby -v
132
+ - bundle install
133
+ - bundle exec rails --version
134
+ - bundle exec rake test:rails
135
+
110
136
  - label: ":ruby: Ruby 4.0 +GC.stress"
111
137
  agents:
112
138
  queue: macos
@@ -114,7 +140,7 @@ steps:
114
140
  RUBY_GC_STRESS: "1"
115
141
  commands:
116
142
  - brew install --quiet ruby@4.0
117
- - export PATH="$(brew --prefix ruby@4.0)/bin:$PATH"
143
+ - export PATH="$(brew --prefix ruby@4.0)/bin:$$PATH"
118
144
  - bundle config set --local path .bundle
119
145
  - ruby -v
120
146
  - bundle install
@@ -128,7 +154,7 @@ steps:
128
154
  RUBY_GC_STRESS: "1"
129
155
  commands:
130
156
  - brew install --quiet ruby@4.0
131
- - export PATH="$(brew --prefix ruby@4.0)/bin:$PATH"
157
+ - export PATH="$(brew --prefix ruby@4.0)/bin:$$PATH"
132
158
  - bundle config set --local path .bundle
133
159
  - ruby -v
134
160
  - bundle install
@@ -141,7 +167,7 @@ steps:
141
167
  commands:
142
168
  - brew unlink ruby
143
169
  - brew install --HEAD --quiet ruby
144
- - export PATH="$(brew --prefix ruby)/bin:$PATH"
170
+ - export PATH="$(brew --prefix ruby)/bin:$$PATH"
145
171
  - bundle config set --local path .bundle
146
172
  - ruby -v
147
173
  - bundle install
@@ -156,7 +182,7 @@ steps:
156
182
  commands:
157
183
  - brew unlink ruby
158
184
  - brew install --HEAD --quiet ruby
159
- - export PATH="$(brew --prefix ruby)/bin:$PATH"
185
+ - export PATH="$(brew --prefix ruby)/bin:$$PATH"
160
186
  - bundle config set --local path .bundle
161
187
  - ruby -v
162
188
  - bundle install
@@ -171,7 +197,7 @@ steps:
171
197
  commands:
172
198
  - brew unlink ruby
173
199
  - brew install --HEAD --quiet ruby
174
- - export PATH="$(brew --prefix ruby)/bin:$PATH"
200
+ - export PATH="$(brew --prefix ruby)/bin:$$PATH"
175
201
  - bundle config set --local path .bundle
176
202
  - ruby -v
177
203
  - bundle install
@@ -187,7 +213,7 @@ steps:
187
213
  commands:
188
214
  - brew unlink ruby
189
215
  - brew install --HEAD --quiet ruby
190
- - export PATH="$(brew --prefix ruby)/bin:$PATH"
216
+ - export PATH="$(brew --prefix ruby)/bin:$$PATH"
191
217
  - bundle config set --local path .bundle
192
218
  - ruby -v
193
219
  - bundle install
data/AGENTS.md ADDED
@@ -0,0 +1,25 @@
1
+ # Repository conventions
2
+
3
+ ## Code organisation
4
+
5
+ - Follow local style so the repository reads as one cohesive codebase.
6
+ - Group and order related declarations logically throughout the repository, not just in Ruby code. Use consistent names,
7
+ placement, and spacing to make relationships clear.
8
+ - Use blank lines to separate logical steps and final return values, without fragmenting closely related code.
9
+ - Separate standard-library, third-party, and project requires with blank lines, keeping `require_relative` separate.
10
+ Respect load-order dependencies and order semantically rather than alphabetically.
11
+ - Prefer `atomic-ruby`'s CAS-based queues and synchronisation in server internals. Use mutex-backed alternatives only
12
+ when their semantics justify them, and avoid busy-spinning across blocking operations.
13
+
14
+ ## Documentation and commits
15
+
16
+ - Keep comments focused on what and why. Explain implementation details only when the intent is otherwise unclear.
17
+ - Normally match commit subjects to changelog entries. Preserve separate entries for distinct changes worth calling out.
18
+ - Use inline code formatting for code references in commit subjects and changelog entries where applicable.
19
+
20
+ ## Tests
21
+
22
+ - Load `test_helper` first in test files and group test-support requires together, respecting boot dependencies.
23
+ - Rely on assertions' default failure messages rather than adding custom messages.
24
+ - Follow suite conventions for descriptions, setup, and assertions. Use Minitest's `parallelize_me!` when tests can
25
+ safely run independently.
data/CHANGELOG.md CHANGED
@@ -1,20 +1,28 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.26.0] - 2026-11-10
4
+
5
+ - Add in-process Rack serving
6
+
7
+ ## [0.25.0] - 2026-11-08
8
+
9
+ - Use `io-event` for reactor I/O readiness
10
+
3
11
  ## [0.24.0] - 2026-11-08
4
12
 
5
- - Honour `connection.max_body_size` for HTTP/2 request bodies without closing other streams
6
- - Wait for all current workers to boot before notifying systemd that Raptor is ready
7
- - Prefer each over call for HTTP/1 Rack response bodies that implement both
8
- - Reject empty HTTP/1 Content-Length values and unsupported transfer codings before reading request bodies
9
- - Preserve request Version headers instead of replacing them with the HTTP protocol
10
- - Trim leading tabs as optional HTTP/1 header whitespace
11
- - Honour HTTP/1 Connection token lists, with close taking precedence over keep-alive
12
- - Run worker boot hooks before starting application threads, parsers, and request handling
13
- - Close HTTP/2 response bodies when writing fails or a stream is cancelled
14
- - Bound HTTP/1 chunk size lines and trailers, including incomplete metadata
15
- - Reject invalid CRLF delimiters after HTTP/1 chunk payloads
16
- - Prefer hyphenated request headers over underscore aliases and reject HTTP/1 framing aliases
17
- - Add plugins with lifecycle hooks, cluster stats, and managed background threads
13
+ - Enforce request body limits on HTTP/2 streams
14
+ - Notify systemd only after every current worker boots
15
+ - Prefer enumerable Rack bodies over callable streaming
16
+ - Reject empty request lengths and unsupported transfer codings
17
+ - Preserve `Version` request headers in the Rack environment
18
+ - Trim optional leading tabs from HTTP/1 header values
19
+ - Honour HTTP/1 `Connection` token lists
20
+ - Complete worker boot hooks before starting request handling
21
+ - Close HTTP/2 response bodies when writing fails
22
+ - Bound HTTP/1 chunk size lines and trailers
23
+ - Validate HTTP/1 chunk payload delimiters
24
+ - Preserve request header identity during Rack normalization
25
+ - Add plugins
18
26
 
19
27
  ## [0.23.0] - 2026-10-18
20
28
 
@@ -28,13 +36,13 @@
28
36
  - Apply HTTP/2 initial window changes to responses waiting for flow control
29
37
  - Default `SERVER_NAME` for HTTP/2 requests without a host name
30
38
  - Join repeated HTTP/2 request headers instead of keeping only the last
31
- - Ignore HTTP/2 DATA frames for streams that already closed
39
+ - Ignore HTTP/2 `DATA` frames for streams that already closed
32
40
  - Credit each HTTP/2 stream window for the data that stream receives
33
- - Strip padding from HTTP/2 DATA and HEADERS frames
41
+ - Strip padding from HTTP/2 `DATA` and `HEADERS` frames
34
42
  - Encode and decode long HTTP/2 header strings without overflowing the stack
35
43
  - Limit HTTP/2 request header lists to 112 KiB
36
44
  - Reject HTTP/2 frames larger than the maximum frame size
37
- - Send GOAWAY when an HTTP/2 frame fails to decode
45
+ - Send `GOAWAY` when an HTTP/2 frame fails to decode
38
46
  - Keep pipelined HTTP/1.1 requests out of the previous request's body
39
47
  - Process chunked requests on kept-alive HTTP/1.1 connections without waiting for more data
40
48
  - Validate request framing on kept-alive HTTP/1.1 connections
@@ -93,12 +101,12 @@
93
101
  - Close connected control socket clients during shutdown
94
102
  - Skip TCP corking when `writev(2)` emits a response in one call
95
103
  - Return idle HTTP/1.1 keep-alive connections to the reactor without waiting
96
- - Increase the HTTP/1.1 keep-alive request limit to 1,000 and requeue the connection when other work is waiting
97
- - Add a read-only `/stats` control socket
98
- - Pass the worker index to worker boot and shutdown hooks
99
- - Add `RAPTOR_WORKERS`, `RAPTOR_THREADS`, and `RAPTOR_MAX_THREADS`
104
+ - Increase HTTP/1.1 keep-alive request limit
105
+ - Expose cluster stats over a control socket
106
+ - Pass worker indexes to lifecycle hooks
107
+ - Add worker and thread environment variables
100
108
  - Make worker CPU affinity opt-in
101
- - Add request-local thread and Fiber cleanup options
109
+ - Clean request-local state between requests
102
110
 
103
111
  ## [0.19.0] - 2026-08-29
104
112
 
@@ -169,7 +177,7 @@
169
177
  ## [0.10.0] - 2026-07-07
170
178
 
171
179
  - Memoize the server port string for the Rack env
172
- - Parse the Host header without regex
180
+ - Parse the `Host` header without regex
173
181
  - Apply `TCP_NODELAY` on the listener rather than each accepted socket
174
182
  - Buffer chunked body writes up to 512KB before flushing
175
183
  - Add `QUERY_STRING` to the Rack env template
@@ -184,7 +192,7 @@
184
192
  - Batch response header and body writes into a single `writev(2)` syscall
185
193
  - Close the binder as the last step of graceful shutdown
186
194
  - Lower the backpressure floor for tighter load balancing on small pools
187
- - Add load-aware `SO_REUSEPORT` routing on Linux via an attached BPF program
195
+ - Add load-aware `SO_REUSEPORT` routing via an attached BPF program
188
196
  - Reuse per-thread read buffers across HTTP/1.1 requests
189
197
  - Preserve the first `--bind` when it equals the default
190
198
 
@@ -231,7 +239,7 @@
231
239
 
232
240
  - Load `raptor.rb` or `config/raptor.rb` by default when no config path is supplied
233
241
  - Honour the peer's HTTP/2 flow-control windows when sending `DATA` frames
234
- - Assemble HEADERS across `CONTINUATION` frames
242
+ - Assemble `HEADERS` across `CONTINUATION` frames
235
243
  - Validate HTTP/2 stream IDs and emit `GOAWAY` on protocol errors
236
244
  - Offload TLS handshakes to the thread pool to keep the server thread responsive
237
245
  - Exit eager keep-alive loops on cluster shutdown
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Raptor
2
2
 
3
3
  Raptor is a high-performance, preloading, pre-forking, multi-threaded Ruby 4+ web server implementing Rack 3.2+, using
4
- NIO for non-blocking I/O and Ractors for parallel HTTP/1.1 and HTTP/2 parsing via native C extensions, which also
4
+ io-event for non-blocking I/O and Ractors for parallel HTTP/1.1 and HTTP/2 parsing via native C extensions, which also
5
5
  implement HPACK compression.
6
6
 
7
7
  > [!NOTE]
@@ -37,7 +37,7 @@ run proc { |_env| [200, { "content-type" => "text/plain" }, ["Hello, World!"]] }
37
37
  ```
38
38
  > bundle exec raptor -w 10 -t 3 hello_world.ru
39
39
  [Raptor 39704|Main|Main] Cluster initializing:
40
- [Raptor 39704|Main|Main] ├─ Version: 0.23.0
40
+ [Raptor 39704|Main|Main] ├─ Version: 0.26.0
41
41
  [Raptor 39704|Main|Main] ├─ Ruby Version: ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +YJIT +PRISM [arm64-darwin25]
42
42
  [Raptor 39704|Main|Main] ├─ Environment: development
43
43
  [Raptor 39704|Main|Main] ├─ Master PID: 39704
@@ -76,6 +76,30 @@ Also works with `rackup` and `rails server`:
76
76
  Rails apps using `SOLID_QUEUE_IN_PUMA` run jobs through `config/puma.rb`, which Raptor does not load. Run `bin/jobs` as
77
77
  a separately supervised process or container instead.
78
78
 
79
+ ### Rails system tests
80
+
81
+ Use Raptor as Capybara's server without forking, so Rails can share transactional test data with browser requests.
82
+ Add this to your system test setup, alongside your browser driver configuration:
83
+
84
+ ```ruby
85
+ require "rackup"
86
+ require "rackup/handler/raptor"
87
+
88
+ Capybara.register_server :raptor do |app, port, host|
89
+ Rackup::Handler::Raptor.run(app, Host: host, Port: port, Workers: 0) do |server|
90
+ at_exit { server.shutdown }
91
+ end
92
+ end
93
+ Capybara.server = :raptor
94
+ ```
95
+
96
+ The exit hook shuts the server down before Ruby tears down its background threads.
97
+
98
+ For other in-process uses, `require "raptor/embedded"` and call `Raptor::Embedded.new(app, host: "127.0.0.1", port: 0)`.
99
+ `start` returns when serving is ready, `port` reports the bound port, `shutdown` drains requests and releases resources,
100
+ and `join` waits for termination. Call `shutdown` before process exit. The embedded server leaves signal handlers and
101
+ process-wide configuration to its caller. Cluster plugins and process lifecycle hooks do not run in this mode.
102
+
79
103
  ## Configuration
80
104
 
81
105
  Raptor reads options from a Ruby config file, environment variables, and command line flags, in increasing order of
@@ -250,7 +274,7 @@ Set `control_url` to a Unix socket URL such as `unix:///tmp/raptor-control.sock`
250
274
 
251
275
  ## (Micro) Benchmarks
252
276
 
253
- Raptor 0.24.0 vs Puma 8.0.2 vs Falcon 0.57.0. Each case has 3 fresh-process samples, interleaved in seeded shuffled
277
+ Raptor 0.26.0 vs Puma 8.0.2 vs Falcon 0.57.0. Each case has 3 fresh-process samples, interleaved in seeded shuffled
254
278
  rounds. Each sample warms up for 3s and measures for 15s. Metrics are independent medians. ± is (max − min) / 2 /
255
279
  median, not a confidence interval. Failure counts below each table sum all samples, so a failed run cannot disappear in
256
280
  a median. Throughput counts successful ordinary requests only.
@@ -278,92 +302,92 @@ accounting for shared pages instead of counting them fully in every worker.
278
302
 
279
303
  | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
280
304
  | ------ | -------------- | --------------- | ------------ | --------------- | ------ | ------------- | ---------- | ------- |
281
- | Raptor | Fixed (3) | - | 3.32k ±0.2% | - | 50.10 | - | 2.387 | 364.0 |
282
- | Raptor | Scaling (3–16) | - | 8.10k ±0.3% | - | 22.45 | - | 0.514 | 340.8 |
283
- | Puma | Fixed (3) | Fixed (3) | 1.55k ±0.9% | 53.4% lower | 121.78 | 143.1% higher | 0.737 | 283.8 |
284
- | Puma | Scaling (3–16) | Scaling (3–16) | 8.13k ±0.7% | 0.4% higher | 22.41 | 0.1% lower | 0.481 | 341.2 |
285
- | Falcon | Fibers | Fixed (3) | 10.65k ±4.1% | 220.8% higher | 16.18 | 67.7% lower | 0.450 | 425.5 |
286
- | Falcon | Fibers | Scaling (3–16) | 10.65k ±4.1% | 31.5% higher | 16.18 | 27.9% lower | 0.450 | 425.5 |
305
+ | Raptor | Fixed (3) | - | 3.39k ±0.6% | - | 46.17 | - | 2.434 | 337.1 |
306
+ | Raptor | Scaling (3–16) | - | 8.18k ±1.3% | - | 22.19 | - | 0.467 | 325.6 |
307
+ | Puma | Fixed (3) | Fixed (3) | 1.52k ±1.1% | 55.2% lower | 117.49 | 154.5% higher | 0.996 | 272.0 |
308
+ | Puma | Scaling (3–16) | Scaling (3–16) | 8.14k ±1.3% | 0.4% lower | 22.37 | 0.8% higher | 0.473 | 318.8 |
309
+ | Falcon | Fibers | Fixed (3) | 10.99k ±1.2% | 224.1% higher | 15.60 | 66.2% lower | 0.440 | 447.2 |
310
+ | Falcon | Fibers | Scaling (3–16) | 10.99k ±1.2% | 34.3% higher | 15.60 | 29.7% lower | 0.440 | 447.2 |
287
311
 
288
312
  ### IO over HTTP/1.1 keep-alive
289
313
 
290
314
  | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
291
315
  | ------ | -------------- | --------------- | ----------- | --------------- | ------ | ------------- | ---------- | ------- |
292
- | Raptor | Fixed (3) | - | 3.01k ±8.2% | - | 56.38 | - | 2.306 | 365.0 |
293
- | Raptor | Scaling (3–16) | - | 7.55k ±0.6% | - | 24.18 | - | 0.500 | 363.7 |
294
- | Raptor | Fixed (16) | - | 7.56k ±0.7% | - | 24.16 | - | 0.468 | 367.8 |
295
- | Puma | Fixed (3) | Fixed (3) | 1.49k ±0.2% | 50.4% lower | 105.33 | 86.8% higher | 0.784 | 290.7 |
296
- | Puma | Scaling (3–16) | Scaling (3–16) | 7.63k ±0.9% | 1.1% higher | 23.89 | 1.2% lower | 0.466 | 329.9 |
297
- | Puma | Fixed (16) | Fixed (16) | 7.73k ±0.5% | 2.2% higher | 23.55 | 2.5% lower | 0.461 | 332.3 |
298
- | Falcon | Fibers | Fixed (3) | 7.54k ±1.8% | 150.8% higher | 24.40 | 56.7% lower | 0.373 | 357.4 |
299
- | Falcon | Fibers | Scaling (3–16) | 7.54k ±1.8% | 0.1% lower | 24.40 | 0.9% higher | 0.373 | 357.4 |
300
- | Falcon | Fibers | Fixed (16) | 7.54k ±1.8% | 0.2% lower | 24.40 | 1.0% higher | 0.373 | 357.4 |
316
+ | Raptor | Fixed (3) | - | 3.37k ±1.8% | - | 37.11 | - | 2.460 | 353.2 |
317
+ | Raptor | Scaling (3–16) | - | 7.39k ±1.7% | - | 24.53 | - | 0.470 | 344.9 |
318
+ | Raptor | Fixed (16) | - | 7.49k ±1.6% | - | 24.24 | - | 0.455 | 341.6 |
319
+ | Puma | Fixed (3) | Fixed (3) | 1.48k ±0.2% | 56.2% lower | 106.41 | 186.8% higher | 0.979 | 282.1 |
320
+ | Puma | Scaling (3–16) | Scaling (3–16) | 7.58k ±1.1% | 2.5% higher | 24.00 | 2.2% lower | 0.435 | 310.5 |
321
+ | Puma | Fixed (16) | Fixed (16) | 7.59k ±0.9% | 1.4% higher | 23.97 | 1.1% lower | 0.437 | 316.5 |
322
+ | Falcon | Fibers | Fixed (3) | 7.31k ±1.5% | 116.9% higher | 24.86 | 33.0% lower | 0.380 | 336.6 |
323
+ | Falcon | Fibers | Scaling (3–16) | 7.31k ±1.5% | 1.1% lower | 24.86 | 1.4% higher | 0.380 | 336.6 |
324
+ | Falcon | Fibers | Fixed (16) | 7.31k ±1.5% | 2.3% lower | 24.86 | 2.6% higher | 0.380 | 336.6 |
301
325
 
302
326
  ### IO over HTTP/2
303
327
 
304
- | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
305
- | ------ | -------------- | --------------- | ----------- | --------------- | ------ | ------------- | ---------- | ------- |
306
- | Raptor | Fixed (3) | - | 1.60k ±6.4% | - | 119.04 | - | 0.779 | 418.0 |
307
- | Raptor | Scaling (3–16) | - | 7.98k ±9.9% | - | 23.38 | - | 0.565 | 894.4 |
308
- | Falcon | Fibers | Fixed (3) | 7.18k ±1.3% | 348.7% higher | 26.29 | 77.9% lower | 0.438 | 515.8 |
309
- | Falcon | Fibers | Scaling (3–16) | 7.18k ±1.3% | 10.0% lower | 26.29 | 12.4% higher | 0.438 | 515.8 |
328
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
329
+ | ------ | -------------- | --------------- | ------------ | --------------- | ------ | ------------- | ---------- | ------- |
330
+ | Raptor | Fixed (3) | - | 1.61k ±4.7% | - | 120.85 | - | 0.815 | 399.9 |
331
+ | Raptor | Scaling (3–16) | - | 6.80k ±22.8% | - | 40.32 | - | 0.580 | 912.4 |
332
+ | Falcon | Fibers | Fixed (3) | 7.12k ±3.5% | 342.8% higher | 26.17 | 78.3% lower | 0.432 | 539.7 |
333
+ | Falcon | Fibers | Scaling (3–16) | 7.12k ±3.5% | 4.7% higher | 26.17 | 35.1% lower | 0.432 | 539.7 |
310
334
 
311
335
  ### CPU over HTTP/1.1
312
336
 
313
337
  | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
314
338
  | ------ | -------------- | --------------- | ----------- | --------------- | ------ | ------------- | ---------- | ------- |
315
- | Raptor | Fixed (3) | - | 3.13k ±0.3% | - | 72.60 | - | 2.673 | 526.0 |
316
- | Raptor | Scaling (3–16) | - | 3.22k ±2.0% | - | 68.49 | - | 2.546 | 520.0 |
317
- | Puma | Fixed (3) | Fixed (3) | 2.98k ±0.7% | 4.8% lower | 62.70 | 13.6% lower | 2.615 | 474.7 |
318
- | Puma | Scaling (3–16) | Scaling (3–16) | 2.64k ±3.2% | 17.9% lower | 59.81 | 12.7% lower | 2.522 | 567.4 |
319
- | Falcon | Fibers | Fixed (3) | 3.65k ±7.0% | 16.4% higher | 46.10 | 36.5% lower | 2.311 | 389.2 |
320
- | Falcon | Fibers | Scaling (3–16) | 3.65k ±7.0% | 13.3% higher | 46.10 | 32.7% lower | 2.311 | 389.2 |
339
+ | Raptor | Fixed (3) | - | 3.43k ±1.1% | - | 67.46 | - | 2.507 | 509.4 |
340
+ | Raptor | Scaling (3–16) | - | 3.26k ±2.5% | - | 70.51 | - | 2.572 | 499.6 |
341
+ | Puma | Fixed (3) | Fixed (3) | 3.21k ±3.5% | 6.3% lower | 56.00 | 17.0% lower | 2.454 | 441.3 |
342
+ | Puma | Scaling (3–16) | Scaling (3–16) | 3.15k ±2.3% | 3.4% lower | 49.26 | 30.1% lower | 2.186 | 431.0 |
343
+ | Falcon | Fibers | Fixed (3) | 3.36k ±4.2% | 2.1% lower | 48.20 | 28.5% lower | 2.555 | 435.8 |
344
+ | Falcon | Fibers | Scaling (3–16) | 3.36k ±4.2% | 3.0% higher | 48.20 | 31.6% lower | 2.555 | 435.8 |
321
345
 
322
346
  ### CPU over HTTP/1.1 keep-alive
323
347
 
324
348
  | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
325
349
  | ------ | -------------- | --------------- | ----------- | --------------- | ------ | ------------- | ---------- | ------- |
326
- | Raptor | Fixed (3) | - | 2.88k ±1.6% | - | 57.40 | - | 2.721 | 515.4 |
327
- | Raptor | Scaling (3–16) | - | 2.81k ±3.8% | - | 56.14 | - | 2.721 | 528.2 |
328
- | Puma | Fixed (3) | Fixed (3) | 3.24k ±0.7% | 12.3% higher | 60.73 | 5.8% higher | 2.449 | 406.4 |
329
- | Puma | Scaling (3–16) | Scaling (3–16) | 2.71k ±3.6% | 3.3% lower | 70.56 | 25.7% higher | 2.459 | 487.6 |
330
- | Falcon | Fibers | Fixed (3) | 3.62k ±1.0% | 25.8% higher | 62.03 | 8.1% higher | 2.369 | 542.0 |
331
- | Falcon | Fibers | Scaling (3–16) | 3.62k ±1.0% | 29.1% higher | 62.03 | 10.5% higher | 2.369 | 542.0 |
350
+ | Raptor | Fixed (3) | - | 3.10k ±5.2% | - | 54.76 | - | 2.560 | 484.4 |
351
+ | Raptor | Scaling (3–16) | - | 2.97k ±1.2% | - | 49.64 | - | 2.553 | 505.3 |
352
+ | Puma | Fixed (3) | Fixed (3) | 3.52k ±6.1% | 13.7% higher | 51.14 | 6.6% lower | 2.299 | 384.4 |
353
+ | Puma | Scaling (3–16) | Scaling (3–16) | 2.90k ±4.2% | 2.3% lower | 65.73 | 32.4% higher | 2.283 | 472.5 |
354
+ | Falcon | Fibers | Fixed (3) | 3.17k ±1.9% | 2.4% higher | 69.58 | 27.1% higher | 2.766 | 536.5 |
355
+ | Falcon | Fibers | Scaling (3–16) | 3.17k ±1.9% | 6.7% higher | 69.58 | 40.2% higher | 2.766 | 536.5 |
332
356
 
333
357
  ### CPU over HTTP/2
334
358
 
335
359
  | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
336
360
  | ------ | -------------- | --------------- | ------------ | --------------- | ------ | ------------- | ---------- | ------- |
337
- | Raptor | Fixed (3) | - | 3.12k ±1.3% | - | 65.66 | - | 2.439 | 587.6 |
338
- | Raptor | Scaling (3–16) | - | 3.15k ±0.4% | - | 74.95 | - | 2.344 | 609.0 |
339
- | Falcon | Fibers | Fixed (3) | 3.49k ±20.7% | 11.7% higher | 120.58 | 83.6% higher | 2.309 | 536.3 |
340
- | Falcon | Fibers | Scaling (3–16) | 3.49k ±20.7% | 10.7% higher | 120.58 | 60.9% higher | 2.309 | 536.3 |
361
+ | Raptor | Fixed (3) | - | 3.26k ±2.1% | - | 71.84 | - | 2.329 | 573.4 |
362
+ | Raptor | Scaling (3–16) | - | 3.25k ±2.6% | - | 69.37 | - | 2.383 | 590.6 |
363
+ | Falcon | Fibers | Fixed (3) | 2.02k ±11.2% | 37.9% lower | 176.57 | 145.8% higher | 2.405 | 559.2 |
364
+ | Falcon | Fibers | Scaling (3–16) | 2.02k ±11.2% | 37.9% lower | 176.57 | 154.5% higher | 2.405 | 559.2 |
341
365
 
342
366
  ### 64 KiB body over HTTP/1.1 keep-alive
343
367
 
344
- | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
345
- | ------ | -------------- | --------------- | ------------ | --------------- | ------ | ------------- | ---------- | ------- |
346
- | Raptor | Scaling (3–16) | - | 96.13k ±0.8% | - | 3.68 | - | 0.048 | 512.3 |
347
- | Puma | Scaling (3–16) | Scaling (3–16) | 83.26k ±0.0% | 13.4% lower | 3.94 | 7.3% higher | 0.055 | 1344.6 |
348
- | Falcon | Fibers | Scaling (3–16) | 98.30k ±0.5% | 2.3% higher | 3.89 | 5.7% higher | 0.051 | 471.5 |
368
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
369
+ | ------ | -------------- | --------------- | ------------- | --------------- | ------ | ------------- | ---------- | ------- |
370
+ | Raptor | Scaling (3–16) | - | 94.58k ±2.0% | - | 3.77 | - | 0.052 | 507.6 |
371
+ | Puma | Scaling (3–16) | Scaling (3–16) | 88.40k ±2.1% | 6.5% lower | 3.66 | 2.9% lower | 0.054 | 1237.4 |
372
+ | Falcon | Fibers | Scaling (3–16) | 103.33k ±5.0% | 9.2% higher | 3.89 | 3.2% higher | 0.051 | 344.7 |
349
373
 
350
374
  ### IO + SSE (native) over HTTP/1.1 keep-alive
351
375
 
352
- | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
353
- | ------------- | -------------- | --------------- | ------------ | --------------- | ------ | ------------- | ---------- | ------- |
354
- | Raptor | Fixed (3) | - | 3.01k ±4.9% | - | 51.22 | - | 2.286 | 369.6 |
355
- | Raptor | Scaling (3–16) | - | 7.49k ±0.8% | - | 24.39 | - | 0.488 | 372.7 |
356
- | Puma | Fixed (3) | Fixed (3) | 0.39k ±13.8% | 87.0% lower | 314.80 | 514.6% higher | 1.250 | 282.9 |
357
- | Puma | Scaling (3–16) | Scaling (3–16) | 7.51k ±0.4% | 0.4% higher | 24.23 | 0.6% lower | 0.464 | 336.3 |
358
- | Falcon | Fibers | Fixed (3) | 7.57k ±4.4% | 151.9% higher | 24.04 | 53.1% lower | 0.375 | 355.5 |
359
- | Falcon | Fibers | Scaling (3–16) | 7.57k ±4.4% | 1.2% higher | 24.04 | 1.4% lower | 0.375 | 355.5 |
360
- | Raptor (enum) | Fixed (3) | - | 0.88k ±0.3% | - | 63.76 | - | 7.972 | 362.6 |
376
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
377
+ | ------------- | -------------- | --------------- | ----------- | --------------- | ------ | ------------- | ---------- | ------- |
378
+ | Raptor | Fixed (3) | - | 3.24k ±1.6% | - | 44.69 | - | 2.396 | 355.6 |
379
+ | Raptor | Scaling (3–16) | - | 7.39k ±0.8% | - | 24.47 | - | 0.468 | 346.4 |
380
+ | Puma | Fixed (3) | Fixed (3) | 0.35k ±4.5% | 89.3% lower | 316.86 | 609.1% higher | 1.764 | 243.6 |
381
+ | Puma | Scaling (3–16) | Scaling (3–16) | 7.38k ±4.3% | 0.2% lower | 24.61 | 0.6% higher | 0.440 | 311.3 |
382
+ | Falcon | Fibers | Fixed (3) | 7.32k ±1.0% | 125.8% higher | 24.83 | 44.4% lower | 0.383 | 354.4 |
383
+ | Falcon | Fibers | Scaling (3–16) | 7.32k ±1.0% | 1.0% lower | 24.83 | 1.5% higher | 0.383 | 354.4 |
384
+ | Raptor (enum) | Fixed (3) | - | 0.97k ±4.8% | - | 56.38 | - | 7.624 | 324.4 |
361
385
 
362
- Puma, Fixed (3) had 28 timeouts, 1 stalled stream across all samples.
386
+ Puma, Fixed (3) had 68 timeouts, 1 stalled stream across all samples.
363
387
 
364
- Raptor (enum), Fixed (3) had 29 timeouts across all samples.
388
+ Raptor (enum), Fixed (3) had 21 timeouts, 2 stalled streams across all samples.
365
389
 
366
- > ruby 4.1.0dev (2026-10-27T15:28:18Z HEAD 33ac6afea7) +YJIT +PRISM [aarch64-linux]
390
+ > ruby 4.1.0dev (2026-11-10T00:02:04Z HEAD 7a07e456e4) +YJIT +PRISM [aarch64-linux]
367
391
  > 10 workers, 120 HTTP/1.1 client connections, 40 HTTP/2 connections × 3 streams.
368
392
 
369
393
  Run [bin/benchmark](bin/benchmark) to reproduce.
data/Rakefile CHANGED
@@ -20,6 +20,13 @@ Rake::ExtensionTask.new("raptor_native", GEMSPEC) do |ext|
20
20
  ext.lib_dir = "lib/raptor"
21
21
  end
22
22
 
23
+ namespace :test do
24
+ desc "Run Rails system tests"
25
+ task rails: :compile do
26
+ ruby "-Itest", "test/system/rails.rb"
27
+ end
28
+ end
29
+
23
30
  namespace :bpf do
24
31
  task :compile do
25
32
  if RUBY_PLATFORM.include?("linux")
@@ -41,6 +48,6 @@ namespace :rbs do
41
48
  end
42
49
  end
43
50
 
44
- task default: %i[clobber compile bpf:compile rbs:generate test]
45
- task build: %i[clobber compile rbs:generate]
46
- task ci: %i[clobber compile test]
51
+ task default: [:clobber, :compile, :"bpf:compile", :"rbs:generate", :test]
52
+ task build: [:clobber, :compile, :"rbs:generate"]
53
+ task ci: [:clobber, :compile, :test]
@@ -16,7 +16,7 @@ This guide compares their architecture and relates it to the [current README ben
16
16
  | Protocol processing | App threads and reactor share the worker's GVL | Eager parsing inline, separate protocol Ractors on the pipeline path |
17
17
  | Pool coordination | Mutex and condition variables | Lock-free work queue with idle-thread parking |
18
18
  | Pool growth | Adds threads when queued work exceeds waiting capacity | Adds threads when queued work and blocking time justify growth |
19
- | Idle and partial connections | `nio4r` reactor | `nio4r` reactor |
19
+ | Idle and partial connections | `nio4r` reactor | `io-event` reactor |
20
20
  | Cluster dispatch | Shared listeners with load-sensitive accept delay | Shared listeners, plus load-aware BPF dispatch on supported Linux TCP bindings |
21
21
  | HTTP/2 | Not implemented | Native parsing, HPACK and stream flow control |
22
22
  | Worker monitoring | Pipe messages | Shared-memory stats |
@@ -129,7 +129,7 @@ flowchart TB
129
129
  subgraph Worker["Raptor worker process, one of N"]
130
130
  SRV["Server thread<br/>IO.select + accept_nonblock<br/>polls for pool capacity"]
131
131
 
132
- RCT["Reactor thread<br/>NIO::Selector<br/>read_nonblock 64KB<br/>Red-Black-Tree timeouts"]
132
+ RCT["Reactor thread<br/>io-event selector<br/>read_nonblock 64KB<br/>Red-Black-Tree timeouts"]
133
133
 
134
134
  subgraph RP1["HTTP/1.1 RactorPool"]
135
135
  RW1["Pipeline Ractor 1<br/>HTTP/1.x parser<br/>chunked decode"]
@@ -211,6 +211,8 @@ Puma's cluster accept delay backs off according to worker load. On supported Lin
211
211
 
212
212
  Both reactors keep idle and partial connections off application threads. Puma tracks deadlines in a sorted array. Raptor uses a red-black tree for logarithmic insertion and removal of individual deadlines. Updating a connection's deadline does not require scanning the other connections.
213
213
 
214
+ Raptor uses `io-event` readiness fibers only on its reactor thread. It does not install a Fiber scheduler or change how the application runs.
215
+
214
216
  ### Responses and streaming
215
217
 
216
218
  Ordinary HTTP/1.1 response writing runs on app threads in both servers. Raptor can gather headers and array-body buffers with `writev` and reuse per-thread response buffers. Puma uses its own buffered writes and Linux TCP corking. Both support file bodies and enumerable streaming bodies.
data/docs/talk.md CHANGED
@@ -640,7 +640,7 @@ with no hard maximum. You don't have to choose one fixed thread count.</big>
640
640
 
641
641
  Slow clients, half-sent requests, and idle keep-alive connections aren't ready to be read yet. The reactor watches all of them at once.
642
642
 
643
- <big>Like Puma, it uses [`nio4r`](https://github.com/socketry/nio4r), backed by `epoll` on Linux
643
+ <big>It uses [`io-event`](https://github.com/socketry/io-event), backed by `epoll` on Linux
644
644
  and `kqueue` on macOS.</big>
645
645
 
646
646
  <big>The HTTP/1.1 defaults are 30 seconds for the first bytes,
@@ -12,24 +12,31 @@ module Rackup
12
12
  Port: 9292
13
13
  }.freeze
14
14
 
15
- # Boots a Raptor cluster serving the given Rack application.
15
+ # Serves the given Rack application using the handler options.
16
16
  #
17
17
  # @param app [#call] the Rack application to serve
18
18
  # @param options [Hash] handler options provided by Rackup or the host
19
- # @yield before the cluster starts running
20
- # @yieldparam cluster [::Raptor::Cluster] the cluster instance
19
+ # @yield before the server starts running
20
+ # @yieldparam server [::Raptor::Cluster, ::Raptor::Embedded] the server instance
21
21
  # @return [void]
22
22
  #
23
- # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, **untyped options) { (::Raptor::Cluster) -> void } -> void
23
+ # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, **untyped options) { (::Raptor::Cluster | ::Raptor::Embedded) -> void } -> void
24
24
  def self.run(app, **options)
25
25
  require_relative "../../raptor/cli"
26
26
  require_relative "../../raptor/cluster"
27
+ require_relative "../../raptor/embedded"
27
28
 
28
- cluster = ::Raptor::Cluster.new(build_cluster_options(app, options))
29
+ configuration = build_options(app, options)
30
+ server = if configuration[:workers].zero?
31
+ configuration.delete(:app)
32
+ ::Raptor::Embedded.new(app, **configuration)
33
+ else
34
+ ::Raptor::Cluster.new(configuration)
35
+ end
29
36
 
30
- yield cluster if block_given?
37
+ yield server if block_given?
31
38
 
32
- cluster.run
39
+ server.run
33
40
  end
34
41
 
35
42
  # Returns the handler-specific options surfaced by `rackup --help`.
@@ -41,24 +48,24 @@ module Rackup
41
48
  {
42
49
  "Host=HOST" => "Hostname to listen on (default: #{DEFAULT_OPTIONS[:Host]})",
43
50
  "Port=PORT" => "Port to listen on (default: #{DEFAULT_OPTIONS[:Port]})",
44
- "Workers=NUM" => "Number of worker processes (default: available processor count)",
51
+ "Workers=NUM" => "Number of worker processes, or 0 for in-process serving (default: available processor count)",
45
52
  "Threads=NUM" => "Number of threads per worker (default: 3)",
46
53
  "MaxThreads=NUM" => "Maximum threads per worker (`unlimited` for no limit; default: unlimited)",
47
54
  "Config=PATH" => "Load additional configuration from PATH"
48
55
  }
49
56
  end
50
57
 
51
- # Builds a Raptor cluster options hash from Rack handler options.
58
+ # Builds Raptor server configuration from Rack handler options.
52
59
  #
53
60
  # Options not explicitly supplied by the user (per the `:user_supplied_options`
54
61
  # key) are treated as host-provided defaults.
55
62
  #
56
63
  # @param app [#call] the Rack application to serve
57
64
  # @param options [Hash] handler options provided by Rackup or the host
58
- # @return [Hash{Symbol => untyped}] cluster configuration
65
+ # @return [Hash{Symbol => untyped}] server configuration
59
66
  #
60
67
  # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Hash[Symbol, untyped] options) -> Hash[Symbol, untyped]
61
- def self.build_cluster_options(app, options)
68
+ def self.build_options(app, options)
62
69
  defaults = DEFAULT_OPTIONS.dup
63
70
 
64
71
  if (user_supplied_options = options.delete(:user_supplied_options))
@@ -116,7 +123,7 @@ module Rackup
116
123
  result[:on_error] = config[:on_error] if config.key?(:on_error)
117
124
  config.merge(result)
118
125
  end
119
- private_class_method :build_cluster_options
126
+ private_class_method :build_options
120
127
  end
121
128
 
122
129
  register :raptor, Raptor