raptor 0.2.0 → 0.4.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: 8391f9399db2e88dab106ad995a34060e6ae07f34c84825bfdcb066cd129dad4
4
- data.tar.gz: 25cb3658e2aded284f302b84a1ee91d67344a5b5dd36c6c0e854381cfb4df048
3
+ metadata.gz: a9d82dc0869e270278088d312d03a6ed914e494da5fb6b7bf6e41c03257ddf41
4
+ data.tar.gz: c113ab336b5ad9e5e72095e79af4702b5adb0efb46117ba43b6879fe2e4a728c
5
5
  SHA512:
6
- metadata.gz: e398237ccb391aff663c4d75e5470c0e4e0e6f6b273e26bface4cd6793691c3297053ab0a5d744deac8fd087c4f7dec0beb8426d8a762d3a089902d11dbe53e5
7
- data.tar.gz: c4cfd5f7ffdcf7f970cc8c85868629bac6c6061312120c0dc2a3c64fa6431cf475bc8fbb877dce0bad3febc2bd87ccc5342e2bc9c8dad196c69306431ae91345
6
+ metadata.gz: 365e09dbfcc7cbb265b8ac6569a66e66a0629ecfb4fd176164cb4c55b6f0223ef05f3e17f3318f7ec6cd58be94396996885b232c496e46620f42049ca9a1fe84
7
+ data.tar.gz: d6d2b9b18cd0da790c2f13fce8994f799bac41e94270f9c69f4b57241f6931d98a8ef81573e8257d1766dfde0a63807d2f06cf57fa4a44ef8f754533b733aeff
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2026-05-29
4
+
5
+ - Load `raptor.rb` or `config/raptor.rb` by default when no config path is supplied
6
+ - Honour the peer's HTTP/2 flow-control windows when sending `DATA` frames
7
+ - Assemble HEADERS across `CONTINUATION` frames
8
+ - Validate HTTP/2 stream IDs and emit `GOAWAY` on protocol errors
9
+ - Offload TLS handshakes to the thread pool to keep the server thread responsive
10
+ - Exit eager keep-alive loops on cluster shutdown
11
+ - Apply the write timeout to HTTP/2 frame writes
12
+ - Reject HPACK dynamic table size updates larger than 4096 bytes
13
+ - Reject malformed HTTP/1.1 requests with a 400 response
14
+ - Rescue unexpected errors in the reactor and pipeline collector
15
+
16
+ ## [0.3.0] - 2026-05-25
17
+
18
+ - Load cluster options from a Ruby config file via `--config`
19
+ - Replace workers one at a time on `SIGUSR2` (phased restart)
20
+ - Invoke `:on_error` callback with `(env, exception)` when the Rack app raises
21
+ - Spool request bodies larger than `--body-spool-threshold` to a tempfile
22
+ - Reject HTTP/1.1 requests larger than `--max-body-size` with a 413 response
23
+ - Write pidfile via `--pidfile`
24
+ - Set `SO_REUSEPORT` on TCP listeners where supported
25
+
3
26
  ## [0.2.0] - 2026-05-25
4
27
 
5
28
  - Add Rack handler for booting via `rackup` or `rails server`
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Raptor
2
2
 
3
- Raptor is a high-performance, multi-threaded, multi-process Ruby Rack 3 web server that leverages Ractors for parallel
4
- HTTP/1.1 and HTTP/2 request processing, native C extensions for HTTP parsing and HPACK compression, and NIO for
5
- non-blocking I/O.
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.
6
6
 
7
7
  ## Installation
8
8
 
