raptor 0.14.0 → 0.15.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: 90014b6262f682d50ed48e31fea60c483aeb217fa28f758bcef6eeada39baf98
4
- data.tar.gz: 37b4e90995cc4e76319318f14a6c1d73a6d1ab841c97f25c5b0543ef07f52c87
3
+ metadata.gz: 064ae83daf94da7f8f08e7e8ec641da9d340980d7b79b74f5135a0cb6bbba420
4
+ data.tar.gz: a672546792de83d7567980cd37eb10d123a528b20c741ea03c9edd599e59dd92
5
5
  SHA512:
6
- metadata.gz: ba970eef596750d5e2cc885018bc6d981edb0ef1479dfade73d66264e54693f513e52e5ec4a630f198b1ba241f121ca976588717b9c15e34360198a8c8bbcb75
7
- data.tar.gz: 6aa65a255c612c094b568b252c8f5ac42debabf92e72896a341fd0ac5031960e56255774c750bdafa2e6658261dc2e3104dc0a546e839d99ce1be773d42cd26e
6
+ metadata.gz: 558d707115b817ab6312c0c2b9ea118953b30a642d7d2b6fc7dffd8544fc3cf85b5add06eb97f4db837568cb67fca32f833a729adc89c9d62af2b2194243125f
7
+ data.tar.gz: a6a82af2ad505fd6c452f957b126c58522848163e80df0b6b856054c345837f4c46ce458370670c1a62f33255cfcd24c21df77bb0e556cd4c6f6d53485b95bfb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.15.0] - 2026-08-16
4
+
5
+ - Yield between accepts when the thread pool queue exceeds the pool size
6
+ - Default the worker count to the cgroup-aware available processor count
7
+
3
8
  ## [0.14.0] - 2026-08-09
4
9
 
5
10
  - Cap eager HTTP/2 frame consumption at thread pool saturation
data/README.md CHANGED
@@ -36,23 +36,23 @@ run proc { |_env| [200, { "content-type" => "text/plain" }, ["Hello, World!"]] }
36
36
 
37
37
  ```
38
38
  > bundle exec raptor -w 4 -t 3 hello_world.ru
39
- [Raptor 59013|Main|Main] Cluster initializing:
40
- [Raptor 59013|Main|Main] ├─ Version: 0.14.0
41
- [Raptor 59013|Main|Main] ├─ Ruby Version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
42
- [Raptor 59013|Main|Main] ├─ Environment: development
43
- [Raptor 59013|Main|Main] ├─ Master PID: 59013
44
- [Raptor 59013|Main|Main] │ └─ 4 worker processes
45
- [Raptor 59013|Main|Main] │ ├─ 1 server thread
46
- [Raptor 59013|Main|Main] │ ├─ 1 reactor thread
47
- [Raptor 59013|Main|Main] │ ├─ 1 pipeline ractor
48
- [Raptor 59013|Main|Main] │ ├─ 1 pipeline collector thread
49
- [Raptor 59013|Main|Main] │ ├─ 3 worker threads
50
- [Raptor 59013|Main|Main] │ └─ 1 stats thread
51
- [Raptor 59013|Main|Main] └─ Listening on 0.0.0.0:9292
52
- [Raptor 59023|Main|Main] Worker 0 booted
53
- [Raptor 59024|Main|Main] Worker 1 booted
54
- [Raptor 59025|Main|Main] Worker 2 booted
55
- [Raptor 59026|Main|Main] Worker 3 booted
39
+ [Raptor 33061|Main|Main] Cluster initializing:
40
+ [Raptor 33061|Main|Main] ├─ Version: 0.15.0
41
+ [Raptor 33061|Main|Main] ├─ Ruby Version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
42
+ [Raptor 33061|Main|Main] ├─ Environment: development
43
+ [Raptor 33061|Main|Main] ├─ Master PID: 33061
44
+ [Raptor 33061|Main|Main] │ └─ 4 worker processes
45
+ [Raptor 33061|Main|Main] │ ├─ 1 server thread
46
+ [Raptor 33061|Main|Main] │ ├─ 1 reactor thread
47
+ [Raptor 33061|Main|Main] │ ├─ 1 pipeline ractor
48
+ [Raptor 33061|Main|Main] │ ├─ 1 pipeline collector thread
49
+ [Raptor 33061|Main|Main] │ ├─ 3 worker threads
50
+ [Raptor 33061|Main|Main] │ └─ 1 stats thread
51
+ [Raptor 33061|Main|Main] └─ Listening on 0.0.0.0:9292
52
+ [Raptor 33064|Main|Main] Worker 0 booted
53
+ [Raptor 33065|Main|Main] Worker 1 booted
54
+ [Raptor 33066|Main|Main] Worker 2 booted
55
+ [Raptor 33067|Main|Main] Worker 3 booted
56
56
  ```
