raptor 0.18.0 → 0.19.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: df28f67647ac4c928757722392b5a0da9f61a31c6f81ad7126b6023b12311355
4
- data.tar.gz: 5f7e1298a2aa0fb6622db15749b503261b62a4321549b39f955282285bd74c0c
3
+ metadata.gz: 6620d52a503c84e57e9aea223f0980e2215aeb58da7154e077c31c0d1858bf12
4
+ data.tar.gz: 36c546ae9bb960f6049a82e123af665375785872533b4657f0c336abd8ab7a9f
5
5
  SHA512:
6
- metadata.gz: 00bc3616aa1877c4b79f3acdf00165981ed6401e3a2fb16a2c1565ef92b557772dd1960b31f85b370778e3f2f735904eca6b8cf969ca4aa1f62de3687aea1839
7
- data.tar.gz: af947a551e8f0e3b8d07bd8c07e9b1e686e8f36a1d48af458fb331e1ca8c937f44504b4c9895c233cfe3905d22bce1176d57438baa428e9d44257edc46ba5c1f
6
+ metadata.gz: 729594070744ec522ec48852af6cce2c5aeef4df2050359473bdbf3f11aeaa167dbf7124e9a2fa21e622f77b6351d508fa3f4cf7438839e1b4e8a037784a7c89
7
+ data.tar.gz: 6812627b196892d9ffbc7608d34fc54c790d2d96ecdd4fa0823946389cc147a6e1640028cd057b958e91e9a2fc6307397e693bd5e6e7998b1c41b9fc9d13195b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.19.0] - 2026-08-29
4
+
5
+ - Skip HTTP/2 Ractor pools without SSL bindings
6
+ - Default to adaptive application thread scaling
7
+
3
8
  ## [0.18.0] - 2026-08-29
4
9
 
5
10
  - Add adaptive application thread scaling with `max_threads`
data/README.md CHANGED
@@ -36,30 +36,29 @@ run proc { |_env| [200, { "content-type" => "text/plain" }, ["Hello, World!"]] }
36
36
 
37
37
  ```
38
38
  > bundle exec raptor -w 10 -t 3 hello_world.ru
39
- [Raptor 83654|Main|Main] Cluster initializing:
40
- [Raptor 83654|Main|Main] ├─ Version: 0.18.0
41
- [Raptor 83654|Main|Main] ├─ Ruby Version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
42
- [Raptor 83654|Main|Main] ├─ Environment: development
43
- [Raptor 83654|Main|Main] ├─ Master PID: 83654
44
- [Raptor 83654|Main|Main] │ └─ 10 worker processes
45
- [Raptor 83654|Main|Main] │ ├─ 1 server thread
46
- [Raptor 83654|Main|Main] │ ├─ 1 reactor thread
47
- [Raptor 83654|Main|Main] │ ├─ 1 HTTP/1.1 pipeline ractor
48
- [Raptor 83654|Main|Main] │ ├─ 1 HTTP/2 pipeline ractor
49
- [Raptor 83654|Main|Main] │ ├─ 2 pipeline collector threads
50
- [Raptor 83654|Main|Main] │ ├─ 3 worker threads
51
- [Raptor 83654|Main|Main] └─ 1 stats thread
52
- [Raptor 83654|Main|Main] └─ Listening on 0.0.0.0:9292
53
- [Raptor 83658|Main|Main] Worker 2 booted
54
- [Raptor 83659|Main|Main] Worker 3 booted
55
- [Raptor 83656|Main|Main] Worker 0 booted
56
- [Raptor 83660|Main|Main] Worker 4 booted
57
- [Raptor 83662|Main|Main] Worker 6 booted
58
- [Raptor 83657|Main|Main] Worker 1 booted
59
- [Raptor 83661|Main|Main] Worker 5 booted
60
- [Raptor 83663|Main|Main] Worker 7 booted
61
- [Raptor 83664|Main|Main] Worker 8 booted
62
- [Raptor 83665|Main|Main] Worker 9 booted
39
+ [Raptor 46475|Main|Main] Cluster initializing:
40
+ [Raptor 46475|Main|Main] ├─ Version: 0.19.0
41
+ [Raptor 46475|Main|Main] ├─ Ruby Version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
42
+ [Raptor 46475|Main|Main] ├─ Environment: development
43
+ [Raptor 46475|Main|Main] ├─ Master PID: 46475
44
+ [Raptor 46475|Main|Main] │ └─ 10 worker processes
45
+ [Raptor 46475|Main|Main] │ ├─ 1 server thread
46
+ [Raptor 46475|Main|Main] │ ├─ 1 reactor thread
47
+ [Raptor 46475|Main|Main] │ ├─ 1 HTTP/1.1 pipeline ractor
48
+ [Raptor 46475|Main|Main] │ ├─ 1 pipeline collector thread
49
+ [Raptor 46475|Main|Main] │ ├─ 3 worker threads (scaling, no limit)
50
+ [Raptor 46475|Main|Main] │ └─ 1 stats thread
51
+ [Raptor 46475|Main|Main] └─ Listening on 0.0.0.0:9292
52
+ [Raptor 46480|Main|Main] Worker 0 booted
53
+ [Raptor 46484|Main|Main] Worker 4 booted
54
+ [Raptor 46482|Main|Main] Worker 2 booted
55
+ [Raptor 46481|Main|Main] Worker 1 booted
56
+ [Raptor 46483|Main|Main] Worker 3 booted
57
+ [Raptor 46485|Main|Main] Worker 5 booted
58
+ [Raptor 46486|Main|Main] Worker 6 booted
59
+ [Raptor 46487|Main|Main] Worker 7 booted
60
+ [Raptor 46488|Main|Main] Worker 8 booted
61
+ [Raptor 46489|Main|Main] Worker 9 booted
63
62
  ```