@@ -31,9 +31,9 @@ run proc { |_env| [200, { "content-type" => "text/plain" }, ["Hello, World!"]] }
31
31
  ```
32
32
  > bundle exec raptor -t 3 -w 4 hello_world.ru
33
33
  Raptor Cluster initializing:
34
- ├─ Version: 0.1.0
35
- ├─ Ruby Version: ruby 4.0.4 (2026-05-12 revision b89eb1bcbf) +YJIT +PRISM [arm64-darwin23]
36
- ├─ Master PID: 71052
34
+ ├─ Version: 0.4.0
35
+ ├─ Ruby Version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
36
+ ├─ Master PID: 26456
37
37
  │ └─ 4 worker processes
38
38
  │ ├─ 1 server thread
39
39
  │ ├─ 1 reactor thread
@@ -42,10 +42,10 @@ Raptor Cluster initializing:
42
42
  │ ├─ 3 worker threads
43
43
  │ └─ 1 stats thread
44
44
  └─ Listening on 0.0.0.0:9292
45
- [71054] Worker 0 booted
46
- [71055] Worker 1 booted
47
- [71056] Worker 2 booted
48
- [71057] Worker 3 booted
45
+ [26459] Worker 0 booted
46
+ [26460] Worker 1 booted
47
+ [26461] Worker 2 booted
48
+ [26462] Worker 3 booted
49
49
  ```
50
50
 
51
51
  ```
@@ -60,17 +60,17 @@ Also works with `rackup` and `rails server`:
60
60
  > bundle exec rails server -u raptor
61
61
  ```
62
62
 
63
- ## Benchmarks
63
+ ## (Micro) Benchmarks
64
64
 
65
- Raptor 0.1.0 vs Puma 8.0.1:
65
+ Raptor 0.4.0 vs Puma 8.0.1:
66
66
 
67
67
  | Protocol | Raptor | Puma |
68
68
  | --------------------- | ------------ | ------------ |
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 |
69
+ | HTTP/1.1 | ~20k req/s | ~21k req/s |
70
+ | HTTP/1.1 (keep-alive) | ~60k req/s | ~45k req/s |
71
+ | HTTP/2 | ~23k req/s | N/A |
72
72
 
73
- > Ruby 4.0.4 +YJIT, macOS Apple Silicon. 4 workers, 3 threads, 12 concurrent connections.
73
+ > Ruby 4.0.5 +YJIT, macOS Apple Silicon. 4 workers, 3 threads, 12 concurrent connections.
74
74
 
75
75
  ## Development
76
76
 
@@ -457,6 +457,7 @@ static int hpack_decode_header_block(const uint8_t *buf, size_t len,
457
457
  /* dynamic table size update (RFC 7541 6.3) */
458
458
  uint64_t new_size;
459
459
  if (hpack_decode_int(buf, len, &pos, 5, &new_size) < 0) return -1;
460
+ if (new_size > HTTP2_DEFAULT_HEADER_TABLE_SIZE) return -1;
460
461
  *max_table_size = (long)new_size;
461
462
  *dyn_table = dynamic_table_evict(*dyn_table, *max_table_size);
462
463
 
@@ -44,7 +44,8 @@ module Rackup
44
44
  "Port=PORT" => "Port to listen on (default: #{DEFAULT_OPTIONS[:Port]})",
45
45
  "Threads=NUM" => "Number of threads per worker (default: 3)",
46
46
  "Ractors=NUM" => "Number of pipeline ractors per worker (default: 1)",
47
- "Workers=NUM" => "Number of worker processes (default: nprocessors)"
47
+ "Workers=NUM" => "Number of worker processes (default: nprocessors)",
48
+ "Config=PATH" => "Load additional configuration from PATH"
48
49
  }
49
50
  end
50
51
 
@@ -67,20 +68,32 @@ module Rackup
67
68
  end
68
69
  end
69
70
 
70
- host = options[:Host] || defaults[:Host]
71
- port = options[:Port] || defaults[:Port]
72
-
73
71
  cli_defaults = ::Raptor::CLI::DEFAULT_OPTIONS
72
+ config_path = options[:Config] || ::Raptor::CLI.default_config_path
73
+ config = config_path ? ::Raptor::CLI.load_config_file(config_path) : {}
74
74
 
