raptor 0.1.0 → 0.3.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: 3d7a708bc4146a30fa1b03dbca5404dff1173c0b39348de9e3ae3a0330907063
4
- data.tar.gz: 5db6986d6c9ca964873ec42f408d13087755b59c2517e7f942b8d2421ab88133
3
+ metadata.gz: 7c247b4c221d0f19ff1383ca0a4092a54bc12cd2f945655fa24e1ab49745eb70
4
+ data.tar.gz: cb932a7a048d87eb0913f8c0dbe52750ea4e525e4c4dc32e5fcc531af0daaf41
5
5
  SHA512:
6
- metadata.gz: 8e163192ea38a0fa01852fe9c8ea31b82ef90b7fdd8f1b2b65b04e73f49e10792ba01225644d23104b42a7c1d3f07f47c273123a36ff4e77ef6259e695f967b8
7
- data.tar.gz: 90b1789fff941091795ebac8092deb97d6cdc51f915e38399bc782c05d6a101866f651d42a6183c948b99c25ab70532357c70d1090a68609cda0f6f3be46de8b
6
+ metadata.gz: d8e1543054862e8a87c7261884348456da13d4432c3424687b74f506b884283d2773068b6beb10cbe9e209e969b98a918720861b3d0e05bc04d930ba38ec1921
7
+ data.tar.gz: f215cc4a92af86542d49f2c4772f0a65af09988779bbb1c44512ab075d4fb5c0758cb51d9765a0ee9d08fe340d31b42cea1610b021c443818fe40fbdcea63b93
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2026-05-25
4
+
5
+ - Load cluster options from a Ruby config file via `--config`
6
+ - Replace workers one at a time on `SIGUSR2` (phased restart)
7
+ - Invoke `:on_error` callback with `(env, exception)` when the Rack app raises
8
+ - Spool request bodies larger than `--body-spool-threshold` to a tempfile
9
+ - Reject HTTP/1.1 requests larger than `--max-body-size` with a 413 response
10
+ - Write pidfile via `--pidfile`
11
+ - Set `SO_REUSEPORT` on TCP listeners where supported
12
+
13
+ ## [0.2.0] - 2026-05-25
14
+
15
+ - Add Rack handler for booting via `rackup` or `rails server`
16
+ - Replace the HTTP/2 per-connection write mutex with a lock-free writer
17
+ - Parse the first HTTP/1.1 request inline on the server thread
18
+
3
19
  ## [0.1.0] - 2026-05-22
4
20
 
5
21
  - Initial release
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Raptor
2
2
 
3
- Raptor is a high-performance, multi-threaded, multi-process Ruby web server that leverages Ractors for parallel HTTP/1.1
4
- and HTTP/2 request processing, native C extensions for HTTP parsing and HPACK compression, and NIO for non-blocking I/O.
3
+ Raptor is a high-performance, preloading, multi-process, multi-threaded Ruby 4+ web server implementing Rack 3+,
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.
5
6
 
6
7
  ## Installation
7
8
 
@@ -30,9 +31,9 @@ run proc { |_env| [200, { "content-type" => "text/plain" }, ["Hello, World!"]] }
30
31
  ```
31
32
  > bundle exec raptor -t 3 -w 4 hello_world.ru
32
33
  Raptor Cluster initializing:
33
- ├─ Version: 0.1.0
34
+ ├─ Version: 0.3.0
34
35
  ├─ Ruby Version: ruby 4.0.4 (2026-05-12 revision b89eb1bcbf) +YJIT +PRISM [arm64-darwin23]
35
- ├─ Master PID: 71052
36
+ ├─ Master PID: 31504
36
37
  │ └─ 4 worker processes
37
38
  │ ├─ 1 server thread
38
39
  │ ├─ 1 reactor thread
@@ -41,10 +42,10 @@ Raptor Cluster initializing:
41
42
  │ ├─ 3 worker threads
42
43
  │ └─ 1 stats thread
43
44
  └─ Listening on 0.0.0.0:9292
44
- [71054] Worker 0 booted
45
- [71055] Worker 1 booted
46
- [71056] Worker 2 booted
47
- [71057] Worker 3 booted
45
+ [31506] Worker 0 booted
46
+ [31507] Worker 1 booted
47
+ [31508] Worker 2 booted
48
+ [31509] Worker 3 booted
48
49
  ```