64
63
 
65
64
  ```
@@ -93,7 +92,7 @@ The config file is a Ruby file that evaluates to a hash of options. By default R
93
92
  drain_accept_queue: false,
94
93
  workers: 4, # `Etc.nprocessors`
95
94
  threads: 3,
96
- max_threads: nil, # `Float::INFINITY` for no limit
95
+ max_threads: Float::INFINITY, # set to `threads` for a fixed pool
97
96
  chdir: nil,
98
97
  environment: nil, # falls back to `RAILS_ENV`, then `RACK_ENV`, then `"development"`
99
98
  connection: {
@@ -129,10 +128,10 @@ The config file is a Ruby file that evaluates to a hash of options. By default R
129
128
  }
130
129
  ```
131
130
 
132
- `threads` sets the number of application threads each worker keeps running. Set `max_threads` above it to let Raptor
133
- add temporary threads when queued work is held up by blocking operations, or use `Float::INFINITY` for no limit.
134
- Raptor does not add threads when waiting for the GVL is the bottleneck. Temporary threads leave after the queue
135
- drains. The default `nil` keeps the pool fixed at `threads`.
131
+ `threads` sets the number of application threads each worker keeps running. By default, Raptor adds temporary threads
132
+ without a fixed limit when queued work is held up by blocking operations. It does not add threads when waiting for the
133
+ GVL is the bottleneck, and temporary threads leave after the queue drains. Set `max_threads` to cap growth, or set it
134
+ to the same value as `threads` for a fixed pool.
136
135
 
137
136
  ## Bindings
138
137
 
@@ -207,7 +206,7 @@ Worker 1 (phase 0): pid=91351, requests=1199, busy=1/3, backlog=0, booted, last_
207
206
 
208
207
  ## (Micro) Benchmarks
209
208
 
210
- Raptor 0.18.0 vs Puma 8.0.2 vs Falcon 0.57.0 across two workload profiles. **IO-bound** is a GET endpoint that
209
+ Raptor 0.19.0 vs Puma 8.0.2 vs Falcon 0.57.0 across two workload profiles. **IO-bound** is a GET endpoint that
211
210
  interleaves 5-10 short sleeps (total 2.5-15ms) with small CPU work, simulating a read path that makes several DB or
212
211
  cache calls. **CPU-bound** is a POST endpoint that accepts a small JSON body, interleaves 3-5 chunks of JSON item
