Module: Hyperion::FiberLocal
- Defined in:
- lib/hyperion/fiber_local.rb
Overview
FiberLocal — tooling for fiber-local request scope under the Async scheduler.
## Background
Under fiber-per-request concurrency (Hyperion Phase 2+, Falcon), all in-flight requests share the same OS thread. Code that stores per-request state on ‘Thread.current` would leak between requests.
**Ruby 3.2+ already solves the most common case:** ‘Thread.current = v` writes to FIBER-local storage, not thread-local storage. Each fiber’s ‘Thread.current` is independent. This is what Hyperion relies on.
The remaining footgun is ‘Thread.current.thread_variable_set`, which IS genuinely thread-shared and will leak across fiber-scheduled requests. Old Rails code (< 7.0) sometimes used this. Modern Rails uses `ActiveSupport::IsolatedExecutionState` (which routes to Fiber storage), so well-maintained apps are not affected.
## What this module provides
‘Hyperion::FiberLocal.verify_environment!` — sanity check that the current Ruby actually isolates `Thread.current` per-fiber. Raises if not (which would only happen on Ruby < 3.2).
‘Hyperion::FiberLocal.install!` — opt-in monkey-patch that ALSO routes `thread_variable_get`/`thread_variable_set` to fiber storage. Use only if you know your app stores request scope via thread variables and you accept the trade-offs (genuine thread-pool patterns will break).
Class Method Summary collapse
-
.install! ⇒ Object
Opt-in patch that routes thread_variable_get/set to fiber storage.
- .installed? ⇒ Boolean
-
.uninstall! ⇒ Object
Test-only undo.
-
.verify_environment! ⇒ Object
Confirm that the current Ruby treats Thread.current as fiber-local.
Class Method Details
.install! ⇒ Object
Opt-in patch that routes thread_variable_get/set to fiber storage. Most apps DO NOT need this — Ruby 3.2+ symbol-keyed Thread.current[] is already fiber-local. Only install! if your app uses thread_variable_set for request scope.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/hyperion/fiber_local.rb', line 65 def install! return if @installed ::Thread.class_eval do alias_method :__hyperion_orig_tvar_get, :thread_variable_get alias_method :__hyperion_orig_tvar_set, :thread_variable_set define_method(:thread_variable_get) do |key| sym = key.to_sym storage = ::Fiber.current.storage return storage[sym] if storage&.key?(sym) __hyperion_orig_tvar_get(key) end define_method(:thread_variable_set) do |key, value| ::Fiber.current.storage ||= {} ::Fiber.current.storage[key.to_sym] = value end end @installed = true end |
.installed? ⇒ Boolean
36 37 38 |
# File 'lib/hyperion/fiber_local.rb', line 36 def installed? @installed end |
.uninstall! ⇒ Object
Test-only undo. Not promised for production.
90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/hyperion/fiber_local.rb', line 90 def uninstall! return unless @installed ::Thread.class_eval do alias_method :thread_variable_get, :__hyperion_orig_tvar_get alias_method :thread_variable_set, :__hyperion_orig_tvar_set remove_method :__hyperion_orig_tvar_get remove_method :__hyperion_orig_tvar_set end @installed = false end |
.verify_environment! ⇒ Object
Confirm that the current Ruby treats Thread.current as fiber-local. Raises NotImplementedError on older Ruby where the leak still exists.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/hyperion/fiber_local.rb', line 42 def verify_environment! marker = :__hyperion_fiber_isolation_check__ ::Thread.current[marker] = :outer observed = nil ::Fiber.new { observed = ::Thread.current[marker] }.resume unless observed.nil? raise NotImplementedError, 'Thread.current[:k] is NOT fiber-local on this Ruby. ' \ 'Hyperion requires Ruby 3.2+ for safe fiber-per-request scope. ' \ "Got Ruby #{RUBY_VERSION}." end true ensure ::Thread.current[marker] = nil end |