49
50
 
50
51
  ```
@@ -52,15 +53,22 @@ Raptor Cluster initializing:
52
53
  Hello, World!%
53
54
  ```
54
55
 
55
- ## Benchmarks
56
+ Also works with `rackup` and `rails server`:
56
57
 
57
- Raptor 0.1.0 vs Puma 8.0.1:
58
+ ```
59
+ > bundle exec rackup -s raptor hello_world.ru
60
+ > bundle exec rails server -u raptor
61
+ ```
62
+
63
+ ## (Micro) Benchmarks
64
+
65
+ Raptor 0.3.0 vs Puma 8.0.1:
58
66
 
59
67
  | Protocol | Raptor | Puma |
60
68
  | --------------------- | ------------ | ------------ |
61
- | HTTP/1.1 | 17.5k req/s | 20.8k req/s |
62
- | HTTP/1.1 (keep-alive) | 61.2k req/s | 45.4k req/s |
63
- | HTTP/2 | 23.4k req/s | N/A |
69
+ | HTTP/1.1 | 20.3k req/s | 20.8k req/s |
70
+ | HTTP/1.1 (keep-alive) | 60.9k req/s | 45.4k req/s |
71
+ | HTTP/2 | 22.9k req/s | N/A |
64
72
 
65
73
  > Ruby 4.0.4 +YJIT, macOS Apple Silicon. 4 workers, 3 threads, 12 concurrent connections.
66
74
 
@@ -0,0 +1,102 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ require "etc"
5
+
6
+ module Rackup
7
+ module Handler
8
+ # Rack handler for booting Raptor through Rackup, `rails server`, or any
9
+ # other host that follows the Rack handler protocol.
10
+ #
11
+ module Raptor
12
+ DEFAULT_OPTIONS = {
13
+ Host: "0.0.0.0",
14
+ Port: 9292
15
+ }.freeze
16
+
17
+ # Boots a Raptor cluster serving the given Rack application.
18
+ #
19
+ # @param app [#call] the Rack application to serve
20
+ # @param options [Hash] handler options provided by Rackup or the host
21
+ # @yield [cluster] the cluster instance, before it starts running
22
+ # @return [void]
23
+ #
24
+ # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, **untyped options) { (::Raptor::Cluster) -> void } -> void
25
+ def self.run(app, **options)
26
+ require_relative "../../raptor/cli"
27
+ require_relative "../../raptor/cluster"
28
+
29
+ cluster = ::Raptor::Cluster.new(build_cluster_options(app, options))
30
+
31
+ yield cluster if block_given?
32
+
33
+ cluster.run
34
+ end
35
+
36
+ # Returns the handler-specific options surfaced by `rackup --help`.
37
+ #
38
+ # @return [Hash{String => String}] option spec to description mapping
39
+ #
40
+ # @rbs () -> Hash[String, String]
41
+ def self.valid_options
42
+ {
43
+ "Host=HOST" => "Hostname to listen on (default: #{DEFAULT_OPTIONS[:Host]})",
44
+ "Port=PORT" => "Port to listen on (default: #{DEFAULT_OPTIONS[:Port]})",
45
+ "Threads=NUM" => "Number of threads per worker (default: 3)",
46
+ "Ractors=NUM" => "Number of pipeline ractors per worker (default: 1)",
47
+ "Workers=NUM" => "Number of worker processes (default: nprocessors)",
48
+ "Config=PATH" => "Load additional configuration from PATH"
49
+ }
50
+ end
51
+
52
+ # Builds a Raptor cluster options hash from Rack handler options.
53
+ #
54
+ # Options not explicitly supplied by the user (per the `:user_supplied_options`
55
+ # key) are treated as host-provided defaults.
56
+ #
57
+ # @param app [#call] the Rack application to serve
58
+ # @param options [Hash] handler options provided by Rackup or the host
59
+ # @return [Hash{Symbol => untyped}] cluster configuration
60
+ #
61
+ # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Hash[Symbol, untyped] options) -> Hash[Symbol, untyped]
62
+ def self.build_cluster_options(app, options)
63
+ defaults = DEFAULT_OPTIONS.dup
64
+
65
+ if user_supplied_options = options.delete(:user_supplied_options)
66
+ (options.keys - user_supplied_options).each do |key|
67
+ defaults[key] = options.delete(key)
68
+ end
69
+ end
70
+
71
+ cli_defaults = ::Raptor::CLI::DEFAULT_OPTIONS
72
+ config = options[:Config] ? ::Raptor::CLI.load_config_file(options[:Config]) : {}
73
+
74
+ binds = if options[:Host] || options[:Port]
75
+ host = options[:Host] || defaults[:Host]
76
+ port = options[:Port] || defaults[:Port]
77
+ ["tcp://#{host}:#{port}"]
78
+ else
79
+ config[:binds] || ["tcp://#{defaults[:Host]}:#{defaults[:Port]}"]
80
+ end
81
+
82
+ result = {
83
+ app: app,
84
+ binds: binds,
85
+ threads: (options[:Threads] || config[:threads] || cli_defaults[:threads]).to_i,
86
+ ractors: (options[:Ractors] || config[:ractors] || cli_defaults[:ractors]).to_i,
87
+ workers: (options[:Workers] || config[:workers] || Etc.nprocessors).to_i,
88
+ client: cli_defaults[:client].merge(config[:client] || {})
89
+ }
90
+
91
+ [:rackup, :on_error, :stats_file, :pidfile].each do |key|
92
+ result[key] = config[key] if config.key?(key)
93
+ end
94
+
95
+ result
96
+ end
97
+ private_class_method :build_cluster_options
98
+ end
99
+
100
+ register :raptor, Raptor
101
+ end
102
+ end
data/lib/raptor/binder.rb CHANGED
@@ -173,6 +173,7 @@ module Raptor
173
173
 
