Class: Hyperion::Config
- Inherits:
-
Object
- Object
- Hyperion::Config
- Defined in:
- lib/hyperion/config.rb
Overview
Mutable configuration container — populated by the DSL evaluator (Hyperion::Config.load) and then read by CLI / Server / Master / Worker / Connection / Logger.
All settings have safe defaults that match the per-class DEFAULT_* constants so that running Hyperion without a config file works identically to the pre-rc14 behaviour.
1.7.0 (RFC A4): grouped settings live in nested subconfigs —‘config.h2.*`, `config.admin.*`, `config.worker_health.*`, `config.logging.*`. 1.7 added the nested DSL alongside the legacy flat keys; 1.8 deprecation-warned the flat keys; 2.0 removed them. The nested DSL is the only configuration surface — flat aliases like `h2_max_concurrent_streams` no longer exist on the DSL or on `Config` itself.
Defined Under Namespace
Classes: AdminConfig, BlockProxy, DSL, H2Settings, LoggingConfig, MetricsConfig, TlsConfig, WebSocketConfig, WorkerHealthConfig
Constant Summary collapse
- DEFAULTS =
Top-level (un-nested) defaults. Flat fields that don’t group naturally are deliberately kept here per the RFC’s “only 8 fields warrant nesting in A4” guidance — ‘max_pending`, `idle_keepalive`, `graceful_timeout`, the `tls_*` family, `read_timeout`, and the body/header byte caps stay flat.
{ host: '127.0.0.1', port: 9292, workers: 1, thread_count: 5, tls_cert: nil, tls_key: nil, read_timeout: 30, idle_keepalive: 5, graceful_timeout: 30, max_header_bytes: 64 * 1024, max_body_bytes: 16 * 1024 * 1024, fiber_local_shim: false, yjit: nil, # nil → auto: enable on production/staging; true/false to force. max_pending: nil, max_request_read_seconds: 60, async_io: nil, # nil/true/false (validated strictly in 1.7.0+ via Server constructor). accept_fibers_per_worker: 1, # RFC A6 — opt-in multi-fiber accept under :reuseport. # 2.3-A: io_uring accept policy (Linux 5.6+ only). Tri-state, mirrors `tls.ktls`: # :off — never use io_uring; epoll path always (2.3.0 default). # :auto — use io_uring when supported; quietly fall back otherwise. # :on — demand it; raise at boot if unsupported. # Default flips to :auto in 2.4 only after soak. Operators flip on # via `HYPERION_IO_URING={on,auto}` env var to A/B test. io_uring: :off, # 2.3-B: per-connection in-flight cap. nginx upstream keep-alive # pipelines many client requests through one upstream connection; # without this cap a single greedy upstream conn can hog the worker # thread pool and starve siblings. Tri-state: # * Integer >= 1 — explicit cap (e.g., `4` for `-t 16`). # * :auto — `Config#finalize!` resolves to `thread_count / 4` # (rounded down, minimum 1). Operator opt-in. # * nil (default) — no cap; matches 2.2.0 behaviour. Hyperion is # opt-in by default — the cap is a hardening tool # that operators turn on, not a default flip. max_in_flight_per_conn: nil, # 2.10-E: explicit `preload_static "/path"` DSL entries plus the # CLI's repeatable `--preload-static <dir>` flag accumulate here. # Each element is a `{path: String, immutable: Boolean}` Hash. # `Server#listen` walks the resolved list (which may also include # auto-detected Rails asset paths — see `auto_preload_static_disabled`) # and warms `Hyperion::Http::PageCache` before the accept loop spins. # Default empty so a vanilla Rack app pays nothing. preload_static_dirs: nil, # 2.10-E: when truthy, suppress the Rails-aware auto-detect path # (`Rails.configuration.assets.paths.first(N)`) at boot. Set by # the `--no-preload-static` CLI flag; lets operators turn off # auto-warming on a Rails app while still keeping the option to # configure explicit dirs via `preload_static`. auto_preload_static_disabled: false }.freeze
- HOOKS =
%i[before_fork on_worker_boot on_worker_shutdown].freeze
- MAX_IN_FLIGHT_PER_CONN_AUTO =
2.3-B: top-level ‘:auto` sentinel for `max_in_flight_per_conn`. `Config#finalize!` resolves to `thread_count / 4`, floor 1. Plain symbol (no nested struct) because the only knob is the cap value.
:auto- CLI_FLAT_TO_NESTED =
CLI-only flat→nested setter map. The DSL surface no longer honours these names (2.0 removed the flat DSL forwarders), but ‘Config#merge_cli!` still receives flat-keyed cli_opts hashes built by the OptionParser branches in `Hyperion::CLI`. Routing them via this table keeps CLI flag spellings stable (`–admin-token`, `–log-level`, …) without re-introducing the deprecated DSL surface.
{ h2_max_concurrent_streams: %i[h2 max_concurrent_streams], h2_initial_window_size: %i[h2 initial_window_size], h2_max_frame_size: %i[h2 max_frame_size], h2_max_header_list_size: %i[h2 max_header_list_size], h2_max_total_streams: %i[h2 max_total_streams], admin_token: %i[admin token], admin_listener_port: %i[admin listener_port], admin_listener_host: %i[admin listener_host], worker_max_rss_mb: %i[worker_health max_rss_mb], worker_check_interval: %i[worker_health check_interval], log_level: %i[logging level], log_format: %i[logging format], log_requests: %i[logging requests], tls_handshake_rate_limit: %i[tls handshake_rate_limit] }.freeze
Instance Attribute Summary collapse
-
#admin ⇒ Object
readonly
Nested subconfig readers.
-
#h2 ⇒ Object
readonly
Nested subconfig readers.
-
#logging ⇒ Object
readonly
Nested subconfig readers.
-
#metrics ⇒ Object
readonly
Nested subconfig readers.
-
#tls ⇒ Object
readonly
Nested subconfig readers.
-
#websocket ⇒ Object
readonly
Nested subconfig readers.
-
#worker_health ⇒ Object
readonly
Nested subconfig readers.
Class Method Summary collapse
-
.load(path) ⇒ Object
Load a Ruby DSL config file.
Instance Method Summary collapse
-
#compute_h2_max_total_streams(workers:) ⇒ Object
2.0 default formula (RFC §3): per-conn cap × worker count × 4.
-
#compute_max_in_flight_per_conn ⇒ Object
2.3-B per-conn fairness default: ‘thread_count / 4`, floor 1.
-
#finalize!(workers:) ⇒ Object
Sentinel surfaced through ‘Config#h2.max_total_streams` when the operator hasn’t touched the setting and 2.0’s auto-default formula ought to compute on their behalf at finalize time.
-
#initialize ⇒ Config
constructor
A new instance of Config.
-
#merge_cli!(overrides) ⇒ Object
Apply CLI overrides on top of an existing config.
-
#resolved_preload_static_dirs ⇒ Object
2.10-E — resolve the operator-supplied preload list, falling through to Rails auto-detect when no explicit dirs are configured AND auto-detect is not disabled by the operator.
Constructor Details
#initialize ⇒ Config
Returns a new instance of Config.
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/hyperion/config.rb', line 267 def initialize DEFAULTS.each { |k, v| public_send(:"#{k}=", v) } HOOKS.each { |h| instance_variable_set(:"@#{h}", []) } @h2 = H2Settings.new @admin = AdminConfig.new @worker_health = WorkerHealthConfig.new @logging = LoggingConfig.new @tls = TlsConfig.new @websocket = WebSocketConfig.new @metrics = MetricsConfig.new # 2.10-E: per-instance Array — DEFAULTS is frozen so we can't share # a literal `[]` across Config instances or every operator's DSL # `preload_static` call would mutate the same backing list. @preload_static_dirs = [] end |
Instance Attribute Details
#admin ⇒ Object (readonly)
Nested subconfig readers. The DSL exposes them as block forms (‘h2 do |h| … end`) and the legacy flat forms (`h2_max_concurrent_streams 256`) both write into the same backing object.
87 88 89 |
# File 'lib/hyperion/config.rb', line 87 def admin @admin end |
#h2 ⇒ Object (readonly)
Nested subconfig readers. The DSL exposes them as block forms (‘h2 do |h| … end`) and the legacy flat forms (`h2_max_concurrent_streams 256`) both write into the same backing object.
87 88 89 |
# File 'lib/hyperion/config.rb', line 87 def h2 @h2 end |
#logging ⇒ Object (readonly)
Nested subconfig readers. The DSL exposes them as block forms (‘h2 do |h| … end`) and the legacy flat forms (`h2_max_concurrent_streams 256`) both write into the same backing object.
87 88 89 |
# File 'lib/hyperion/config.rb', line 87 def logging @logging end |
#metrics ⇒ Object (readonly)
Nested subconfig readers. The DSL exposes them as block forms (‘h2 do |h| … end`) and the legacy flat forms (`h2_max_concurrent_streams 256`) both write into the same backing object.
87 88 89 |
# File 'lib/hyperion/config.rb', line 87 def metrics @metrics end |
#tls ⇒ Object (readonly)
Nested subconfig readers. The DSL exposes them as block forms (‘h2 do |h| … end`) and the legacy flat forms (`h2_max_concurrent_streams 256`) both write into the same backing object.
87 88 89 |
# File 'lib/hyperion/config.rb', line 87 def tls @tls end |
#websocket ⇒ Object (readonly)
Nested subconfig readers. The DSL exposes them as block forms (‘h2 do |h| … end`) and the legacy flat forms (`h2_max_concurrent_streams 256`) both write into the same backing object.
87 88 89 |
# File 'lib/hyperion/config.rb', line 87 def websocket @websocket end |
#worker_health ⇒ Object (readonly)
Nested subconfig readers. The DSL exposes them as block forms (‘h2 do |h| … end`) and the legacy flat forms (`h2_max_concurrent_streams 256`) both write into the same backing object.
87 88 89 |
# File 'lib/hyperion/config.rb', line 87 def worker_health @worker_health end |
Class Method Details
.load(path) ⇒ Object
Load a Ruby DSL config file. Returns the populated Config. Path is the operator-supplied –config argument; we evaluate it in a DSL context that maps method calls to attribute setters.
292 293 294 295 296 297 |
# File 'lib/hyperion/config.rb', line 292 def self.load(path) cfg = new contents = File.read(path) DSL.new(cfg).instance_eval(contents, path) cfg end |
Instance Method Details
#compute_h2_max_total_streams(workers:) ⇒ Object
2.0 default formula (RFC §3): per-conn cap × worker count × 4. The 4× headroom factor assumes the average connection holds 25% of the per-conn cap; well above realistic legitimate fan-out yet still bounds the OOM abuse window (5k conns × 128 streams = 640k fibers).
339 340 341 342 343 |
# File 'lib/hyperion/config.rb', line 339 def compute_h2_max_total_streams(workers:) cap_per_conn = @h2.max_concurrent_streams || H2Settings.new.max_concurrent_streams worker_count = (workers && workers.positive? ? workers : 1) cap_per_conn * worker_count * 4 end |
#compute_max_in_flight_per_conn ⇒ Object
2.3-B per-conn fairness default: ‘thread_count / 4`, floor 1. Each conn caps at 25% of the worker’s thread budget so a single greedy upstream connection can’t starve siblings. Floor of 1 ensures degenerate ‘-t 1` / `-t 2` / `-t 3` configurations still serve traffic (cap 1 = strictly serial per conn, but no rejects while no conn is currently dispatched).
351 352 353 354 355 356 |
# File 'lib/hyperion/config.rb', line 351 def compute_max_in_flight_per_conn threads = (@thread_count && @thread_count.positive? ? @thread_count : 1) cap = threads / 4 cap = 1 if cap < 1 cap end |
#finalize!(workers:) ⇒ Object
Sentinel surfaced through ‘Config#h2.max_total_streams` when the operator hasn’t touched the setting and 2.0’s auto-default formula ought to compute on their behalf at finalize time. The ‘nil` value (RFC §3 1.7 default) used to mean “admission disabled forever”; 2.0 redefines `nil` as “auto” and adds an explicit `H2Settings::UNBOUNDED` sentinel for operators who want the pre-2.0 unbounded behaviour.
The Auto path is a sentinel-only wire — ‘H2Settings#initialize` no longer sets a hard `nil`; finalize! resolves it to `max_concurrent_streams × workers × 4` and writes the result back onto `h2.max_total_streams`. Operators reading the value before finalize see the sentinel; after finalize see the resolved integer. Resolve any “auto” sentinels to concrete integers based on finalized peer settings. Called once after `merge_cli!` and after the worker count is known (Master#initialize / CLI run_single). Idempotent — a finalized config can be re-finalized without changing values.
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/hyperion/config.rb', line 318 def finalize!(workers:) case @h2.max_total_streams when H2Settings::AUTO @h2.max_total_streams = compute_h2_max_total_streams(workers: workers) when H2Settings::UNBOUNDED @h2.max_total_streams = nil end # 2.3-B: resolve the `:auto` sentinel for the per-conn fairness # cap. `thread_count / 4` (floor 1) gives each conn at most 25% of # the worker's thread budget — the recommended default. Operators # who set an explicit integer at config time keep their value # untouched; nil (no cap, 2.2.0 default) is also preserved. @max_in_flight_per_conn = compute_max_in_flight_per_conn if @max_in_flight_per_conn == MAX_IN_FLIGHT_PER_CONN_AUTO self end |
#merge_cli!(overrides) ⇒ Object
Apply CLI overrides on top of an existing config. Only non-nil values in ‘overrides` are applied — preserves the precedence ordering (CLI > env > config file > default).
2.0.0: flat keys that map to a nested subconfig (‘admin_token` → `admin.token`, `log_level` → `logging.level`, …) are dispatched through `CLI_FLAT_TO_NESTED`. The DSL no longer accepts these names, but the CLI flag surface keeps its 1.x spellings — operators don’t have to learn a new flag set.
2.10-E: ‘:preload_static` is special-cased — it’s an Array of dir strings from the repeatable ‘–preload-static` flag, and we APPEND each as `immutable: true` to the already-populated `preload_static_dirs` list. Operator config-file entries land first; CLI flags win by being applied last.
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 |
# File 'lib/hyperion/config.rb', line 373 def merge_cli!(overrides) overrides.each do |key, value| next if value.nil? if key == :preload_static Array(value).each do |dir| preload_static_dirs << { path: dir.to_s, immutable: true } end elsif (route = CLI_FLAT_TO_NESTED[key]) group, nested = route public_send(group).public_send(:"#{nested}=", value) elsif respond_to?(:"#{key}=") public_send(:"#{key}=", value) end end self end |
#resolved_preload_static_dirs ⇒ Object
2.10-E — resolve the operator-supplied preload list, falling through to Rails auto-detect when no explicit dirs are configured AND auto-detect is not disabled by the operator. Always returns an Array of ‘immutable:` Hashes (possibly empty).
Precedence:
1. Operator-supplied (DSL `preload_static` or CLI flags) — used verbatim.
2. Otherwise, Rails-detected paths if auto-detect is enabled.
3. Otherwise, [] — no preload, 1.x cold-cache behaviour.
400 401 402 403 404 405 406 407 |
# File 'lib/hyperion/config.rb', line 400 def resolved_preload_static_dirs return preload_static_dirs.dup unless preload_static_dirs.empty? return [] if auto_preload_static_disabled Hyperion::StaticPreload.detect_rails_paths.map do |path| { path: path, immutable: true } end end |