75
- {
75
+ binds = if options[:Host] || options[:Port]
76
+ host = options[:Host] || defaults[:Host]
77
+ port = options[:Port] || defaults[:Port]
78
+ ["tcp://#{host}:#{port}"]
79
+ else
80
+ config[:binds] || ["tcp://#{defaults[:Host]}:#{defaults[:Port]}"]
81
+ end
82
+
83
+ result = {
76
84
  app: app,
77
- binds: ["tcp://#{host}:#{port}"],
78
- threads: (options[:Threads] || cli_defaults[:threads]).to_i,
79
- ractors: (options[:Ractors] || cli_defaults[:ractors]).to_i,
80
- workers: (options[:Workers] || Etc.nprocessors).to_i,
81
- client: cli_defaults[:client],
82
- stats_file: nil
85
+ binds: binds,
86
+ threads: (options[:Threads] || config[:threads] || cli_defaults[:threads]).to_i,
87
+ ractors: (options[:Ractors] || config[:ractors] || cli_defaults[:ractors]).to_i,
88
+ workers: (options[:Workers] || config[:workers] || Etc.nprocessors).to_i,
89
+ client: cli_defaults[:client].merge(config[:client] || {})
83
90
  }
91
+
92
+ [:rackup, :on_error, :stats_file, :pid_file].each do |key|
93
+ result[key] = config[key] if config.key?(key)
94
+ end
95
+
96
+ result
84
97
  end
85
98
  private_class_method :build_cluster_options
86
99
  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,51 @@ 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
+ pid_file: nil,
41
44
  }.freeze
42
45
 
46
+ DEFAULT_CONFIG_PATHS = ["raptor.rb", "config/raptor.rb"].freeze
47
+
48
+ # Loads a configuration file and returns the hash it evaluates to.
49
+ #
50
+ # The file is evaluated at the top level so constants like `Raptor::*` resolve
51
+ # the same as in a regular Ruby script. The final expression must be a Hash
52
+ # of cluster options (the same keys accepted by {Raptor::Cluster#initialize}).
53
+ #
54
+ # @param path [String] path to a Ruby file that evaluates to a Hash
55
+ # @return [Hash{Symbol => untyped}] cluster options
56
+ # @raise [ArgumentError] if the file does not evaluate to a Hash
57
+ #
58
+ # @rbs (String path) -> Hash[Symbol, untyped]
59
+ def self.load_config_file(path)
60
+ config = eval(File.read(path), TOPLEVEL_BINDING, path, 1)
61
+ raise ArgumentError, "Config file at #{path.inspect} must return a Hash, got #{config.class}" unless config.is_a?(Hash)
62
+
63
+ config
64
+ end
65
+
66
+ # Returns the first existing path in {DEFAULT_CONFIG_PATHS} resolved
67
+ # against `root`, or nil if none exist.
68
+ #
69
+ # Used to pick up a project-local config file when no `-c`/`--config`
70
+ # flag was supplied.
71
+ #
72
+ # @param root [String] directory to resolve the default paths against
73
+ # @return [String, nil] the config path, or nil if no default file exists
74
+ #
75
+ # @rbs (?String root) -> String?
76
+ def self.default_config_path(root = Dir.pwd)
77
+ DEFAULT_CONFIG_PATHS.find { |path| File.exist?(File.join(root, path)) }
78
+ end
79
+
43
80
  # @rbs @command: Symbol
44
81
  # @rbs @options: Hash[Symbol, untyped]
45
82
  # @rbs @parser: OptionParser
@@ -70,6 +107,9 @@ module Raptor
70
107
  end
71
108
  @options = DEFAULT_OPTIONS.dup
72
109
  @options[:client] = @options[:client].dup
110
+
111
+ apply_config_file(extract_config_path(argv) || self.class.default_config_path)
112
+
73
113
  @parser = create_parser
74
114
  @parser.parse!(argv)
75
115
 
@@ -111,6 +151,49 @@ module Raptor
111
151
  end
112
152
  end
113
153
 