213
212
  building (total 450-1500 items) with sub-100µs sleeps, and returns the built array, simulating a write path that does
@@ -1085,18 +1085,18 @@ The bit that matters most in practice:
1085
1085
 
1086
1086
  ## Scaling app threads without making CPU contention worse
1087
1087
 
1088
- The pool stays fixed at `threads` unless you set `max_threads`.
1088
+ The pool starts at `threads` and scales automatically when more threads would help.
1089
1089
 
1090
1090
  - <big>A native CRuby thread hook measures time running, blocked outside the GVL, and waiting for the GVL</big>
1091
1091
  - <big>The queue has to stay non-empty, and every current worker has to be active</big>
1092
1092
  - <big>Blocked time has to exceed half of worker time</big>
1093
1093
  - <big>GVL wait has to stay below two percent</big>
1094
- - <big>Only then does the pool add a temporary thread, up to `max_threads`</big>
1094
+ - <big>Only then does the pool add a temporary thread</big>
1095
1095
  - <big>When the queue drains, temporary threads leave and the pool returns to `threads`</big>
1096
1096
 
1097
1097
  The distinction matters. More threads help when requests are asleep in database or network calls. They make CPU-bound Ruby slower when the GVL is already the bottleneck.
1098
1098
 
1099
- `max_threads: Float::INFINITY` allows the pool to grow without a fixed limit. That still does not make OS threads as cheap as fibers.
1099
+ Growth has no fixed limit by default. Set `max_threads` to cap it, or set it to `threads` to keep the pool fixed. OS threads still are not as cheap as fibers.
1100
1100
 
1101
1101
  <br>
1102
1102
  <br>
@@ -38,7 +38,7 @@ The rest of this doc explains why the shape looks like that.
38
38
  | ---------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------- |
39
39
  | Ruby requirement | 3.0 and up | 4.0 and up (needs `Ractor::Port`) |
40
40
  | Process model | Single, or cluster (pre-forks by default with 2+ workers); optional refork | Cluster only, always pre-forks |
41
- | Threading model | `ThreadPool` with `Mutex` + `ConditionVariable`; autoscaling min/max, or fixed when min == max | `AtomicThreadPool` with a CAS-based queue; fixed by default, with GVL-aware adaptive scaling up to a configured limit |
41
+ | Threading model | `ThreadPool` with `Mutex` + `ConditionVariable`; autoscaling min/max, or fixed when min == max | `AtomicThreadPool` with a CAS-based queue; GVL-aware adaptive scaling from a fixed baseline, with an optional limit |
42
42
  | True parallelism | One GVL inside each worker; parser runs on an app thread | Protocol Ractors can parse in parallel with the app on the pipeline path |
43
43
  | I/O multiplexing | `nio4r` reactor for keep-alive idle and slow reads | `nio4r` reactor for the same, plus a red-black tree for O(log n) timeouts |
44
44
  | Cluster dispatch | Workers race on inherited listeners with a load-proportional accept delay | Two-choice load-aware BPF dispatch for TCP on Linux; shared-listener fallback |
@@ -234,11 +234,11 @@ Your Rack app still runs on ordinary threads under one worker GVL, so the app do
234
234
 
235
235
  **How the Ractor pools actually work.** Raptor uses the `ractor-pool` gem, which is another one of my libraries. Each pool has one coordinator Ractor and M pipeline Ractors. When a pipeline Ractor is idle, it sends itself back to the coordinator via `coordinator.send(Ractor.current, move: true)`. When work arrives at the coordinator, it either forwards it to a waiting Ractor (if any) or queues it. This coordinator-dispatch pattern guarantees that no Ractor sits idle while there is work. Results flow back through a shared `Ractor::Port` (a many-to-one channel added in recent Ruby versions and stable in 4.0) to a Ruby-side collector thread. If `M == 1` the coordinator is skipped and work goes straight to the single pipeline Ractor.
236
236
 
