Module: RactorRailsShim::FallbackBuilder
- Extended by:
- RoleDefaults
- Defined in:
- lib/ractor_rails_shim/roles/fallback_builder.rb
Defined Under Namespace
Modules: ShareableCopy, ValueLookup
Class Method Summary collapse
-
.build! ⇒ Object
Build the shareable fallback for every class_attribute / mattr_accessor value the shim has rerouted.
-
.built? ⇒ Boolean
Has build! run? Lives on FallbackBuilder (Issue #24 — own your own state), NOT on the facade singleton.
- .class_attr_values ⇒ Object
- .class_attributes ⇒ Object
-
.configure(safe_const_get: nil, replace_unshareable_procs: nil, replace_locks_and_concurrent_maps: nil, reassign_shareable_const: nil, class_attributes: nil, class_attr_values: nil, shareable_mattr_defaults: nil, storage: nil, value_lookup_chain: nil) ⇒ Object
Inject the callable + registry collaborators.
- .reassign_shareable_const ⇒ Object
- .replace_locks_and_concurrent_maps ⇒ Object
- .replace_unshareable_procs ⇒ Object
-
.reset_built! ⇒ Object
Clear the built flag.
-
.reset_configuration ⇒ Object
Restore the default (facade-lookup) collaborators.
- .safe_const_get ⇒ Object
-
.shareable_copy(val) ⇒ Object
Return a fresh copy of a mutable default container (Hash/Array) so the fallback entry is independent.
- .shareable_mattr_defaults ⇒ Object
- .storage ⇒ Object
-
.try_make_shareable(val, owner_name, attr_name, default: false) ⇒ Object
Best-effort attempt to make
valshareable (callable-replacement for Procs + lock-replacement + make_shareable). - .value_lookup_chain ⇒ Object
Methods included from RoleDefaults
default_funnel, default_reassign_shareable_const, default_safe_const_get
Class Method Details
.build! ⇒ Object
Build the shareable fallback for every class_attribute / mattr_accessor value the shim has rerouted. For each registered attribute we:
1. Read the main-ractor value (from its IES slot, which `redefine`
seeded at class_attribute-definition time).
2. Make it shareable (deep-freeze + callable-replacement for any Procs
it holds — same technique as make_app_shareable!, applied to the
config sub-graph).
3. Store under the IES key in a frozen Hash on RactorRailsShim, which
is readable from every Ractor (it's a constant).
Workers' class_attribute readers fall back to this when their own IES slot is nil. Must run in the main Ractor. Idempotent (guarded by
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 220 def self.build! return nil if @built @built = true fallback = {} chain = value_lookup_chain lookup_ctx = { storage: storage, class_attr_values: class_attr_values, main: ::Ractor.main?, safe_const_get: safe_const_get, } class_attributes.each do |(owner_name, attr_name, ies_key, default_val)| # Skip the Rails logger — it's intrinsically unshareable (IO + Mutex + # mutable formatter) and workers build their own per-Ractor logger # via the patched reader. Trying to make it shareable would freeze the # IO, breaking logging in main too. next if owner_name == "Rails" && attr_name == :logger # Walk the ValueLookup chain to resolve the value. The first # non-nil result wins; if all links return nil, val stays nil. val = nil chain.each do |link| val = link.lookup(owner_name, attr_name, ies_key, **lookup_ctx) break unless val.nil? end shareable_val = nil # Try the live value first. if !val.nil? shareable_val = try_make_shareable(val, owner_name, attr_name) end # If the live value couldn't be shared (e.g. __callbacks holds # self-capturing Procs), fall back to the definition-time default. # For a frozen shared app this is correct: boot-time callbacks # already ran in main; workers treat them as already-run # (empty/no-op). The default is dup'd if it's a mutable container # (Hash/Array) so each entry in the fallback is an independent # shareable copy. if shareable_val.nil? && !default_val.nil? shareable_val = try_make_shareable(shareable_copy(default_val), owner_name, attr_name, default: true) end fallback[ies_key] = shareable_val if shareable_val end fallback.freeze Ractor.make_shareable(fallback) # Make the shareable mattr-defaults subset shareable too (workers # read it via the constant). Frozen + reassigned via the centralized # helper. shareable_mattr_defaults.freeze Ractor.make_shareable(shareable_mattr_defaults) reassign_shareable_const.call(:SHAREABLE_FALLBACK, fallback) reassign_shareable_const.call(:SHAREABLE_MATTR_DEFAULTS, shareable_mattr_defaults) fallback end |
.built? ⇒ Boolean
Has build! run? Lives on FallbackBuilder (Issue #24 — own your own state), NOT on the facade singleton.
199 200 201 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 199 def self.built? @built end |
.class_attr_values ⇒ Object
106 107 108 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 106 def self.class_attr_values @class_attr_values || RactorRailsShim::Registry.class_attr_values end |
.class_attributes ⇒ Object
102 103 104 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 102 def self.class_attributes @class_attributes || RactorRailsShim::Registry.class_attributes end |
.configure(safe_const_get: nil, replace_unshareable_procs: nil, replace_locks_and_concurrent_maps: nil, reassign_shareable_const: nil, class_attributes: nil, class_attr_values: nil, shareable_mattr_defaults: nil, storage: nil, value_lookup_chain: nil) ⇒ Object
Inject the callable + registry collaborators. The callables:
safe_const_get(path) → value|nil; replace_unshareable_procs(val)
and replace_locks_and_concurrent_maps(val) mutate val in place;
reassign_shareable_const(sym, value) reassigns the shareable
constant. The registries: class_attributes (CLASS_ATTRIBUTES
array), class_attr_values (CLASS_ATTR_VALUES store),
shareable_mattr_defaults (SHAREABLE_MATTR_DEFAULTS array),
storage (IES storage, hash-like: storage). The lookup
chain: value_lookup_chain (Array of lookup modules, each
responding to .lookup(owner, attr_name, ies_key, **ctx) -> val|nil).
Passing nil for any (or calling reset_configuration) restores
the facade-lookup default for that collaborator.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 57 def self.configure(safe_const_get: nil, replace_unshareable_procs: nil, replace_locks_and_concurrent_maps: nil, reassign_shareable_const: nil, class_attributes: nil, class_attr_values: nil, shareable_mattr_defaults: nil, storage: nil, value_lookup_chain: nil) @safe_const_get = safe_const_get @replace_unshareable_procs = replace_unshareable_procs @replace_locks_and_concurrent_maps = replace_locks_and_concurrent_maps @reassign_shareable_const = reassign_shareable_const @class_attributes = class_attributes @class_attr_values = class_attr_values @shareable_mattr_defaults = shareable_mattr_defaults @storage = storage @value_lookup_chain = value_lookup_chain end |
.reassign_shareable_const ⇒ Object
98 99 100 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 98 def self.reassign_shareable_const @reassign_shareable_const || RactorRailsShim.method(:_reassign_shareable_const) end |
.replace_locks_and_concurrent_maps ⇒ Object
94 95 96 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 94 def self.replace_locks_and_concurrent_maps @replace_locks_and_concurrent_maps || RactorRailsShim::ShareabilityTraversal.method(:replace_locks_and_concurrent_maps!) end |
.replace_unshareable_procs ⇒ Object
90 91 92 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 90 def self.replace_unshareable_procs @replace_unshareable_procs || RactorRailsShim::ShareabilityTraversal.method(:replace_unshareable_procs!) end |
.reset_built! ⇒ Object
Clear the built flag. Test seam + reinstall seam.
204 205 206 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 204 def self.reset_built! @built = false end |
.reset_configuration ⇒ Object
Restore the default (facade-lookup) collaborators. Test seam.
74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 74 def self.reset_configuration @safe_const_get = nil @replace_unshareable_procs = nil @replace_locks_and_concurrent_maps = nil @reassign_shareable_const = nil @class_attributes = nil @class_attr_values = nil @shareable_mattr_defaults = nil @storage = nil @value_lookup_chain = nil end |
.safe_const_get ⇒ Object
86 87 88 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 86 def self.safe_const_get @safe_const_get || default_safe_const_get end |
.shareable_copy(val) ⇒ Object
Return a fresh copy of a mutable default container (Hash/Array) so the fallback entry is independent. Frozen/shareable defaults pass through. Uses the ShareableCopy dispatch table (Issue #40, POODR §5b).
313 314 315 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 313 def self.shareable_copy(val) ShareableCopy.call(val) end |
.shareable_mattr_defaults ⇒ Object
110 111 112 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 110 def self.shareable_mattr_defaults @shareable_mattr_defaults || RactorRailsShim::Registry.shareable_mattr_defaults end |
.storage ⇒ Object
114 115 116 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 114 def self.storage @storage || RactorRailsShim.storage end |
.try_make_shareable(val, owner_name, attr_name, default: false) ⇒ Object
Best-effort attempt to make val shareable (callable-replacement for
Procs + lock-replacement + make_shareable). Returns the shareable val,
or nil if it can't be made shareable. On failure, emits a warning
(unless default: — defaults are expected to sometimes be unshareable,
so we skip the noise).
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 284 def self.try_make_shareable(val, owner_name, attr_name, default: false) # __callbacks and validators hold callback chains / validator # instances with self-capturing Procs that can NEVER be made # shareable. This is expected: workers correctly treat callbacks as # already-run (the nil-safe run_callbacks patch yields the block # directly). Skip the attempt entirely — don't waste cycles traversing # the graph, and don't emit warnings for known-expected failures. attr_sym = attr_name.to_s return nil if attr_sym.end_with?("__callbacks") || attr_sym.end_with?("__validators") || attr_sym.end_with?("default_connection_handler") begin replace_unshareable_procs.call(val) replace_locks_and_concurrent_maps.call(val) Ractor.make_shareable(val) val rescue StandardError => e unless default warn "ractor-rails-shim: could not make attribute " \ "#{owner_name}##{attr_name} shareable (#{e.class}: #{e.[0,80]}); workers will fall back to default or nil" end nil end end |
.value_lookup_chain ⇒ Object
118 119 120 |
# File 'lib/ractor_rails_shim/roles/fallback_builder.rb', line 118 def self.value_lookup_chain @value_lookup_chain || ValueLookup::DEFAULT_CHAIN end |