57
57
 
58
58
  ```
@@ -193,7 +193,7 @@ Worker 1 (phase 0): pid=91351, requests=1199, busy=1/3, backlog=0, booted, last_
193
193
 
194
194
  ## (Micro) Benchmarks
195
195
 
196
- Raptor 0.14.0 vs Puma 8.0.2 vs PumaPlus 0.1.0 vs Falcon 0.57.0 across two workload profiles. **IO-bound** is a GET
196
+ Raptor 0.15.0 vs Puma 8.0.2 vs PumaPlus 0.1.0 vs Falcon 0.57.0 across two workload profiles. **IO-bound** is a GET
197
197
  endpoint that interleaves 5-10 short sleeps (total 2.5-15ms) with small CPU work, simulating a read path that makes
198
198
  several DB or cache calls. **CPU-bound** is a POST endpoint that accepts a small JSON body, interleaves 3-5 chunks of
199
199
  JSON item building (total 450-1500 items) with sub-100µs sleeps, and returns the built array, simulating a write path
@@ -202,20 +202,20 @@ that does most of its work in Ruby with a few near-zero-cost cache hits.
202
202
  Each cell reports the median throughput and median p95 latency independently across 5 runs, so the two numbers in a row
203
203
  may come from different runs. Every run starts a fresh server process so the samples are independent of each other;
204
204
  state accumulated in a previous run cannot bias the next. Across the whole table, the widest spread
205
- ((max - min) / 2 / median) between runs of a single cell was ±35.4% for throughput and ±95.1% for p95.
205
+ ((max - min) / 2 / median) between runs of a single cell was ±29.5% for throughput and ±42.2% for p95.
206
206
 
207
- | Protocol | Workload | Raptor req/s | Raptor p95 | Puma req/s | Puma p95 | vs Puma req/s | vs Puma p95 | PumaPlus req/s | PumaPlus p95 | vs PumaPlus req/s | vs PumaPlus p95 | Falcon req/s | Falcon p95 | vs Falcon req/s | vs Falcon p95 |
208
- | --------------------- | -------- | ------------ | ---------- | ----------- | --------- | ------------- | ----------- | -------------- | ------------ | ----------------- | --------------- | ------------ | ---------- | --------------- | ------------- |
209
- | HTTP/1.1 | IO | 1.33k req/s | 52.20 ms | 0.48k req/s | 140.60 ms | 175.8% higher | 62.9% lower | 0.48k req/s | 122.30 ms | 177.0% higher | 57.3% lower | 3.41k req/s | 21.50 ms | 60.9% lower | 142.8% higher |
210
- | HTTP/1.1 | CPU | 3.93k req/s | 20.80 ms | 1.74k req/s | 39.60 ms | 125.6% higher | 47.5% lower | 2.32k req/s | 27.60 ms | 69.2% higher | 24.6% lower | 3.60k req/s | 16.40 ms | 9.3% higher | 26.8% higher |
211
- | HTTP/1.1 (keep-alive) | IO | 1.31k req/s | 46.00 ms | 0.46k req/s | 133.80 ms | 183.3% higher | 65.6% lower | 0.46k req/s | 128.20 ms | 186.0% higher | 64.1% lower | 2.06k req/s | 35.40 ms | 36.5% lower | 29.9% higher |
212
- | HTTP/1.1 (keep-alive) | CPU | 3.48k req/s | 21.50 ms | 1.73k req/s | 41.00 ms | 101.5% higher | 47.6% lower | 2.09k req/s | 30.40 ms | 66.0% higher | 29.3% lower | 3.74k req/s | 17.70 ms | 7.0% lower | 21.5% higher |
213
- | HTTP/2 | IO | 0.47k req/s | 161.47 ms | N/A | N/A | - | - | 0.52k req/s | 113.14 ms | 8.1% lower | 42.7% higher | 2.12k req/s | 34.10 ms | 77.6% lower | 373.6% higher |
214
- | HTTP/2 | CPU | 2.09k req/s | 40.26 ms | N/A | N/A | - | - | 2.23k req/s | 29.19 ms | 5.9% lower | 37.9% higher | 2.85k req/s | 36.93 ms | 26.4% lower | 9.0% higher |
207
+ | Protocol | Workload | Raptor req/s | Raptor p95 | Puma req/s | Puma p95 | vs Puma req/s | vs Puma p95 | PumaPlus req/s | PumaPlus p95 | vs PumaPlus req/s | vs PumaPlus p95 | Falcon req/s | Falcon p95 | vs Falcon req/s | vs Falcon p95 |
208
+ | --------------------- | -------- | ------------ | ---------- | ----------- | --------- | ------------- | ------------ | -------------- | ------------ | ----------------- | --------------- | ------------ | ---------- | --------------- | ------------- |
209
+ | HTTP/1.1 | IO | 3.28k req/s | 59.90 ms | 1.44k req/s | 132.20 ms | 127.7% higher | 54.7% lower | 1.55k req/s | 91.30 ms | 111.3% higher | 34.4% lower | 12.50k req/s | 13.80 ms | 73.7% lower | 334.1% higher |
210
+ | HTTP/1.1 | CPU | 8.49k req/s | 32.40 ms | 9.08k req/s | 19.40 ms | 6.5% lower | 67.0% higher | 8.79k req/s | 17.40 ms | 3.4% lower | 86.2% higher | 6.89k req/s | 25.50 ms | 23.2% higher | 27.1% higher |
211
+ | HTTP/1.1 (keep-alive) | IO | 2.08k req/s | 54.60 ms | 1.40k req/s | 112.80 ms | 48.0% higher | 51.6% lower | 1.53k req/s | 91.10 ms | 35.8% higher | 40.1% lower | 6.17k req/s | 28.70 ms | 66.3% lower | 90.2% higher |
212
+ | HTTP/1.1 (keep-alive) | CPU | 9.04k req/s | 17.40 ms | 8.92k req/s | 21.30 ms | 1.3% higher | 18.3% lower | 9.04k req/s | 17.00 ms | 0.1% higher | 2.4% higher | 7.32k req/s | 29.90 ms | 23.5% higher | 41.8% lower |
213
+ | HTTP/2 | IO | 0.97k req/s | 201.32 ms | N/A | N/A | - | - | 1.49k req/s | 92.51 ms | 35.0% lower | 117.6% higher | 6.61k req/s | 27.95 ms | 85.4% lower | 620.4% higher |
214
+ | HTTP/2 | CPU | 5.42k req/s | 35.22 ms | N/A | N/A | - | - | 8.61k req/s | 18.18 ms | 37.0% lower | 93.8% higher | 7.32k req/s | 29.95 ms | 25.9% lower | 17.6% higher |
215
215
 
216
216
  > ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [aarch64-linux]
217
- > 4 worker processes; Raptor, Puma, and PumaPlus run 3 threads per worker, Falcon runs unbounded fibers per worker;
218
- > 48 concurrent HTTP/1.1 client connections; 16 concurrent HTTP/2 client connections × 3 streams each
217
+ > 10 worker processes; Raptor, Puma, and PumaPlus run 3 threads per worker, Falcon runs unbounded fibers per worker;
218
+ > 120 concurrent HTTP/1.1 client connections; 40 concurrent HTTP/2 client connections × 3 streams each
219
219
 
220
220
  See [bin/benchmark](bin/benchmark) for more details.
221
221
 
@@ -1,7 +1,7 @@
1
1
  # rbs_inline: enabled
2
2
  # frozen_string_literal: true
3
3
 
4
- require "etc"
4
+ require "concurrent/utility/processor_counter"
5
5
 
6
6
  module Rackup
7
7
  module Handler
@@ -42,7 +42,7 @@ module Rackup
42
42
  {
43
43
  "Host=HOST" => "Hostname to listen on (default: #{DEFAULT_OPTIONS[:Host]})",
44
44
  "Port=PORT" => "Port to listen on (default: #{DEFAULT_OPTIONS[:Port]})",
45
- "Workers=NUM" => "Number of worker processes (default: nprocessors)",
45
+ "Workers=NUM" => "Number of worker processes (default: available processor count)",
46
46
  "Ractors=NUM" => "Number of pipeline ractors per worker (default: 1)",
47
47
  "Threads=NUM" => "Number of threads per worker (default: 3)",
48
48
  "Config=PATH" => "Load additional configuration from PATH"
@@ -80,7 +80,7 @@ module Rackup
80
80
  end,
81
81
  socket_backlog: (config[:socket_backlog] || cli_defaults[:socket_backlog]).to_i,
82
82
  drain_accept_queue: config.key?(:drain_accept_queue) ? config[:drain_accept_queue] : cli_defaults[:drain_accept_queue],
83
- workers: (options[:Workers] || config[:workers] || Etc.nprocessors).to_i,
83
+ workers: (options[:Workers] || config[:workers] || Concurrent.available_processor_count).to_i,
84
84
  ractors: (options[:Ractors] || config[:ractors] || cli_defaults[:ractors]).to_i,
85
85
  threads: (options[:Threads] || config[:threads] || cli_defaults[:threads]).to_i,
86
86
  app: app
data/lib/raptor/cli.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # rbs_inline: enabled
2
2
  # frozen_string_literal: true
3
3
 
4
- require "etc"
4
+ require "concurrent/utility/processor_counter"
5
5
  require "json"
6
6
  require "optparse"
7
7
 
@@ -24,7 +24,7 @@ module Raptor
24
24
  # cli.run
25
25
  #
26
26
  class CLI
27
- DEFAULT_WORKER_COUNT = Etc.nprocessors
27
+ DEFAULT_WORKER_COUNT = Integer(Concurrent.available_processor_count)
28
28
 
29
29
  NESTED_OPTION_KEYS = [:connection, :http1, :http2].freeze
30
30
 
data/lib/raptor/server.rb CHANGED
@@ -9,9 +9,9 @@ require_relative "reuseport_bpf"
9
9
 
10
10
  module Raptor
11
11
  # Accepts client connections on TCP, Unix, and SSL listeners and
12
- # dispatches them into the request pipeline. Backs off from accepting
13
- # when the reactor backlog is high so an overloaded process leaves
14
- # connections for peers to absorb.
12
+ # dispatches them into the request pipeline. Yields between accepts
13
+ # when the thread pool queue exceeds the pool size so overloaded
14
+ # workers stay responsive to in-flight requests.
15
15
  #
16
16
  class Server
17
17
  HTTP_SCHEME = "http"
@@ -22,6 +22,7 @@ module Raptor
22
22
  DEFAULT_REMOTE_ADDR = "127.0.0.1"
23
23
  DEFAULT_SERVER_NAME = "localhost"
24
24
 
25
+ BACKPRESSURE_THRESHOLD_MULTIPLIER = 1.2
25
26
  MIN_BACKPRESSURE_THRESHOLD = 8
26
27
 
27
28
  # @rbs @binder: Binder
@@ -73,7 +74,7 @@ module Raptor
73
74
  Thread.new do
74
75
  Thread.current.name = "Server"
75
76
 
76
- backpressure_threshold = [(@thread_pool.size * 1.2).ceil, MIN_BACKPRESSURE_THRESHOLD].max
77
+ backpressure_threshold = [(@thread_pool.size * BACKPRESSURE_THRESHOLD_MULTIPLIER).ceil, MIN_BACKPRESSURE_THRESHOLD].max
77
78
 
78
79
  while @running.true?
79
80
  begin
@@ -85,6 +86,11 @@ module Raptor
85
86
  next unless ready_servers
86
87
  next if @reactor.backlog >= backpressure_threshold
87
88
 
89
+ if @thread_pool.queue_size > @thread_pool.size
90
+ Thread.pass
91
+ next
92
+ end
93
+
88
94
  ready_servers.each { |listener| accept_connection(listener) }
89
95
  end
90
96
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Raptor
5
- VERSION = "0.14.0"
5
+ VERSION = "0.15.0"
6
6
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Raptor
4
4
  # Accepts client connections on TCP, Unix, and SSL listeners and
5
- # dispatches them into the request pipeline. Backs off from accepting
6
- # when the reactor backlog is high so an overloaded process leaves
7
- # connections for peers to absorb.
5
+ # dispatches them into the request pipeline. Yields between accepts
6
+ # when the thread pool queue exceeds the pool size so overloaded
7
+ # workers stay responsive to in-flight requests.
8
8
  class Server
9
9
  HTTP_SCHEME: ::String
10
10
 
@@ -16,27 +16,29 @@ module Raptor
16
16
 
17
17
  DEFAULT_SERVER_NAME: ::String
18
18
 
19
- MIN_BACKPRESSURE_THRESHOLD: ::Integer
19
+ BACKPRESSURE_THRESHOLD_MULTIPLIER: ::Float
20
20
 
21
- @running: AtomicBoolean
21
+ MIN_BACKPRESSURE_THRESHOLD: ::Integer
22
22
 
23
- @bpf_active: bool
23
+ @binder: Binder
24
24
 
25
- @drain_accept_queue: bool
25
+ @listeners: Array[TCPServer | UNIXServer | Binder::SslListener]
26
26
 
27
- @first_data_timeout: Integer
27
+ @reactor: Reactor
28
28
 
29
- @http2: Http2
29
+ @thread_pool: AtomicThreadPool
30
30
 
31
31
  @http1: Http1
32
32
 
33
- @thread_pool: AtomicThreadPool
33
+ @http2: Http2
34
34
 
35
- @reactor: Reactor
35
+ @first_data_timeout: Integer
36
36
 
37
- @listeners: Array[TCPServer | UNIXServer | Binder::SslListener]
37
+ @drain_accept_queue: bool
38
38
 
39
- @binder: Binder
39
+ @bpf_active: bool
40
+
41
+ @running: AtomicBoolean
40
42
 
41
43
  # Creates a new Server instance.
42
44
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raptor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: concurrent-ruby
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: ractor-pool
56
70
  requirement: !ruby/object:Gem::Requirement