237
- Raptor runs two independent pools per worker, one for HTTP/1.1 parsing and one for HTTP/2 parsing. Both defaults scale with headroom via `round(cores / workers)`, clamped to `[1, 3]` for `http1.ractors` and `[1, 2]` for `http2.ractors`. Splitting the pools means h1 and h2 parsing never share ractor slots, so a burst of small HTTP/1.1 requests cannot delay HTTP/2 frame handling on the same connection, and vice versa.
237
+ Raptor always runs an HTTP/1.1 pool because every binding supports HTTP/1.1. It only starts an HTTP/2 pool when an SSL binding is present, since HTTP/2 is negotiated over TLS. SSL bindings still need both pools because ALPN allows each client to choose HTTP/2 or HTTP/1.1. Both pool sizes scale with headroom via `round(cores / workers)`, clamped to `[1, 3]` for `http1.ractors` and `[1, 2]` for `http2.ractors`. Keeping the pools separate means h1 and h2 parsing never share ractor slots, so a burst of small HTTP/1.1 requests cannot delay HTTP/2 frame handling on the same connection, and vice versa.
238
238
 
239
239
  **Why a custom thread pool.** The `AtomicThreadPool` in `atomic-ruby` (another one of my libraries) is backed by an `AtomicQueue`. The queue is a Michael-Scott multi-producer, multi-consumer FIFO: a singly linked list with a dummy sentinel and atomic head and tail pointers. Producers append nodes at the tail; consumers advance the head. Both operations are O(1) and make progress through compare-and-swap rather than a queue-wide mutex. Separate atoms track queue size and active app threads for backpressure.
240
240
 
241
- The pool is fixed at `threads` by default. Setting `max_threads` above that baseline enables adaptive growth; `Float::INFINITY` removes the fixed limit. A native CRuby thread-event hook measures how much active workers spend running, blocked outside the GVL, and waiting to acquire it. The pool only adds a temporary thread after work has remained queued across several samples, every current worker is active, blocked time is above half of measured worker time, and GVL wait is below two percent. That last check matters: adding threads helps when existing threads are asleep in database or network calls, but hurts when CPU-bound Ruby threads are already fighting over the GVL. Temporary threads retire after the queue has remained empty for a second.
241
+ The pool starts at `threads` and scales without a fixed limit by default. Set `max_threads` to cap its growth, or set it to the same value as `threads` to keep the pool fixed. A native CRuby thread-event hook measures how much active workers spend running, blocked outside the GVL, and waiting to acquire it. The pool only adds a temporary thread after work has remained queued across several samples, every current worker is active, blocked time is above half of measured worker time, and GVL wait is below two percent. That last check matters: adding threads helps when existing threads are asleep in database or network calls, but hurts when CPU-bound Ruby threads are already fighting over the GVL. Temporary threads retire after the queue has remained empty for a second.
242
242
 
243
243
  The pool still uses an `AtomicConditionVariable` under the hood to park idle threads (idle threads call `Thread.stop` and get woken with `Thread#wakeup`; there is no spinning), because idle spinning would waste CPU. The difference from Puma's pool is not "no locks anywhere" but rather "the hot path (enqueue and dequeue when the queue has items) is lock-free". Once every worker is busy the mechanics look similar; where things diverge is under contention when you have many threads all trying to push and pop.
244
244
 
@@ -44,7 +44,7 @@ module Rackup
44
44
  "Port=PORT" => "Port to listen on (default: #{DEFAULT_OPTIONS[:Port]})",
45
45
  "Workers=NUM" => "Number of worker processes (default: available processor count)",
46
46
  "Threads=NUM" => "Number of threads per worker (default: 3)",
47
- "MaxThreads=NUM" => "Maximum threads per worker (`unlimited` for no limit; default: fixed at Threads)",
47
+ "MaxThreads=NUM" => "Maximum threads per worker (`unlimited` for no limit; default: unlimited)",
48
48
  "Config=PATH" => "Load additional configuration from PATH"
49
49
  }
50
50
  end
@@ -71,6 +71,8 @@ module Rackup
71
71
  cli_defaults = ::Raptor::CLI::DEFAULT_OPTIONS