174
174
  tcp_server = TCPServer.new(host, port)
175
175
  tcp_server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
176
+ tcp_server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true) if Socket.const_defined?(:SO_REUSEPORT)
176
177
  tcp_server.listen SOCKET_BACKLOG
177
178
 
178
179
  [tcp_server]
data/lib/raptor/cli.rb CHANGED
@@ -32,14 +32,35 @@ module Raptor
32
32
  ractors: 1,
33
33
  workers: DEFAULT_WORKER_COUNT,
34
34
  rackup: "config.ru",
35
- stats_file: "tmp/raptor.json",
36
35
  client: {
37
36
  first_data_timeout: 30,
38
37
  chunk_data_timeout: 10,
39
38
  persistent_data_timeout: 65,
39
+ max_body_size: nil,
40
+ body_spool_threshold: 1024 * 1024,
40
41
  },
42
+ stats_file: "tmp/raptor.json",
43
+ pidfile: nil,
41
44
  }.freeze
42
45
 
46
+ # Loads a configuration file and returns the hash it evaluates to.
47
+ #
48
+ # The file is evaluated at the top level so constants like `Raptor::*` resolve
49
+ # the same as in a regular Ruby script. The final expression must be a Hash
50
+ # of cluster options (the same keys accepted by {Raptor::Cluster#initialize}).
51
+ #
52
+ # @param path [String] path to a Ruby file that evaluates to a Hash
53
+ # @return [Hash{Symbol => untyped}] cluster options
54
+ # @raise [ArgumentError] if the file does not evaluate to a Hash
55
+ #
56
+ # @rbs (String path) -> Hash[Symbol, untyped]
57
+ def self.load_config_file(path)
58
+ config = eval(File.read(path), TOPLEVEL_BINDING, path, 1)
59
+ raise ArgumentError, "Config file at #{path.inspect} must return a Hash, got #{config.class}" unless config.is_a?(Hash)
60
+
61
+ config
62
+ end
63
+
43
64
  # @rbs @command: Symbol
44
65
  # @rbs @options: Hash[Symbol, untyped]
45
66
  # @rbs @parser: OptionParser
