Module: Hyperion::Server::ConnectionLoop
- Defined in:
- lib/hyperion/server/connection_loop.rb
Overview
2.12-C — Connection lifecycle in C.
Engaged by ‘Server#start_raw_loop` when ALL of the following hold:
* The listener is plain TCP (no TLS, no h2 ALPN dance).
* The route table has at least one `RouteTable::StaticEntry`
registration (i.e. `Server.handle_static` was called).
* The route table has NO non-StaticEntry registrations
(any `Server.handle(:GET, '/api', dynamic_handler)` disables
the C path; the C loop only knows how to write prebuilt
responses).
* The `HYPERION_C_ACCEPT_LOOP` env knob is not set to `"0"` /
`"off"` (operator escape hatch for debug).
On engage, the Ruby accept loop is not run for this listener; ‘Hyperion::Http::PageCache.run_static_accept_loop` drives the accept-and-serve loop entirely in C and only re-enters Ruby for:
1. Per-request lifecycle hooks
(`Runtime#fire_request_start` / `fire_request_end`), gated
by a single C-side integer flag so the no-hook hot path
stays one syscall.
2. Connection handoff: requests that don't match a `StaticEntry`
(or are malformed, h2/upgrade, or carry a body) are passed
back as `(fd, partial_buffer)` — Ruby resumes ownership of
the fd and dispatches via the regular `Connection` path.
The wiring lives in this module so the conditional logic stays out of the Server hot-path entry methods.
Class Method Summary collapse
-
.available? ⇒ Boolean
Whether the C accept loop is available and the env didn’t disable it.
-
.build_handoff_callback(server) ⇒ Object
Build the handoff callback the C loop invokes when a connection’s first request can’t be served from the static cache.
-
.build_lifecycle_callback(runtime) ⇒ Object
Build a lifecycle callback that, when invoked from the C loop with ‘(method_str, path_str)`, fires the runtime’s ‘fire_request_start` / `fire_request_end` hooks against a minimal `Hyperion::Request` value object.
-
.eligible_route_table?(route_table) ⇒ Boolean
Whether the route table is C-loop eligible: only ‘StaticEntry` handlers, at least one of them, no dynamic handlers anywhere.
-
.io_uring_eligible? ⇒ Boolean
2.12-D — whether to engage the io_uring accept loop variant over the 2.12-C ‘accept4` loop.
Class Method Details
.available? ⇒ Boolean
Whether the C accept loop is available and the env didn’t disable it.
39 40 41 42 43 44 45 |
# File 'lib/hyperion/server/connection_loop.rb', line 39 def available? return false unless defined?(::Hyperion::Http::PageCache) return false unless ::Hyperion::Http::PageCache.respond_to?(:run_static_accept_loop) env = ENV['HYPERION_C_ACCEPT_LOOP'] env.nil? || !%w[0 off false no].include?(env.downcase) end |
.build_handoff_callback(server) ⇒ Object
Build the handoff callback the C loop invokes when a connection’s first request can’t be served from the static cache. Receives ‘(fd, partial_buffer_or_nil)` — Ruby owns the fd from that point on. We wrap the fd in a `Socket` (so `apply_timeout` and the rest of the Connection path see the same surface they always see) and dispatch through the server’s existing ‘dispatch_handed_off` helper.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/hyperion/server/connection_loop.rb', line 140 def build_handoff_callback(server) lambda do |fd, partial| server.send(:dispatch_handed_off, fd, partial) rescue StandardError => e server.send(:runtime_logger).warn do { message: 'C loop handoff dispatch failed', error: e., error_class: e.class.name } end # Always close the fd if dispatch raised — Ruby owns it. begin require 'socket' ::Socket.for_fd(fd).close rescue StandardError nil end end end |
.build_lifecycle_callback(runtime) ⇒ Object
Build a lifecycle callback that, when invoked from the C loop with ‘(method_str, path_str)`, fires the runtime’s ‘fire_request_start` / `fire_request_end` hooks against a minimal `Hyperion::Request` value object. `env=nil` and the response slot carries the `:c_static` symbol (just a marker —the wire write already happened in C and we have no `[status, headers, body]` tuple to hand back).
The proc captures ‘runtime` so multi-tenant deployments with per-Server runtimes route hooks to the right observer registry. Allocation cost: one Request per request when hooks are active. The C loop only invokes this callback when `lifecycle_active?` is true; the no-hook path pays nothing.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/hyperion/server/connection_loop.rb', line 110 def build_lifecycle_callback(runtime) lambda do |method_str, path_str| request = ::Hyperion::Request.new( method: method_str, path: path_str, query_string: nil, http_version: 'HTTP/1.1', headers: {}, body: nil ) if runtime.has_request_hooks? runtime.fire_request_start(request, nil) runtime.fire_request_end(request, nil, :c_static, nil) end nil rescue StandardError # Hook errors are already swallowed inside `Runtime#fire_*`; # this rescue catches Request allocation oddities so a # misbehaving observer can't take down the C loop. nil end end |
.eligible_route_table?(route_table) ⇒ Boolean
Whether the route table is C-loop eligible: only ‘StaticEntry` handlers, at least one of them, no dynamic handlers anywhere.
83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/hyperion/server/connection_loop.rb', line 83 def eligible_route_table?(route_table) return false unless route_table any_static = false route_table.instance_variable_get(:@routes).each_value do |path_table| path_table.each_value do |handler| return false unless handler.is_a?(::Hyperion::Server::RouteTable::StaticEntry) any_static = true end end any_static end |
.io_uring_eligible? ⇒ Boolean
2.12-D — whether to engage the io_uring accept loop variant over the 2.12-C ‘accept4` loop. All four conditions must hold:
1. Operator opted in via `HYPERION_IO_URING_ACCEPT=1`. This
is OFF by default for 2.12.0 — flipping the default to ON
is a 2.13 decision after production-soak.
2. The C ext was compiled with `HAVE_LIBURING` (probed at
gem-install time via `extconf.rb` — needs `liburing-dev`
headers). Builds without it ship the stub method that
returns `:unavailable` regardless of the env var.
3. `Hyperion::Http::PageCache.run_static_io_uring_loop` is
defined (paranoia: the symbol always exists on builds
that loaded the C ext, but the check keeps us from
NameError'ing on partial installs).
4. A liburing runtime probe — opening a tiny ring with
`io_uring_queue_init`. The probe lives inside the C
method itself (`run_static_io_uring_loop` returns
`:unavailable` if `io_uring_queue_init` fails); we
don't pre-probe here because that would require holding
a ring open across the eligibility check, and the
penalty for "engaged but probe-fail at run time" is
one cheap fall-through to the `accept4` path.
69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/hyperion/server/connection_loop.rb', line 69 def io_uring_eligible? return false unless available? return false unless ::Hyperion::Http::PageCache.respond_to?(:run_static_io_uring_loop) return false unless ::Hyperion::Http::PageCache.respond_to?(:io_uring_loop_compiled?) && ::Hyperion::Http::PageCache.io_uring_loop_compiled? env = ENV['HYPERION_IO_URING_ACCEPT'] return false unless env %w[1 on true yes].include?(env.downcase) end |