raptor 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4a7acc08848f71017602d3abfb38b7d352a48d7bc7929e54f3a693dc3a0fe83
4
- data.tar.gz: 1ee020abeb67db2a2eba6ff64f59054bf2e91447bd3996464f5a3279c3d3e748
3
+ metadata.gz: 6dcf0e8beb3e5b74822eff407c51fbb0e83445ab54f75c37dad2cab2f9113ec5
4
+ data.tar.gz: 9131a6153b10e93e2e0b717355645fe485a1db0af52c3472f07a35fa4f95d64f
5
5
  SHA512:
6
- metadata.gz: b5b25843f29afe7e47a90c85f48ba5f9d06e7f5dc819d24595821940f8207fa46054208f38601a455271d026246bfd32144329782f1a842f44cd500bf3d10bbf
7
- data.tar.gz: 5c1f9f18fbf946a8e4a648bed675f83ba7d71bdc9a85354b5df1ccc6ba39bd65b5740c31e1ffd11f4f88960b52aaee0ce2b46747fac52907bae6b6598f4e390e
6
+ metadata.gz: 8a020f0e5c42eb4755a69c790f891aa334369d2ec58c063516893a53785574290feca84ebdd9d072b11601129f720eb2d82f52a7abbd7e0aa7bf79b17c57705c
7
+ data.tar.gz: 3b90235078097337dcade23681c4b3ae638d04094a40a5c21e81f8aac127bfa547dd8d07e7dac1adda539299ab62c3883d15d84a692942da2a1b69eed7946035
@@ -1,36 +1,94 @@
1
1
  steps:
2
- - label: ":ruby: Ruby 4.0"
3
- image: "ruby:4.0"
4
- commands:
5
- - ruby -v
6
- - bundle install
7
- - bundle exec rake ci
2
+ - group: ":linux: Linux"
3
+ key: linux
4
+ steps:
5
+ - label: ":ruby: Ruby 4.0"
6
+ image: "ruby:4.0"
7
+ commands:
8
+ - ruby -v
9
+ - bundle install
10
+ - bundle exec rake ci
8
11
 
9
- - label: ":ruby: Ruby 4.0 +YJIT"
10
- image: "ruby:4.0"
11
- env:
12
- RUBY_YJIT_ENABLE: "1"
13
- commands:
14
- - ruby -v
15
- - bundle install
16
- - bundle exec rake ci
12
+ - label: ":ruby: Ruby 4.0 +YJIT"
13
+ image: "ruby:4.0"
14
+ env:
15
+ RUBY_YJIT_ENABLE: "1"
16
+ commands:
17
+ - ruby -v
18
+ - bundle install
19
+ - bundle exec rake ci
17
20
 
18
- - label: ":ruby: Ruby head"
19
- soft_fail: true
20
- image: "rubylang/ruby:master-dev"
21
- commands:
22
- - apt-get update && apt-get install -y libyaml-dev
23
- - ruby -v
24
- - bundle install
25
- - bundle exec rake ci
21
+ - label: ":ruby: Ruby head"
22
+ soft_fail: true
23
+ image: "rubylang/ruby:master-dev"
24
+ commands:
25
+ - apt-get update && apt-get install -y libyaml-dev
26
+ - ruby -v
27
+ - bundle install
28
+ - bundle exec rake ci
26
29
 