@@ -70,6 +91,9 @@ module Raptor
70
91
  end
71
92
  @options = DEFAULT_OPTIONS.dup
72
93
  @options[:client] = @options[:client].dup
94
+
95
+ apply_config_file(extract_config_path(argv))
96
+
73
97
  @parser = create_parser
74
98
  @parser.parse!(argv)
75
99
 
@@ -111,6 +135,49 @@ module Raptor
111
135
  end
112
136
  end
113
137
 
138
+ # Scans argv for a `-c`/`--config` flag and returns the configured path.
139
+ #
140
+ # The pre-scan runs before the main OptionParser pass so the config file
141
+ # can be applied as a base layer that CLI args then override. All four
142
+ # OptionParser-accepted forms (`-c PATH`, `-cPATH`, `--config PATH`,
143
+ # `--config=PATH`) are recognized.
144
+ #
145
+ # @param argv [Array<String>] command-line arguments to scan
146
+ # @return [String, nil] the config path, or nil if no flag was supplied
147
+ #
148
+ # @rbs (Array[String] argv) -> String?
149
+ def extract_config_path(argv)
150
+ argv.each_with_index do |arg, i|
151
+ case arg
152
+ when "-c", "--config" then return argv[i + 1]
153
+ when /\A--config=(.*)\z/, /\A-c(.+)\z/ then return Regexp.last_match(1)
154
+ end
155
+ end
156
+ nil
157
+ end
158
+
159
+ # Loads a config file and merges it into `@options` over the defaults.
160
+ #
161
+ # Top-level keys replace defaults; the nested `:client` hash is merged
162
+ # key-by-key so a config file does not need to restate every client option.
163
+ #
164
+ # @param path [String, nil] path to the config file, or nil to no-op
165
+ # @return [void]
166
+ #
167
+ # @rbs (String? path) -> void
168
+ def apply_config_file(path)
169
+ return unless path
170
+
171
+ config = self.class.load_config_file(path)
172
+ config.each do |key, value|
173
+ if key == :client && value.is_a?(Hash)
174
+ @options[:client] = @options[:client].merge(value)
175
+ else
176
+ @options[key] = value
177
+ end
178
+ end
179
+ end
180
+
114
181
  # Creates the OptionParser instance with all supported command-line options.
115
182
  #
116
183
  # @return [OptionParser] configured option parser
@@ -120,6 +187,10 @@ module Raptor
120
187
  OptionParser.new do |opts|
121
188
  opts.banner = "Usage: raptor [options] [rackup file]"
122
189
 
190
+ opts.on("-c", "--config PATH", String, "Load configuration from PATH") do
191
+ # Loaded in #initialize before parsing so CLI args can override config values
192
+ end
193
+
123
194
  opts.on("-b", "--bind URI", String, "Bind address (default: tcp://0.0.0.0:9292)") do |bind|
124
195
  if @options[:binds] == DEFAULT_OPTIONS[:binds]
125
196
  @options[:binds] = [bind]
@@ -152,10 +223,22 @@ module Raptor
152
223
  @options[:client][:persistent_data_timeout] = timeout
153
224
  end
154
225
 
226
+ opts.on("--max-body-size BYTES", Integer, "Maximum request body size in bytes (default: unlimited)") do |bytes|
227
+ @options[:client][:max_body_size] = bytes
228
+ end
229
+
230
+ opts.on("--body-spool-threshold BYTES", Integer, "Spool request bodies larger than this to a tempfile (default: #{1024 * 1024})") do |bytes|
231
+ @options[:client][:body_spool_threshold] = bytes
232
+ end
233
+
155
234
  opts.on("--stats-file PATH", String, "Stats file path (default: tmp/raptor.json)") do |path|
156
235
  @options[:stats_file] = path
157
236
  end
158
237
 
238
+ opts.on("--pidfile PATH", String, "Pidfile path (default: none)") do |path|
239
+ @options[:pidfile] = path
240
+ end
241
+
159
242
  opts.on("--help", "Show this help") do
160
243
  puts opts
161
244
  exit
@@ -58,13 +58,17 @@ module Raptor
58
58
  # @rbs @ractor_count: Integer