154
+ # Scans argv for a `-c`/`--config` flag and returns the configured path.
155
+ #
156
+ # The pre-scan runs before the main OptionParser pass so the config file
157
+ # can be applied as a base layer that CLI args then override. All four
158
+ # OptionParser-accepted forms (`-c PATH`, `-cPATH`, `--config PATH`,
159
+ # `--config=PATH`) are recognized.
160
+ #
161
+ # @param argv [Array<String>] command-line arguments to scan
162
+ # @return [String, nil] the config path, or nil if no flag was supplied
163
+ #
164
+ # @rbs (Array[String] argv) -> String?
165
+ def extract_config_path(argv)
166
+ argv.each_with_index do |arg, i|
167
+ case arg
168
+ when "-c", "--config" then return argv[i + 1]
169
+ when /\A--config=(.*)\z/, /\A-c(.+)\z/ then return Regexp.last_match(1)
170
+ end
171
+ end
172
+ nil
173
+ end
174
+
175
+ # Loads a config file and merges it into `@options` over the defaults.
176
+ #
177
+ # Top-level keys replace defaults; the nested `:client` hash is merged
178
+ # key-by-key so a config file does not need to restate every client option.
179
+ #
180
+ # @param path [String, nil] path to the config file, or nil to no-op
181
+ # @return [void]
182
+ #
183
+ # @rbs (String? path) -> void
184
+ def apply_config_file(path)
185
+ return unless path
186
+
187
+ config = self.class.load_config_file(path)
188
+ config.each do |key, value|
189
+ if key == :client && value.is_a?(Hash)
190
+ @options[:client] = @options[:client].merge(value)
191
+ else
192
+ @options[key] = value
193
+ end
194
+ end
195
+ end
196
+
114
197
  # Creates the OptionParser instance with all supported command-line options.
115
198
  #
116
199
  # @return [OptionParser] configured option parser
@@ -120,6 +203,10 @@ module Raptor
120
203
  OptionParser.new do |opts|
121
204
  opts.banner = "Usage: raptor [options] [rackup file]"
122
205
 
206
+ opts.on("-c", "--config PATH", String, "Load configuration from PATH") do
207
+ # Loaded in #initialize before parsing so CLI args can override config values
208
+ end
209
+
123
210
  opts.on("-b", "--bind URI", String, "Bind address (default: tcp://0.0.0.0:9292)") do |bind|
124
211
  if @options[:binds] == DEFAULT_OPTIONS[:binds]
125
212
  @options[:binds] = [bind]
@@ -152,10 +239,22 @@ module Raptor
152
239
  @options[:client][:persistent_data_timeout] = timeout
153
240
  end
154
241
 
242
+ opts.on("--max-body-size BYTES", Integer, "Maximum request body size in bytes (default: unlimited)") do |bytes|
243
+ @options[:client][:max_body_size] = bytes
244
+ end
245
+
246
+ opts.on("--body-spool-threshold BYTES", Integer, "Spool request bodies larger than this to a tempfile (default: #{1024 * 1024})") do |bytes|
247
+ @options[:client][:body_spool_threshold] = bytes
248
+ end
249
+
155
250
  opts.on("--stats-file PATH", String, "Stats file path (default: tmp/raptor.json)") do |path|
156
251
  @options[:stats_file] = path
157
252
  end
158
253
 
254
+ opts.on("--pid-file PATH", String, "PID file path (default: none)") do |path|
255
+ @options[:pid_file] = path
256
+ end
257
+
159
258
  opts.on("--help", "Show this help") do
160
259
  puts opts
161
260
  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 @pid_file: 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
  #
@@ -79,7 +83,10 @@ module Raptor
79
83
  # @option options [Array<String>] :binds array of bind URIs
80
84
  # @option options [#call] :app pre-built Rack application
81
85
  # @option options [String] :rackup path to Rack configuration file
82
- # @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] :pid_file path to write the master PID to, or nil to disable
83
90
  # @return [void]
84
91
  #
85
92
  # @rbs (Hash[Symbol, untyped] options) -> void
@@ -88,6 +95,9 @@ module Raptor
88
95
  @ractor_count = options[:ractors]
89
96
  @worker_count = options[:workers]
90
97
  @client_options = options[:client]
98
+ @on_error = options[:on_error]
99
+ @stats_file = options[:stats_file]
100
+ @pid_file = options[:pid_file]
91
101
 
92
102
  @binder = Binder.new(options[:binds])
93
103
  @server_port = @binder.server_port
@@ -97,14 +107,16 @@ module Raptor
97
107
  @shutdown = false
98
108
  @workers = {}
99
109
  @stats = Stats.new(@worker_count)
100
- @stats_file = options[:stats_file]
110
+ @phased_restart_requested = false
111
+ @phased_restarting = false
101
112
  end
102
113
 
103
114
  # Starts the multi-process cluster and manages worker processes.
104
115
  #
105
116
  # Forks the configured number of worker processes and monitors them,