72
72
  config_path = options[:Config] || ::Raptor::CLI.default_config_path
73
73
  config = config_path ? ::Raptor::CLI.load_config_file(config_path) : {}
74
+ threads = (options[:Threads] || config[:threads] || cli_defaults[:threads]).to_i
75
+ max_threads = options[:MaxThreads] || config.fetch(:max_threads, cli_defaults[:max_threads])
74
76
 
75
77
  result = {
76
78
  binds: if options[:Host] || options[:Port]
@@ -81,8 +83,8 @@ module Rackup
81
83
  socket_backlog: (config[:socket_backlog] || cli_defaults[:socket_backlog]).to_i,
82
84
  drain_accept_queue: config.key?(:drain_accept_queue) ? config[:drain_accept_queue] : cli_defaults[:drain_accept_queue],
83
85
  workers: (options[:Workers] || config[:workers] || Concurrent.available_processor_count).to_i,
84
- threads: (options[:Threads] || config[:threads] || cli_defaults[:threads]).to_i,
85
- max_threads: ::Raptor::CLI.parse_max_threads(options[:MaxThreads] || config[:max_threads]),
86
+ threads: threads,
87
+ max_threads: ::Raptor::CLI.parse_max_threads(max_threads, threads: threads),
86
88
  app: app
87
89
  }
88
90
  result[:rackup] = config[:rackup] if config.key?(:rackup)
data/lib/raptor/binder.rb CHANGED
@@ -106,6 +106,15 @@ module Raptor
106
106
  tcp_listener.local_address.ip_port
107
107
  end
108
108
 
109
+ # Returns whether any listener can negotiate HTTP/2.
110
+ #
111
+ # @return [Boolean]
112
+ #
113
+ # @rbs () -> bool
114
+ def http2?
115
+ @listeners.any? { |listener| listener.is_a?(SslListener) }
116
+ end
117
+
109
118
  # Closes all listening sockets.
110
119
  #
111
120
  # @return [void]
data/lib/raptor/cli.rb CHANGED
@@ -34,7 +34,7 @@ module Raptor
34
34
  drain_accept_queue: false,
35
35
  workers: DEFAULT_WORKER_COUNT,
36
36
  threads: 3,
37
- max_threads: nil,
37
+ max_threads: Float::INFINITY,
38
38
  rackup: "config.ru",
39
39
  chdir: nil,
40
40
  environment: nil,
@@ -101,17 +101,27 @@ module Raptor
101
101
 
102
102
  # Parses a maximum thread count from a CLI, config, or Rack handler value.
103
103
  #
104
- # @param value [Integer, Float, String, nil] maximum thread count
105
- # @return [Integer, Float, nil] parsed thread count, `Float::INFINITY` for
106
- # `"unlimited"`, or nil for a fixed-size pool
104
+ # @param value [Integer, Float, String] maximum thread count
105
+ # @param threads [Integer] baseline thread count
106
+ # @return [Integer, Float] parsed thread count, or `Float::INFINITY` for
107
+ # `"unlimited"`
108
+ # @raise [ArgumentError] if the maximum is nil or less than the baseline
107
109
  #
108
- # @rbs (Integer | Float | String | nil value) -> (Integer | Float)?
109
- def self.parse_max_threads(value)
110
- return unless value
111
- return Float::INFINITY if value == "unlimited" || value == Float::INFINITY
112
- return value if value.is_a?(Integer)
110
+ # @rbs (Integer | Float | String value, threads: Integer) -> (Integer | Float)
111
+ def self.parse_max_threads(value, threads:)
112
+ raise ArgumentError, "max_threads cannot be nil" unless value
113
+
114
+ max_threads = if value == "unlimited" || value == Float::INFINITY
115
+ Float::INFINITY
116
+ elsif value.is_a?(Integer)
117
+ value
118
+ else
119
+ Integer(value, 10)
120
+ end
121
+
122
+ raise ArgumentError, "max_threads must be greater than or equal to threads" if max_threads < threads
113
123
 
114
- Integer(value, 10)
124
+ max_threads
115
125
  end
116
126
 