27
- - label: ":ruby: Ruby head +YJIT"
28
- soft_fail: true
29
- image: "rubylang/ruby:master-dev"
30
- env:
31
- RUBY_YJIT_ENABLE: "1"
32
- commands:
33
- - apt-get update && apt-get install -y libyaml-dev
34
- - ruby -v
35
- - bundle install
36
- - bundle exec rake ci
30
+ - label: ":ruby: Ruby head +YJIT"
31
+ soft_fail: true
32
+ image: "rubylang/ruby:master-dev"
33
+ env:
34
+ RUBY_YJIT_ENABLE: "1"
35
+ commands:
36
+ - apt-get update && apt-get install -y libyaml-dev
37
+ - ruby -v
38
+ - bundle install
39
+ - bundle exec rake ci
40
+
41
+ - group: ":mac: macOS"
42
+ key: macos
43
+ steps:
44
+ - label: ":ruby: Ruby 4.0"
45
+ agents:
46
+ queue: macos
47
+ commands:
48
+ - brew install --quiet ruby@4.0
49
+ - export PATH="$(brew --prefix ruby@4.0)/bin:$PATH"
50
+ - bundle config set --local path .bundle
51
+ - ruby -v
52
+ - bundle install
53
+ - bundle exec rake ci
54
+
55
+ - label: ":ruby: Ruby 4.0 +YJIT"
56
+ agents:
57
+ queue: macos
58
+ env:
59
+ RUBY_YJIT_ENABLE: "1"
60
+ commands:
61
+ - brew install --quiet ruby@4.0
62
+ - export PATH="$(brew --prefix ruby@4.0)/bin:$PATH"
63
+ - bundle config set --local path .bundle
64
+ - ruby -v
65
+ - bundle install
66
+ - bundle exec rake ci
67
+
68
+ - label: ":ruby: Ruby head"
69
+ soft_fail: true
70
+ agents:
71
+ queue: macos
72
+ commands:
73
+ - brew unlink ruby
74
+ - brew install --HEAD --quiet ruby
75
+ - export PATH="$(brew --prefix ruby)/bin:$PATH"
76
+ - bundle config set --local path .bundle
77
+ - ruby -v
78
+ - bundle install
79
+ - bundle exec rake ci
80
+
81
+ - label: ":ruby: Ruby head +YJIT"
82
+ soft_fail: true
83
+ agents:
84
+ queue: macos
85
+ env:
86
+ RUBY_YJIT_ENABLE: "1"
87
+ commands:
88
+ - brew unlink ruby
89
+ - brew install --HEAD --quiet ruby
90
+ - export PATH="$(brew --prefix ruby)/bin:$PATH"
91
+ - bundle config set --local path .bundle
92
+ - ruby -v
93
+ - bundle install
94
+ - bundle exec rake ci
data/.dockerignore ADDED
@@ -0,0 +1,5 @@
1
+ /.bundle
2
+ /.git
3
+ /docs
4
+ /sig/generated
5
+ /tmp
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.9.0] - 2026-07-07
4
+
5
+ - Reuse a per-thread response buffer for status lines and headers
6
+ - Pin each worker to a distinct CPU when workers fit 1:1
7
+ - Batch response header and body writes into a single `writev(2)` syscall
8
+ - Close the binder as the last step of graceful shutdown
9
+ - Lower the backpressure floor for tighter load balancing on small pools
10
+ - Add load-aware `SO_REUSEPORT` routing on Linux via an attached BPF program
11
+ - Reuse per-thread read buffers across HTTP/1.1 requests
12
+ - Preserve the first `--bind` when it equals the default
13
+
14
+ ## [0.8.0] - 2026-07-02
15
+
16
+ - Add systemd `LISTEN_FDS` socket activation and `sd_notify` lifecycle messages
17
+ - Add hot restart on `SIGUSR2`, inheriting listening sockets across the re-exec
18
+ - Drop `SIGUSR1` stats logging and move phased restart from `SIGUSR2` to `SIGUSR1`
19
+ - Add `chdir` and `environment` for Rack app loading, with fallback to `RAILS_ENV` and `RACK_ENV`
20
+ - Add `access_log_file` for Common Log Format access logging, reopened on `SIGHUP`
21
+ - Add `stdout_file` and `stderr_file` for redirecting stdout/stderr, reopened on `SIGHUP`
22
+ - Add `drain_accept_queue` for dispatching every queued connection on shutdown
23
+ - Add `worker_drain_timeout` for force-killing hung app threads during worker shutdown
24
+ - Reject `Content-Length` values containing non-digit characters with 400
25
+ - Populate `SERVER_SOFTWARE` and `HTTP_VERSION` in the Rack env
26
+ - Honour `X-Forwarded-Proto`, `X-Forwarded-Scheme`, and `X-Forwarded-Ssl` from upstream proxies
27
+ - Split newline-joined response header values into separate header lines
28
+ - Reject excessive chunked framing overhead with 400 (slow-trickle attack guard)
29
+ - Reject ambiguous request framing (`Transfer-Encoding` + `Content-Length`, or `chunked` not the final encoding) with 400
30
+ - Send `100 Continue` when an HTTP/1.1 client sends `Expect: 100-continue`
31
+ - Add new configuration options and split `client:` into protocol-scoped namespaces
32
+
3
33
  ## [0.7.0] - 2026-06-12
