Module: RactorRailsShim::ConstantShareabilizer
- Extended by:
- RoleDefaults
- Defined in:
- lib/ractor_rails_shim/roles/constant_shareabilizer.rb
Class Method Summary collapse
-
.applied? ⇒ Boolean
Has apply! fully resolved? Lives on ConstantShareabilizer (Issue #24 — own your own state), NOT on the facade singleton.
-
.apply! ⇒ Object
Run after Rails is fully booted (after Rails.application.initialize!) and BEFORE spawning worker Ractors.
-
.configure(funnel: nil, register_patch: nil, introspectable: nil, noop_lock_class: nil, shareable_constants_registry: nil) ⇒ Object
Inject the callable/class collaborators.
- .funnel ⇒ Object
-
.install ⇒ Object
Register the patch + apply it now if ActiveSupport is loaded.
- .introspectable ⇒ Object
-
.make_shareable!(const_path) ⇒ Object
Resolve a constant path string to a value, and if it exists and is not already shareable, replace it with its shareable (deep-frozen) version.
-
.make_value_shareable(val) ⇒ Object
Best-effort shareable replacement for a constant value.
- .noop_lock_class ⇒ Object
- .register_patch ⇒ Object
-
.reset_applied! ⇒ Object
Clear the applied flag.
-
.reset_configuration ⇒ Object
Restore the default (facade-lookup) collaborators.
-
.safe_const_get(path, inherit: true) ⇒ Object
Resolve a constant path string (e.g. "A::B::C") to its value, returning nil if any segment isn't defined.
-
.shareable_constants ⇒ Object
The registry of constant path strings whose values are made shareable at boot.
- .shareable_constants_registry ⇒ Object
-
.split_const_path(path) ⇒ Object
Split "A::B::C" into [A::B (module), :C].
Methods included from RoleDefaults
default_funnel, default_reassign_shareable_const, default_safe_const_get
Class Method Details
.applied? ⇒ Boolean
Has apply! fully resolved? Lives on ConstantShareabilizer (Issue #24 — own your own state), NOT on the facade singleton.
70 71 72 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 70 def self.applied? @applied end |
.apply! ⇒ Object
Run after Rails is fully booted (after Rails.application.initialize!)
and BEFORE spawning worker Ractors. Re-attempts to make every
registered constant shareable; constants that didn't exist at install
time (e.g. Rails::Railtie, loaded after module Rails opens) get
fixed here. Safe to call multiple times; already-shareable constants
are no-ops. MUST run in the main Ractor (const_set writes the
constant table). Public wrapper is prepare_for_ractors! on the
facade.
126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 126 def self.apply! return if @applied # Only set the done flag when every registered constant was made # shareable (or already was). If any returned false (constant doesn't # exist yet), leave the flag unset so a later call (from # make_app_shareable! or prepare_for_ractors!) retries the now-loadable # constants — otherwise workers hit IsolationError on the unshareable # values that were missed on the first pass. all_resolved = shareable_constants.map { |path| make_shareable!(path) }.all? @applied = true if all_resolved end |
.configure(funnel: nil, register_patch: nil, introspectable: nil, noop_lock_class: nil, shareable_constants_registry: nil) ⇒ Object
Inject the callable/class collaborators. funnel responds to
call(label) { block } (runs the block, rescues StandardError —
matches _swallow). register_patch responds to call(name, ver).
introspectable responds to call(val) returning truthy/nil (matches
_introspectable?). noop_lock_class responds to .new (matches
NoOpLock). shareable_constants_registry is the array of constant
path strings. Passing nil for any (or calling
reset_configuration) restores the facade-lookup default.
49 50 51 52 53 54 55 56 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 49 def self.configure(funnel: nil, register_patch: nil, introspectable: nil, noop_lock_class: nil, shareable_constants_registry: nil) @funnel = funnel @register_patch = register_patch @introspectable = introspectable @noop_lock_class = noop_lock_class @shareable_constants_registry = shareable_constants_registry end |
.funnel ⇒ Object
79 80 81 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 79 def self.funnel @funnel || default_funnel end |
.install ⇒ Object
Register the patch + apply it now if ActiveSupport is loaded. Called at install time; if ActiveSupport isn't loaded yet, the constants don't exist, so we re-run from patch_rails_module! (which fires once Rails — and thus ActiveSupport — is defined). Guarded by
111 112 113 114 115 116 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 111 def self.install register_patch.call(:shareable_constants, "8.1") return unless defined?(::ActiveSupport) apply! end |
.introspectable ⇒ Object
87 88 89 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 87 def self.introspectable @introspectable || RactorRailsShim::ShareabilityTraversal.method(:introspectable?) end |
.make_shareable!(const_path) ⇒ Object
Resolve a constant path string to a value, and if it exists and is not already shareable, replace it with its shareable (deep-frozen) version. Returns true if the constant was made shareable (or already was); false if it doesn't exist yet (caller may retry).
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 142 def self.make_shareable!(const_path) owner, name = split_const_path(const_path) return false unless owner && name return true if owner.const_defined?(name, false) == false val = owner.const_get(name, false) return true if Ractor.shareable?(val) shareable = make_value_shareable(val) return true unless shareable # Deep-freeze and reassign. Ractor.make_shareable mutates `val` in # place (freezing it and its reachable objects) and returns it. # const_set warns "already initialized constant" because Rails' # environment_inquirer.rb defined the constant first. The reassign is # intentional (we're replacing the mutable value with its frozen # shareable twin), so silence that one warning. verbose, $VERBOSE = $VERBOSE, nil begin owner.const_set(name, shareable) ensure $VERBOSE = verbose end true end |
.make_value_shareable(val) ⇒ Object
Best-effort shareable replacement for a constant value. Any object that
responds to :synchronize (the duck type for a Mutex-like lock — covers
Monitor, Mutex, and third-party lock classes alike) becomes a NoOpLock
(used as sentinel sentinels, e.g. PRIMARY_KEY_NOT_SET) can't be frozen
(BasicObject has no #freeze method) — replace with a frozen Symbol.
Everything else is deep-frozen via Ractor.make_shareable; if that
fails (e.g. a Proc, or a Concurrent::Map / TypeMap holding Procs —
both intrinsically unshareable and needing upstream Rails changes),
returns nil and the constant is left as-is (the worker will raise a
clear IsolationError on read).
Uses the existing _introspectable? helper (make_shareable.rb) instead
of ad-hoc rescue false guards. BasicObject subclasses don't define
is_a?/respond_to? (Kernel not included); _introspectable? safely
detects this via a guarded respond_to?(:is_a?) check.
183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 183 def self.make_value_shareable(val) if introspectable.call(val) && val.respond_to?(:synchronize) Ractor.make_shareable(noop_lock_class.new) elsif !introspectable.call(val) || !val.respond_to?(:freeze) # Non-introspectable (BasicObject without is_a?) OR lacks #freeze # (BasicObject subclasses). Replace with a frozen Symbol sentinel — # it's compared with `equal?`, and a frozen Symbol is always # shareable. Ractor.make_shareable(:"__shim_unshareable_sentinel__") else funnel.call("make_value_shareable #{val.class}") { Ractor.make_shareable(val) } end end |
.noop_lock_class ⇒ Object
91 92 93 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 91 def self.noop_lock_class @noop_lock_class || RactorRailsShim.singleton_class.const_get(:NoOpLock) end |
.register_patch ⇒ Object
83 84 85 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 83 def self.register_patch @register_patch || RactorRailsShim.method(:_register_patch) end |
.reset_applied! ⇒ Object
Clear the applied flag. Test seam + reinstall seam.
75 76 77 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 75 def self.reset_applied! @applied = false end |
.reset_configuration ⇒ Object
Restore the default (facade-lookup) collaborators. Test seam.
59 60 61 62 63 64 65 66 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 59 def self.reset_configuration @applied = false @funnel = nil @register_patch = nil @introspectable = nil @noop_lock_class = nil @shareable_constants_registry = nil end |
.safe_const_get(path, inherit: true) ⇒ Object
Resolve a constant path string (e.g. "A::B::C") to its value, returning nil if any segment isn't defined. When inherit is false, each segment is looked up only in its parent's own constant table (const_get name, false), matching the original no-inherit lookups in _freeze_messages_constants!. Replaces dense rescue/& chains like:
(Object.const_get(:A) rescue nil)&.const_get(:B, false) rescue nil
203 204 205 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 203 def self.safe_const_get(path, inherit: true) path.split("::").inject(Object) { |ns, n| ns.const_get(n, inherit) } rescue nil end |
.shareable_constants ⇒ Object
The registry of constant path strings whose values are made shareable at boot. Lives on RactorRailsShim (per-concern files concat into it); this reader delegates so call sites don't reach past the role object.
102 103 104 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 102 def self.shareable_constants shareable_constants_registry end |
.shareable_constants_registry ⇒ Object
95 96 97 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 95 def self.shareable_constants_registry @shareable_constants_registry || RactorRailsShim::Registry.shareable_constants end |
.split_const_path(path) ⇒ Object
Split "A::B::C" into [A::B (module), :C]. Returns [nil, nil] if the parent isn't defined.
209 210 211 212 213 214 215 |
# File 'lib/ractor_rails_shim/roles/constant_shareabilizer.rb', line 209 def self.split_const_path(path) parts = path.split("::") return [Object, parts.first.to_sym] if parts.size == 1 parent = safe_const_get(parts[0...-1].join("::")) return [nil, nil] unless parent [parent, parts.last.to_sym] end |