Module: Hyperion
- Defined in:
- lib/hyperion.rb,
lib/hyperion/cli.rb,
lib/hyperion/tls.rb,
lib/hyperion/pool.rb,
lib/hyperion/config.rb,
lib/hyperion/logger.rb,
lib/hyperion/master.rb,
lib/hyperion/parser.rb,
lib/hyperion/server.rb,
lib/hyperion/worker.rb,
lib/hyperion/metrics.rb,
lib/hyperion/request.rb,
lib/hyperion/runtime.rb,
lib/hyperion/version.rb,
lib/hyperion/h2_codec.rb,
lib/hyperion/io_uring.rb,
lib/hyperion/connection.rb,
lib/hyperion/fiber_local.rb,
lib/hyperion/thread_pool.rb,
lib/hyperion/adapter/rack.rb,
lib/hyperion/deprecations.rb,
lib/hyperion/h2_admission.rb,
lib/hyperion/dispatch_mode.rb,
lib/hyperion/http/sendfile.rb,
lib/hyperion/http2_handler.rb,
lib/hyperion/worker_health.rb,
lib/hyperion/admin_listener.rb,
lib/hyperion/static_preload.rb,
lib/hyperion/http/page_cache.rb,
lib/hyperion/response_writer.rb,
lib/hyperion/websocket/frame.rb,
lib/hyperion/admin_middleware.rb,
lib/hyperion/lint_wrapper_pool.rb,
lib/hyperion/server/route_table.rb,
lib/hyperion/prometheus_exporter.rb,
lib/hyperion/websocket/handshake.rb,
lib/hyperion/websocket/connection.rb,
lib/hyperion/websocket/close_codes.rb,
lib/hyperion/metrics/path_templater.rb,
lib/hyperion/http2/native_hpack_adapter.rb
Defined Under Namespace
Modules: Adapter, Deprecations, FiberLocal, H2Codec, Http, Http2, IOUring, LintWrapperPool, PrometheusExporter, StaticPreload, TLS, WebSocket, WorkerHealth Classes: AdminListener, AdminMiddleware, CLI, CParser, Config, Connection, DispatchMode, Error, H2Admission, Http2Handler, Logger, Master, Metrics, ParseError, Parser, Pool, Request, ResponseWriter, Runtime, Server, ThreadPool, UnsupportedError, Worker
Constant Summary collapse
- FIBER_IO_PROBES =
Probe table for fiber-cooperative I/O libraries. 1.7.0 expanded the validation surface (RFC A9): ‘async_io: true` now requires at least one of these to be loaded, otherwise CLI bootstrap raises at `Hyperion.validate_async_io_loaded_libs!`. The CLI’s pre-fork ‘warn_orphan_async_io` (1.6.1) still emits a soft warn for the nil-default case so existing operators see the same advisory log.
{ 'hyperion-async-pg' => -> { defined?(::Hyperion::AsyncPg) }, 'async-redis' => -> { defined?(::Async::Redis) }, 'async-http' => -> { defined?(::Async::HTTP) } }.freeze
- VERSION =
'2.11.0'
Class Attribute Summary collapse
-
.log_requests ⇒ Object
writeonly
Sets the attribute log_requests.
Class Method Summary collapse
-
.c_parser_available? ⇒ Boolean
Whether the llhttp C extension loaded.
-
.fiber_io_libs_loaded ⇒ Object
Returns the list of currently-loaded fiber-cooperative I/O libraries.
-
.log_requests? ⇒ Boolean
Per-request access logging is ON by default — matches Puma/Rails operator expectations (Rails::Rack::Logger emits one line per request out of the box).
-
.logger ⇒ Object
2.0.0: legacy module-level ‘Hyperion.metrics =` / `Hyperion.logger =` SETTERS are removed.
-
.master_pid ⇒ Object
Pre-fork warmup.
-
.master_pid!(pid = Process.pid) ⇒ Object
Record the master PID and export it for forked workers.
- .metrics ⇒ Object
-
.reset_warmup! ⇒ Object
Test seam: clear the warmup flag so a fresh ‘warmup!` call can re-run.
- .stats ⇒ Object
-
.validate_async_io_loaded_libs!(setting) ⇒ Object
Strict tri-state validation of ‘async_io` at warmup time (RFC A9).
- .warmup! ⇒ Object
-
.yjit_enabled? ⇒ Boolean
Whether YJIT is currently enabled in this Ruby process.
Class Attribute Details
.log_requests=(value) ⇒ Object (writeonly)
Sets the attribute log_requests
44 45 46 |
# File 'lib/hyperion.rb', line 44 def log_requests=(value) @log_requests = value end |
Class Method Details
.c_parser_available? ⇒ Boolean
Whether the llhttp C extension loaded. False on JRuby/TruffleRuby and any environment where extconf.rb / make failed at install time. The pure-Ruby parser handles those cases correctly but is ~2× slower on parse-heavy workloads. Operators running production should confirm this returns true; CLI emits a startup banner if it doesn’t.
67 68 69 |
# File 'lib/hyperion.rb', line 67 def c_parser_available? defined?(::Hyperion::CParser) && ::Hyperion::CParser.respond_to?(:build_response_head) end |
.fiber_io_libs_loaded ⇒ Object
Returns the list of currently-loaded fiber-cooperative I/O libraries. Reads ‘Hyperion::FIBER_IO_PROBES` via `const_get` so `stub_const(’Hyperion::FIBER_IO_PROBES’, …)‘ works for the strict-validation specs without needing a method-injection seam.
149 150 151 152 |
# File 'lib/hyperion.rb', line 149 def fiber_io_libs_loaded probes = Hyperion.const_get(:FIBER_IO_PROBES) probes.select { |_name, probe| probe.call }.keys end |
.log_requests? ⇒ Boolean
Per-request access logging is ON by default — matches Puma/Rails operator expectations (Rails::Rack::Logger emits one line per request out of the box). Operators can disable it via ‘–no-log-requests`, `HYPERION_LOG_REQUESTS=0|false|no|off`, or programmatically via `Hyperion.log_requests = false`. When false, Connection skips ALL access-log work — no Process.clock_gettime, no hash build, nothing.
The hot path uses Logger#access (single-interpolation line build, per-thread cached timestamp, lock-free emit) so default-ON throughput stays well above Puma’s default-OFF baseline.
81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/hyperion.rb', line 81 def log_requests? return @log_requests unless @log_requests.nil? env = ENV['HYPERION_LOG_REQUESTS']&.downcase @log_requests = case env when '0', 'false', 'no', 'off' then false when '1', 'true', 'yes', 'on' then true else true # default ON end end |
.logger ⇒ Object
2.0.0: legacy module-level ‘Hyperion.metrics =` / `Hyperion.logger =` SETTERS are removed. The getters stay as Runtime.default delegators —they’re the canonical REPL convenience — and assignment now happens via the Runtime API:
Hyperion::Runtime.default.metrics = MyAdapter.new # mutate default
server = Hyperion::Server.new(app:, runtime: Hyperion::Runtime.new(metrics: …))
The 1.8.0 deprecation warns called this out for one full release; in-tree spec rewrites flipped to ‘Runtime.default.metrics =` already.
40 41 42 |
# File 'lib/hyperion.rb', line 40 def logger Runtime.default.logger end |
.master_pid ⇒ Object
Pre-fork warmup. Run by Master and CLI single-mode BEFORE children are forked (or before the lone worker starts accepting). Pre-allocates the Rack adapter’s object pools and eager-touches lazily-resolved constants so each forked child inherits warm memory via copy-on-write — the first N requests on a fresh worker no longer pay the allocation / autoload tax that would otherwise serialize behind the GVL on cold start.
Idempotent — second and later calls are no-ops. Failures are swallowed with a warn log: warmup is an optimization, not a correctness gate. If, for instance, OpenSSL can’t be required in some odd environment, we’d rather start cold than refuse to boot. PID of the Hyperion master process. Writable so the master records its own PID at boot; readable everywhere so AdminMiddleware (and other would-be signallers) can target the master regardless of context.
Why not ‘Process.ppid`? Two reasons:
1. In single-worker mode, the "master" and "worker" are the same
process; `Process.ppid` points to the shell / init that launched
hyperion, NOT to ourselves.
2. When the master runs as PID 1 inside containerd / Docker (the
default for `hyperion` as a container CMD), `Process.ppid` from a
worker is `1` — but the worker IS a child of PID 1, so `kill`ing
ppid signals the master correctly only by accident, and the
pre-1.6.3 fallback `ppid > 1 ? ppid : Process.pid` would
MIS-target the worker itself. (Repro: `docker run -e
HYPERION_ADMIN_TOKEN=… hyperion` then `curl -X POST /-/quit` —
response says draining, nothing happens.)
The master sets this at boot (cluster: Master#run, single: CLI.run_single) AND exports ‘HYPERION_MASTER_PID` into ENV so forked workers read the correct value via copy-on-write. The reader prefers the in-process ivar (faster) and falls back to ENV (cross-fork) and finally to `Process.pid` (last-resort: someone constructed AdminMiddleware before the master booted, or in a non-Hyperion test context).
128 129 130 131 132 133 134 |
# File 'lib/hyperion.rb', line 128 def master_pid return @master_pid if @master_pid env = ENV['HYPERION_MASTER_PID'] env_pid = env && env =~ /\A\d+\z/ ? env.to_i : nil env_pid&.positive? ? env_pid : Process.pid end |
.master_pid!(pid = Process.pid) ⇒ Object
Record the master PID and export it for forked workers. Called once by the master at boot. Workers inherit ENV via fork; the worker’s own ‘master_pid` ivar stays nil and its reader falls back to ENV.
139 140 141 142 143 |
# File 'lib/hyperion.rb', line 139 def master_pid!(pid = Process.pid) @master_pid = pid ENV['HYPERION_MASTER_PID'] = pid.to_s pid end |
.metrics ⇒ Object
46 47 48 |
# File 'lib/hyperion.rb', line 46 def metrics Runtime.default.metrics end |
.reset_warmup! ⇒ Object
Test seam: clear the warmup flag so a fresh ‘warmup!` call can re-run. Used by the async_io strict-validation specs that need to exercise the raise/warn paths multiple times in one process.
228 229 230 |
# File 'lib/hyperion.rb', line 228 def reset_warmup! @warmed = false end |
.stats ⇒ Object
50 51 52 |
# File 'lib/hyperion.rb', line 50 def stats metrics.snapshot end |
.validate_async_io_loaded_libs!(setting) ⇒ Object
Strict tri-state validation of ‘async_io` at warmup time (RFC A9). Run after `Hyperion.warmup!`’s eager-load section so any library that monkey-patches in fiber-cooperative I/O during boot has had the chance to install itself.
-
‘true` → MUST have at least one fiber-IO library loaded; raise
ArgumentError otherwise. The error message lists the checked libraries so operators can pick one. -
‘false` → No fiber-IO library should be loaded; if one is, emit
a warn (the operator may still want this for some edge case, so we don't raise). -
‘nil` → Default. The CLI’s existing soft-warn path covers
this; warmup is a no-op.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/hyperion.rb', line 167 def validate_async_io_loaded_libs!(setting) probes = Hyperion.const_get(:FIBER_IO_PROBES) case setting when true loaded = fiber_io_libs_loaded if loaded.empty? raise ArgumentError, 'async_io: true requires a fiber-cooperative I/O library to be loaded ' \ "(checked: #{probes.keys.join(', ')}); none detected. " \ 'See https://github.com/andrew-woblavobla/hyperion#operator-guidance' end when false loaded = fiber_io_libs_loaded unless loaded.empty? Hyperion.logger.warn do { message: 'async_io: false but fiber-cooperative I/O library is loaded', loaded: loaded, impact: 'the library will not yield to a scheduler under async_io: false; verify this is intentional' } end end end nil end |
.warmup! ⇒ Object
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/hyperion.rb', line 193 def warmup! return if @warmed @warmed = true if defined?(::Hyperion::Adapter::Rack) && ::Hyperion::Adapter::Rack.respond_to?(:warmup_pool) ::Hyperion::Adapter::Rack.warmup_pool(8) end # Touch the C extension's response-head builder so its lazily-initialized # internal state runs in the master, not in every child after fork. ::Hyperion::CParser.respond_to?(:build_response_head) if defined?(::Hyperion::CParser) # Eager-load TLS / SSLSocket. The sendfile path's `is_a?` check would # otherwise trigger autoload in the worker on the first TLS response. require 'openssl' defined?(::OpenSSL::SSL::SSLSocket) && ::OpenSSL::SSL::SSLSocket.name # Force Ruby's tzinfo / strftime-cache load by emitting one httpdate. # Subsequent calls hit the per-thread `cached_date` slot in response_writer. Time.now.httpdate nil rescue ArgumentError # Strict-validation error from `validate_async_io_loaded_libs!` — # propagate so operators see the boot-time abort, not a warn-and- # continue. raise rescue StandardError => e Hyperion.logger.warn { { message: 'warmup failed (non-fatal)', error: e. } } nil end |
.yjit_enabled? ⇒ Boolean
Whether YJIT is currently enabled in this Ruby process. False on Rubies that don’t ship YJIT (JRuby, TruffleRuby) and on CRuby builds compiled without YJIT support. Cheap (no allocations) — safe to call from hot paths if needed for diagnostics.
58 59 60 |
# File 'lib/hyperion.rb', line 58 def yjit_enabled? defined?(::RubyVM::YJIT) && ::RubyVM::YJIT.enabled? end |