106
117
  # automatically restarting any that exit unexpectedly. Handles graceful
107
- # 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.
108
120
  #
109
121
  # Each worker process includes:
110
122
  # - 1 server thread (continuously accepts connections with backpressure control)
@@ -121,6 +133,9 @@ module Raptor
121
133
  trap("INT") { shutdown }
122
134
  trap("TERM") { shutdown }
123
135
  trap("USR1") { log_stats }
136
+ trap("USR2") { @phased_restart_requested = true }
137
+
138
+ File.open(@pid_file, File::CREAT | File::EXCL | File::WRONLY) { |file| file.write(Process.pid.to_s) } if @pid_file
124
139
 
125
140
  @worker_count.times { |index| spawn_worker(index) }
126
141
 
@@ -133,29 +148,18 @@ module Raptor
133
148
  end
134
149
 
135
150
  until @shutdown
136
- begin
137
- pid, status = Process.wait2(-1, Process::WNOHANG)
138
- rescue Errno::ECHILD
139
- break
140
- end
151
+ break if reap_workers == :no_children
141
152
 
142
- if pid
143
- index = @workers.key(pid)
144
- @workers.delete(index)
153
+ perform_phased_restart if @phased_restart_requested && !@phased_restarting
145
154
 
146
- unless @shutdown
147
- warn "[#{Process.pid}] Restarting worker #{index} (#{pid}), #{exit_description(status)}"
148
- spawn_worker(index)
149
- end
150
- else
151
- sleep 0.1
152
- end
155
+ sleep 0.1
153
156
  end
154
157
 
155
158
  @workers.values.each { |pid| Process.kill("TERM", pid) rescue nil }
156
159
  @workers.values.each { |pid| Process.wait(pid) rescue nil }
157
160
  stats_file_thread&.join
158
161
  File.delete(@stats_file) rescue nil if @stats_file
162
+ File.delete(@pid_file) rescue nil if @pid_file
159
163
  @stats.unmap
160
164
  end
161
165
 
@@ -182,6 +186,66 @@ module Raptor
182
186
  @workers[index] = pid
183
187
  end
184
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
+
185
249
  # Runs the full server stack inside a worker process.
186
250
  #
187
251
  # Sets up and coordinates the reactor, server, ractor pool, thread pool,
@@ -217,24 +281,29 @@ module Raptor
217
281
  @app.call(env)
218
282
  }
219
283
  thread_pool = AtomicThreadPool.new(name: "Raptor Workers", size: @thread_count)
220
- request = Request.new(counting_app, @server_port)
221
- 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)
222
286
  ractor_pool = RactorPool.new(
223
287
  name: "Raptor Pipeline Workers",
224
288
  size: @ractor_count,
225
289
  worker: request.http_parser_worker
226
290
  ) do |parsed_result|
227
- if parsed_result[:protocol] == :http2
228
- http2.handle_parsed_request(parsed_result, reactor, thread_pool)
229
- else
230
- request.handle_parsed_request(parsed_result, reactor, thread_pool)
291
+ begin
292
+ if parsed_result[:protocol] == :http2
293
+ http2.handle_parsed_request(parsed_result, reactor, thread_pool)
294
+ else
295
+ request.handle_parsed_request(parsed_result, reactor, thread_pool)
296
+ end
297
+ rescue => error
298
+ warn "#{Thread.current.name} rescued:"
299
+ warn error.full_message
231
300
  end
232
301
  end
233
302
 
234
303
  reactor = Reactor.new(thread_pool, ractor_pool, client_options: @client_options)
235
304
  reactor_thread = reactor.run
236
305
 
237
- server = Server.new(@binder, reactor, thread_pool, request)
306
+ server = Server.new(@binder, reactor, thread_pool, request, client_options: @client_options)
238
307
  server_thread = server.run
239
308
 
240
309
  puts "[#{Process.pid}] Worker #{index} booted"
@@ -269,6 +338,7 @@ module Raptor
269
338
  reactor.shutdown
270
339
  reactor_thread.join
271
340
  ractor_pool.shutdown
341
+ request.shutdown
272
342
  thread_pool.shutdown
273
343
  stats_thread.join
274
344
  end