59
59
  # @rbs @worker_count: Integer
60
60
  # @rbs @client_options: Hash[Symbol, Integer]
61
+ # @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
62
+ # @rbs @stats_file: String?
63
+ # @rbs @pidfile: String?
61
64
  # @rbs @binder: Binder
62
65
  # @rbs @server_port: Integer
63
66
  # @rbs @app: untyped
64
67
  # @rbs @shutdown: bool
65
68
  # @rbs @workers: Hash[Integer, Integer]
66
69
  # @rbs @stats: Stats
67
- # @rbs @stats_file: String?
70
+ # @rbs @phased_restart_requested: bool
71
+ # @rbs @phased_restarting: bool
68
72
 
69
73
  # Creates a new Cluster with the specified configuration.
70
74
  #
@@ -77,8 +81,12 @@ module Raptor
77
81
  # @option options [Integer] :ractors number of ractors per worker process
78
82
  # @option options [Integer] :workers number of worker processes
79
83
  # @option options [Array<String>] :binds array of bind URIs
84
+ # @option options [#call] :app pre-built Rack application
80
85
  # @option options [String] :rackup path to Rack configuration file
81
- # @option options [Hash] :client client timeout configuration
86
+ # @option options [Hash] :client client configuration
87
+ # @option options [#call] :on_error callback invoked with (env, exception) when the Rack app raises
88
+ # @option options [String, nil] :stats_file path to write per-worker stats JSON, or nil to disable
89
+ # @option options [String, nil] :pidfile path to write the master PID to, or nil to disable
82
90
  # @return [void]
83
91
  #
84
92
  # @rbs (Hash[Symbol, untyped] options) -> void
@@ -87,23 +95,28 @@ module Raptor
87
95
  @ractor_count = options[:ractors]
88
96
  @worker_count = options[:workers]
89
97
  @client_options = options[:client]
98
+ @on_error = options[:on_error]
99
+ @stats_file = options[:stats_file]
100
+ @pidfile = options[:pidfile]
90
101
 
91
102
  @binder = Binder.new(options[:binds])
92
103
  @server_port = @binder.server_port
93
- @app = Rack::Builder.parse_file(options[:rackup])
104
+ @app = options[:app] || Rack::Builder.parse_file(options[:rackup])
94
105
  log_initialization
95
106
 
96
107
  @shutdown = false
97
108
  @workers = {}
98
109
  @stats = Stats.new(@worker_count)
99
- @stats_file = options[:stats_file]
110
+ @phased_restart_requested = false
111
+ @phased_restarting = false
100
112
  end
101
113
 
102
114
  # Starts the multi-process cluster and manages worker processes.
103
115
  #
104
116
  # Forks the configured number of worker processes and monitors them,
105
117
  # automatically restarting any that exit unexpectedly. Handles graceful
106
- # shutdown via INT or TERM signals, and stats logging via USR1.
118
+ # shutdown via INT or TERM signals, stats logging via USR1, and phased
119
+ # restart via USR2.
107
120
  #
108
121
  # Each worker process includes:
109
122
  # - 1 server thread (continuously accepts connections with backpressure control)
@@ -120,6 +133,9 @@ module Raptor
120
133
  trap("INT") { shutdown }
121
134
  trap("TERM") { shutdown }
122
135
  trap("USR1") { log_stats }
136
+ trap("USR2") { @phased_restart_requested = true }
137
+
138
+ File.open(@pidfile, File::CREAT | File::EXCL | File::WRONLY) { |file| file.write(Process.pid.to_s) } if @pidfile
123
139
 
124
140
  @worker_count.times { |index| spawn_worker(index) }
125
141
 
@@ -132,29 +148,18 @@ module Raptor
132
148
  end
133
149
 
134
150
  until @shutdown
135
- begin
136
- pid, status = Process.wait2(-1, Process::WNOHANG)
137
- rescue Errno::ECHILD
138
- break
139
- end
151
+ break if reap_workers == :no_children
140
152
 
141
- if pid
142
- index = @workers.key(pid)
143
- @workers.delete(index)
153
+ perform_phased_restart if @phased_restart_requested && !@phased_restarting
144
154
 
