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.10.1'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.log_requests=(value) ⇒ Object (writeonly)

Sets the attribute log_requests

Parameters:

  • value

    the value to set the attribute log_requests to.



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.

Returns:

  • (Boolean)


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_loadedObject

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.

Returns:

  • (Boolean)


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

.loggerObject

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_pidObject

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

.metricsObject



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

.statsObject



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.message } }
  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.

Returns:

  • (Boolean)


58
59
60
# File 'lib/hyperion.rb', line 58

def yjit_enabled?
  defined?(::RubyVM::YJIT) && ::RubyVM::YJIT.enabled?
end