117
127
  # @rbs @command: Symbol
@@ -146,7 +156,7 @@ module Raptor
146
156
  @parser.parse!(argv)
147
157
 
148
158
  @options[:rackup] = argv.first if @command == :server && argv.first
149
- @options[:max_threads] = self.class.parse_max_threads(@options[:max_threads])
159
+ @options[:max_threads] = self.class.parse_max_threads(@options[:max_threads], threads: @options[:threads])
150
160
  end
151
161
 
152
162
  # Runs the requested command.
@@ -259,7 +269,7 @@ module Raptor
259
269
  @options[:threads] = num
260
270
  end
261
271
 
262
- opts.on("--max-threads NUM", "Maximum application threads per worker (`unlimited` for no limit; default: fixed at --threads)") do |num|
272
+ opts.on("--max-threads NUM", "Maximum application threads per worker (`unlimited` for no limit; default: unlimited)") do |num|
263
273
  @options[:max_threads] = num
264
274
  end
265
275
 
@@ -126,7 +126,7 @@ module Raptor
126
126
  # @option options [Boolean] :drain_accept_queue whether to drain the kernel accept queue on shutdown
127
127
  # @option options [Integer] :workers number of worker processes
128
128
  # @option options [Integer] :threads number of threads per worker process
129
- # @option options [Integer, Float, nil] :max_threads maximum number of threads per worker process, `Float::INFINITY` for no limit, or nil for a fixed-size pool
129
+ # @option options [Integer, Float] :max_threads maximum number of threads per worker process, or `Float::INFINITY` for no limit
130
130
  # @option options [#call] :app pre-built Rack application
131
131
  # @option options [String] :rackup path to Rack configuration file
132
132
  # @option options [String, nil] :chdir directory to change to before loading the Rack application, or nil to leave the working directory unchanged
@@ -784,15 +784,17 @@ module Raptor
784
784
  end
785
785
  end
786
786
 
787
- http2_ractor_pool = RactorPool.new(
788
- size: @http2_ractor_count,
789
- worker: http2.parser_worker,
790
- name: "HTTP/2"
791
- ) do |parsed_result|
792
- begin
793
- http2.handle_parsed_request(parsed_result, reactor, thread_pool)
794
- rescue => error
795
- Log.rescued_error(error)
787
+ http2_ractor_pool = if @binder.http2?
788
+ RactorPool.new(
789
+ size: @http2_ractor_count,
790
+ worker: http2.parser_worker,
791
+ name: "HTTP/2"
792
+ ) do |parsed_result|
793
+ begin
794
+ http2.handle_parsed_request(parsed_result, reactor, thread_pool)
795
+ rescue => error
796
+ Log.rescued_error(error)
797
+ end
796
798
  end
797
799
  end
798
800
 
@@ -871,7 +873,7 @@ module Raptor
871
873
  reactor.shutdown
872
874
  reactor_thread.join
873
875
  http1_ractor_pool.shutdown
874
- http2_ractor_pool.shutdown
876
+ http2_ractor_pool&.shutdown
875
877
  http1.shutdown
876
878
  drain_thread_pool(thread_pool)
877
879
  stats_thread.join
@@ -979,8 +981,10 @@ module Raptor
979
981
  Log.info "│ ├─ 1 server thread"
980
982
  Log.info "│ ├─ 1 reactor thread"
981
983
  Log.info "│ ├─ #{@http1_ractor_count} HTTP/1.1 pipeline ractor#{"s" if @http1_ractor_count > 1}"
982
- Log.info "│ ├─ #{@http2_ractor_count} HTTP/2 pipeline ractor#{"s" if @http2_ractor_count > 1}"
983
- Log.info "│ ├─ 2 pipeline collector threads"
984
+ http2_enabled = @binder.http2?
985
+ Log.info "│ ├─ #{@http2_ractor_count} HTTP/2 pipeline ractor#{"s" if @http2_ractor_count > 1}" if http2_enabled
986
+ collector_count = http2_enabled ? 2 : 1
987
+ Log.info "│ ├─ #{collector_count} pipeline collector thread#{"s" if collector_count > 1}"
984
988
  thread_limit = if @max_thread_count == Float::INFINITY