145
- unless @shutdown
146
- warn "[#{Process.pid}] Restarting worker #{index} (#{pid}), #{exit_description(status)}"
147
- spawn_worker(index)
148
- end
149
- else
150
- sleep 0.1
151
- end
155
+ sleep 0.1
152
156
  end
153
157
 
154
158
  @workers.values.each { |pid| Process.kill("TERM", pid) rescue nil }
155
159
  @workers.values.each { |pid| Process.wait(pid) rescue nil }
156
160
  stats_file_thread&.join
157
161
  File.delete(@stats_file) rescue nil if @stats_file
162
+ File.delete(@pidfile) rescue nil if @pidfile
158
163
  @stats.unmap
159
164
  end
160
165
 
@@ -181,6 +186,66 @@ module Raptor
181
186
  @workers[index] = pid
182
187
  end
183
188
 
189
+ # Reaps any worker processes that have exited, respawning each one
190
+ # unless the cluster is shutting down.
191
+ #
192
+ # @return [Symbol] :no_children when there are no remaining children, otherwise :reaped
193
+ #
194
+ # @rbs () -> Symbol
195
+ def reap_workers
196
+ loop do
197
+ pid, status = Process.wait2(-1, Process::WNOHANG)
198
+ return :reaped unless pid
199
+
200
+ index = @workers.key(pid)
201
+ @workers.delete(index)
202
+
203
+ unless @shutdown
204
+ warn "[#{Process.pid}] Restarting worker #{index} (#{pid}), #{exit_description(status)}"
205
+ spawn_worker(index)
206
+ end
207
+ end
208
+ rescue Errno::ECHILD
209
+ :no_children
210
+ end
211
+
212
+ # Replaces each worker process one at a time, waiting for the new
213
+ # worker to boot before moving on to the next. Triggered by SIGUSR2.
214
+ #
215
+ # @return [void]
216
+ #
217
+ # @rbs () -> void
218
+ def perform_phased_restart
219
+ @phased_restart_requested = false
220
+ @phased_restarting = true
221
+ puts "[#{Process.pid}] Phased restart starting"
222
+
223
+ begin
224
+ @workers.keys.sort.each do |index|
225
+ return if @shutdown
226
+
227
+ target_pid = @workers[index]
228
+ next unless target_pid
229
+
230
+ Process.kill("TERM", target_pid) rescue nil
231
+
232
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 60
233
+ until @shutdown
234
+ reap_workers
235
+ current = @workers[index]
236
+ break if current && current != target_pid && @stats.all[index][:booted]
237
+ break if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
238
+
239
+ sleep 0.1
240
+ end
241
+ end
242
+
243
+ puts "[#{Process.pid}] Phased restart complete"
244
+ ensure
245
+ @phased_restarting = false
246
+ end
247
+ end
248
+
184
249
  # Runs the full server stack inside a worker process.
185
250
  #
186
251
  # Sets up and coordinates the reactor, server, ractor pool, thread pool,
@@ -216,8 +281,8 @@ module Raptor
216
281
  @app.call(env)
217
282
  }
218
283
  thread_pool = AtomicThreadPool.new(name: "Raptor Workers", size: @thread_count)
219
- request = Request.new(counting_app, @server_port)
220
- http2 = Http2.new(counting_app, @server_port)
284
+ request = Request.new(counting_app, @server_port, client_options: @client_options, on_error: @on_error)
285
+ http2 = Http2.new(counting_app, @server_port, on_error: @on_error)
221
286
  ractor_pool = RactorPool.new(
222
287
  name: "Raptor Pipeline Workers",
223
288
  size: @ractor_count,
@@ -233,7 +298,7 @@ module Raptor
233
298
  reactor = Reactor.new(thread_pool, ractor_pool, client_options: @client_options)
234
299
  reactor_thread = reactor.run
235
300
 
236
- server = Server.new(@binder, reactor, thread_pool)
301
+ server = Server.new(@binder, reactor, thread_pool, request)
237
302
  server_thread = server.run
238
303
 
239
304
  puts "[#{Process.pid}] Worker #{index} booted"