4
34
 
5
35
  - Eagerly consume back-to-back HTTP/2 frame batches in the pipeline collector
data/Dockerfile ADDED
@@ -0,0 +1,28 @@
1
+ FROM golang:bookworm AS hey-builder
2
+ RUN CGO_ENABLED=0 go install github.com/rakyll/hey@latest
3
+
4
+ FROM ruby:latest
5
+
6
+ ENV DEBIAN_FRONTEND=noninteractive
7
+ ENV LANG=C.UTF-8
8
+ ENV LC_ALL=C.UTF-8
9
+ ENV RUBY_YJIT_ENABLE=1
10
+
11
+ RUN apt-get update && apt-get install -y --no-install-recommends \
12
+ clang \
13
+ libbpf-dev \
14
+ libelf-dev \
15
+ linux-libc-dev \
16
+ nghttp2 \
17
+ openssl \
18
+ && rm -rf /var/lib/apt/lists/*
19
+
20
+ COPY --from=hey-builder /go/bin/hey /usr/local/bin/hey
21
+
22
+ ENV BUNDLE_PATH=/workspace/.bundle
23
+ ENV BUNDLE_APP_CONFIG=/workspace/.bundle
24
+ ENV PATH=/workspace/bin:${PATH}
25
+
26
+ WORKDIR /workspace
27
+
28
+ CMD ["bash"]
data/README.md CHANGED
@@ -1,8 +1,14 @@
1
1
  # Raptor
2
2
 
3
- Raptor is a high-performance, preloading, multi-process, multi-threaded Ruby 4+ web server implementing Rack 3.2+,
4
- leveraging Ractors for parallel HTTP/1.1 and HTTP/2 request processing, native C extensions for HTTP parsing and HPACK
5
- compression, and NIO for non-blocking I/O.
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
5
+ implement HPACK compression.
6
+
7
+ > [!NOTE]
8
+ > **Your application does not need to be Ractor-safe.** Ractors handle protocol-level work only; your Rack application
9
+ > is invoked on a thread pool, so any thread-safe Rack app (including Rails) works as-is.
10
+
11
+ Reference documentation is published at <https://joshuay03.github.io/raptor>.
6
12
 
7
13
  ## Installation
8
14
 
@@ -30,22 +36,23 @@ run proc { |_env| [200, { "content-type" => "text/plain" }, ["Hello, World!"]] }
30
36
 
31
37
  ```
32
38
  > bundle exec raptor -w 4 -t 3 hello_world.ru
33
- [Raptor 91348|main|main] Cluster initializing:
34
- [Raptor 91348|main|main] ├─ Version: 0.6.0
35
- [Raptor 91348|main|main] ├─ Ruby Version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
36
- [Raptor 91348|main|main] ├─ Master PID: 91348
37
- [Raptor 91348|main|main] │ └─ 4 worker processes
38
- [Raptor 91348|main|main] │ ├─ 1 server thread
39
- [Raptor 91348|main|main] │ ├─ 1 reactor thread
40
- [Raptor 91348|main|main] │ ├─ 1 pipeline ractor
41
- [Raptor 91348|main|main] │ ├─ 1 pipeline collector thread
42
- [Raptor 91348|main|main] │ ├─ 3 worker threads
43
- [Raptor 91348|main|main] │ └─ 1 stats thread
44
- [Raptor 91348|main|main] └─ Listening on 0.0.0.0:9292
45
- [Raptor 91350|main|main] Worker 0 booted
46
- [Raptor 91351|main|main] Worker 1 booted
47
- [Raptor 91352|main|main] Worker 2 booted
48
- [Raptor 91353|main|main] Worker 3 booted
39
+ [Raptor 76577|Main|Main] Cluster initializing:
40
+ [Raptor 76577|Main|Main] ├─ Version: 0.9.0
41
+ [Raptor 76577|Main|Main] ├─ Ruby Version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
42
+ [Raptor 76577|Main|Main] ├─ Environment: development
43
+ [Raptor 76577|Main|Main] ├─ Master PID: 76577
44
+ [Raptor 76577|Main|Main] │ └─ 4 worker processes
45
+ [Raptor 76577|Main|Main] │ ├─ 1 server thread
46
+ [Raptor 76577|Main|Main] │ ├─ 1 reactor thread
47
+ [Raptor 76577|Main|Main] │ ├─ 1 pipeline ractor
48
+ [Raptor 76577|Main|Main] │ ├─ 1 pipeline collector thread
49
+ [Raptor 76577|Main|Main] │ ├─ 3 worker threads
50
+ [Raptor 76577|Main|Main] └─ 1 stats thread
51
+ [Raptor 76577|Main|Main] └─ Listening on 0.0.0.0:9292
52
+ [Raptor 76579|Main|Main] Worker 0 booted
53
+ [Raptor 76580|Main|Main] Worker 1 booted
54
+ [Raptor 76581|Main|Main] Worker 2 booted
55
+ [Raptor 76582|Main|Main] Worker 3 booted
49
56
  ```
50
57
 
51
58
  ```
@@ -60,18 +67,139 @@ Also works with `rackup` and `rails server`:
60
67
  > bundle exec rails server -u raptor
61
68
  ```
62
69
 
70
+ ## Configuration
71
+
72
+ Raptor accepts configuration via command-line flags, a Ruby config file, or both (CLI flags override config file
73
+ values). Run `bundle exec raptor --help` for the full flag list.
74
+
75
+ The config file is a Ruby file that evaluates to a hash of options. By default Raptor loads `raptor.rb` then
76
+ `config/raptor.rb` from the working directory; pass `-c PATH` to point at a specific file. Settings are nested under
77
+ `connection:` (shared across protocols), `http1:` (HTTP/1.1-specific), and `http2:` (HTTP/2-specific).
78
+
79
+ ```ruby
80
+ # raptor.rb
81
+
82
+ {
83
+ binds: ["tcp://0.0.0.0:9292"],
84
+ socket_backlog: 1024,
85
+ drain_accept_queue: false,
86
+ workers: 4,
87
+ ractors: 1,
88
+ threads: 3,
89
+ chdir: nil,
90
+ environment: nil,
91
+ connection: {
92
+ first_data_timeout: 30,
93
+ chunk_data_timeout: 10,
94
+ write_timeout: 5,
95
+ max_body_size: nil,
96
+ body_spool_threshold: 1024 * 1024,
97
+ },
98
+ http1: {
99
+ persistent_data_timeout: 65,
100
+ max_keepalive_requests: 100,
101
+ },
102
+ http2: {
103
+ max_concurrent_streams: 100,
104
+ },
105
+ worker_boot_timeout: 60,
106
+ worker_timeout: 60,
107
+ worker_drain_timeout: 25,
108
+ worker_shutdown_timeout: 30,
109
+ stats_file: "tmp/raptor.json",
110
+ pid_file: nil,
111
+ stdout_file: nil,
112
+ stderr_file: nil,
113
+ access_log_file: nil,
114
+ }
115
+ ```
116
+
117
+ ## Bindings
118
+
119
+ Raptor accepts multiple `binds:` URIs across three schemes.
120
+
121
+ - `tcp://host:port` for TCP. Host can be a specific IP, `0.0.0.0` / `[::]`, or `localhost` (expanded to both IPv4 and
122
+ IPv6 loopback addresses).
123
+ - `unix:///path/to/socket` for a Unix domain socket. Stale sockets left by crashed processes are cleaned up
124
+ automatically.
125
+ - `ssl://host:port?cert=/path/to.crt&key=/path/to.key` for TLS. HTTP/1.1 and HTTP/2 are negotiated via ALPN.
126
+
127
+ Multiple binds can be combined freely.
128
+
129
+ ## Signals
130
+
131
+ Send to the master process.
132
+
133
+ | Signal | Effect |
134
+ | ------ | ----------------------------------------------------------- |
135
+ | `INT` | Graceful shutdown |
136
+ | `TERM` | Graceful shutdown |
137
+ | `HUP` | Reopen `stdout_file`, `stderr_file`, and `access_log_file` |
138
+ | `USR1` | Phased restart (rolling worker replacement) |
139
+ | `USR2` | Hot restart (re-exec master, inheriting listening sockets) |
140
+
141
+ ## Restarts
142
+
143
+ - **Phased restart** (`USR1`) replaces workers one at a time, waiting for each new worker to boot before retiring the
144
+ previous one. The master process keeps running, so existing workers continue serving until they are individually
145
+ replaced. Use to pick up code changes that don't affect the master's boot path.
146
+ - **Hot restart** (`USR2`) re-execs the master process with its original command line, inheriting the listening sockets
147
+ so accepted connections continue to be served across the swap. The successor master re-runs initialization from
148
+ scratch. Use to pick up changes that affect master-level state (config layout, dependency upgrades, Raptor itself).
149
+
150
+ ## systemd
151
+
152
+ Raptor implements socket activation (`LISTEN_FDS`) and `sd_notify`, so it integrates cleanly with `Type=notify` units.
153
+ When the socket unit is active, systemd hands the pre-bound listening file descriptors to Raptor, which serves them in
154
+ place of `binds:`. `READY=1`, `STOPPING=1`, and `RELOADING=1` lifecycle messages are emitted automatically.
155
+
156
+ ```ini
157
+ # /etc/systemd/system/myapp.socket
158
+ [Socket]
159
+ ListenStream=0.0.0.0:9292
160
+
161
+ [Install]
162
+ WantedBy=sockets.target
163
+ ```
164
+
165
+ ```ini
166
+ # /etc/systemd/system/myapp.service
167
+ [Service]
168
+ Type=notify
169
+ WorkingDirectory=/srv/myapp
170
+ ExecStart=/usr/bin/bundle exec raptor
171
+ ExecReload=/bin/kill -USR2 $MAINPID
172
+ KillMode=mixed
173
+ ```
174
+
175
+ ## Stats
176
+
177
+ Each worker writes per-worker stats (request count, busy threads, backlog, last check-in) to shared memory and to a
178
+ JSON file (default `tmp/raptor.json`; set via `stats_file`).
179
+
180
+ ```
181
+ > bundle exec raptor stats
182
+ Master PID: 91348
183
+ Worker 0 (phase 0): pid=91350, requests=1234, busy=2/3, backlog=0, booted, last_checkin=10:42:01
184
+ Worker 1 (phase 0): pid=91351, requests=1199, busy=1/3, backlog=0, booted, last_checkin=10:42:01
185
+ ...
186
+ ```
187
+
63
188
  ## (Micro) Benchmarks
64
189
 
65
- Raptor 0.6.0 vs Puma 8.0.2:
190
+ Raptor 0.9.0 vs Puma 8.0.2, median of 3 runs across two workload profiles: IO-bound (sleep for a random 5-50ms then return small JSON) and CPU-bound (serialise a JSON array of 20-200 items).
66
191
 
67
- | Protocol | Raptor | Puma |
68
- | --------------------- | ----------- | ----------- |
69
- | HTTP/1.1 | 17.9k req/s | 16.8k req/s |
70
- | HTTP/1.1 (keep-alive) | 60k req/s | 29.6k req/s |
71
- | HTTP/2 | 57.2k req/s | N/A |
192
+ | Protocol | Workload | Raptor | Puma | +/- vs Puma |
193
+ | --------------------- | -------- | ----------- | ---------- | ----------- |
194
+ | HTTP/1.1 | IO | 0.4k req/s | 0.4k req/s | +3.4% |
195
+ | HTTP/1.1 | CPU | 11.5k req/s | 9k req/s | +27.6% |
196
+ | HTTP/1.1 (keep-alive) | IO | 0.4k req/s | 0.4k req/s | +1.5% |
197
+ | HTTP/1.1 (keep-alive) | CPU | 28.8k req/s | 26k req/s | +10.9% |
198
+ | HTTP/2 | IO | 0.3k req/s | N/A | - |
199
+ | HTTP/2 | CPU | 29.6k req/s | N/A | - |
72
200
 
73
- > ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
74
- > 4 workers, 3 threads, 12 concurrent connections
201
+ > ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [aarch64-linux]
202
+ > 4 workers, 3 threads, 24 concurrent connections
75
203
 
76
204
  See [bin/benchmark](bin/benchmark) for more details.
77
205
 
@@ -81,6 +209,10 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
81
209
  extensions and run the tests. You can also run `bin/console` for an interactive prompt that will allow you to
82
210
  experiment.
83
211
 
212
+ On macOS (or any non-Linux host), `bin/dev` builds and drops you into a Docker image with Ruby and the required Linux
213
+ toolchain preinstalled, mounting the repo at `/workspace`. Run `bin/dev` for an interactive shell, or
214
+ `bin/dev <command>` for one-off commands like `bin/dev bundle exec rake` or `bin/dev bin/benchmark`.
215
+
84
216
  ## Contributing
85
217
 
86
218
  Bug reports and pull requests are welcome on GitHub at https://github.com/joshuay03/raptor. This project is intended to
data/Rakefile CHANGED
@@ -16,6 +16,24 @@ Rake::ExtensionTask.new("raptor_http2", GEMSPEC) do |ext|
16
16
  ext.lib_dir = "lib/raptor"
17
17
  end
18
18
 
19
+ Rake::ExtensionTask.new("raptor_native", GEMSPEC) do |ext|
20
+ ext.lib_dir = "lib/raptor"
21
+ end
22
+
23
+ namespace :bpf do
24
+ task :compile do
25
+ if RUBY_PLATFORM.include?("linux")
26
+ arch = `uname -m`.chomp
27
+ Dir["ext/raptor_bpf/*.bpf.c"].each do |source|
28
+ object = source.sub(/\.c\z/, ".o")
29
+ sh "clang -O2 -g -target bpf -I/usr/include/#{arch}-linux-gnu -c #{source} -o #{object}"
30
+ end
31
+ else
32
+ puts "Skipping bpf:compile on #{RUBY_PLATFORM}"
33
+ end
34
+ end
35
+ end
36
+
19
37
  namespace :rbs do
20
38
  task :generate do
21
39
  puts
@@ -23,6 +41,6 @@ namespace :rbs do
23
41
  end
24
42
  end
25
43
 
26
- task default: %i[clobber compile rbs:generate test]
44
+ task default: %i[clobber compile bpf:compile rbs:generate test]
27
45
  task build: %i[clobber compile rbs:generate]
28
46
  task ci: %i[clobber compile test]