985
989
  " (scaling, no limit)"
986
990
  elsif @max_thread_count
@@ -71,7 +71,7 @@ module Raptor
71
71
  # Creates a new Reactor instance.
72
72
  #
73
73
  # @param http1_ractor_pool [RactorPool] ractor pool for HTTP/1.x parsing
74
- # @param http2_ractor_pool [RactorPool] ractor pool for HTTP/2 parsing
74
+ # @param http2_ractor_pool [RactorPool, nil] ractor pool for HTTP/2 parsing, or nil when no listener supports HTTP/2
75
75
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
76
76
  # @param connection_options [Hash] per-connection timeout configuration
77
77
  # @option connection_options [Integer] :first_data_timeout timeout for initial data
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Raptor
5
- VERSION = "0.18.0"
5
+ VERSION = "0.19.0"
6
6
  end
@@ -81,6 +81,13 @@ module Raptor
81
81
  # @rbs () -> Integer
82
82
  def server_port: () -> Integer
83
83
 
84
+ # Returns whether any listener can negotiate HTTP/2.
85
+ #
86
+ # @return [Boolean]
87
+ #
88
+ # @rbs () -> bool
89
+ def http2?: () -> bool
90
+
84
91
  # Closes all listening sockets.
85
92
  #
86
93
  # @return [void]
@@ -46,12 +46,14 @@ module Raptor
46
46
 
47
47
  # Parses a maximum thread count from a CLI, config, or Rack handler value.
48
48
  #
49
- # @param value [Integer, Float, String, nil] maximum thread count
50
- # @return [Integer, Float, nil] parsed thread count, `Float::INFINITY` for
51
- # `"unlimited"`, or nil for a fixed-size pool
52
- #
53
- # @rbs (Integer | Float | String | nil value) -> (Integer | Float)?
54
- def self.parse_max_threads: (Integer | Float | String | nil value) -> (Integer | Float)?
49
+ # @param value [Integer, Float, String] maximum thread count
50
+ # @param threads [Integer] baseline thread count
51
+ # @return [Integer, Float] parsed thread count, or `Float::INFINITY` for
52
+ # `"unlimited"`
53
+ # @raise [ArgumentError] if the maximum is nil or less than the baseline
54
+ #
55
+ # @rbs (Integer | Float | String value, threads: Integer) -> (Integer | Float)
56
+ def self.parse_max_threads: (Integer | Float | String value, threads: Integer) -> (Integer | Float)
55
57
 
56
58
  @parser: OptionParser
57
59
 
@@ -150,7 +150,7 @@ module Raptor
150
150
  # @option options [Boolean] :drain_accept_queue whether to drain the kernel accept queue on shutdown
151
151
  # @option options [Integer] :workers number of worker processes
152
152
  # @option options [Integer] :threads number of threads per worker process
153
- # @option options [Integer, Float, nil] :max_threads maximum number of threads per worker process, `Float::INFINITY` for no limit, or nil for a fixed-size pool
153
+ # @option options [Integer, Float] :max_threads maximum number of threads per worker process, or `Float::INFINITY` for no limit
154
154
  # @option options [#call] :app pre-built Rack application
155
155
  # @option options [String] :rackup path to Rack configuration file
156
156
  # @option options [String, nil] :chdir directory to change to before loading the Rack application, or nil to leave the working directory unchanged
@@ -73,7 +73,7 @@ module Raptor
73
73
  # Creates a new Reactor instance.
74
74
  #
75
75
  # @param http1_ractor_pool [RactorPool] ractor pool for HTTP/1.x parsing
76
- # @param http2_ractor_pool [RactorPool] ractor pool for HTTP/2 parsing
76
+ # @param http2_ractor_pool [RactorPool, nil] ractor pool for HTTP/2 parsing, or nil when no listener supports HTTP/2
77
77
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
78
78
  # @param connection_options [Hash] per-connection timeout configuration
79
79
  # @option connection_options [Integer] :first_data_timeout timeout for initial data
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.18.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young