Module: RactorRailsShim::Freezers::ShareableClassIvarFreezer

Extended by:
RoleDefaults
Defined in:
lib/ractor_rails_shim/roles/freezers.rb

Overview

ShareableClassIvarFreezer: walks SHAREABLE_CLASS_IVARS (class name, ivar name pairs), reads each ivar, makes it Ractor-shareable (deep-frozen), and writes it back so workers read the shareable copy. Also pre-touches memoizing accessors so workers don't try to write the ivar lazily (which would raise FrozenError on the frozen class). Extracted from _freeze_shareable_class_ivars! in make_shareable.rb (Issue #27, POODR §6a Modules & Roles).

Constant Summary collapse

PRE_TOUCH =

Mutable registry of [class_name, method_name] pairs to pre-touch before the main shareable-class-ivar freeze. A pre-touch forces lazy memoizing accessors to populate in the MAIN Ractor so workers don't try to write the (now frozen) ivar on first read. Downstream apps can register their own entries via add_pre_touch.

[
  ["ActiveSupport::Editor", :current],
  ["Warden::Strategies", :_strategies],
]

Class Method Summary collapse

Methods included from RoleDefaults

default_funnel, default_reassign_shareable_const, default_safe_const_get

Class Method Details

.add_pre_touch(class_name, method_name) ⇒ Object



311
312
313
# File 'lib/ractor_rails_shim/roles/freezers.rb', line 311

def self.add_pre_touch(class_name, method_name)
  PRE_TOUCH << [class_name, method_name] unless PRE_TOUCH.any? { |n, m| n == class_name && m == method_name }
end

.callObject



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/ractor_rails_shim/roles/freezers.rb', line 315

def self.call
  RactorRailsShim::Registry.shareable_class_ivars.each do |(class_name, ivar)|
    mod = safe_const_get.call(class_name)
    next unless mod && mod.instance_variable_defined?(ivar)
    val = mod.instance_variable_get(ivar)
    next if val.nil?
    funnel.call("freeze global ivar #{class_name}#{ivar}") do
      Ractor.make_shareable(val)
      mod.instance_variable_set(ivar, val)
    end
  end
  # Pre-touch memoizing accessors so workers short-circuit instead of
  # writing the (now frozen) ivar on first read (FrozenError avoidance).
  # Walked from the PRE_TOUCH registry (Issue #41) — data-driven
  # instead of hardcoded.
  PRE_TOUCH.each do |class_name, method_name|
    mod = safe_const_get.call(class_name)
    next unless mod && mod.respond_to?(method_name, true)
    funnel.call("pre-touch #{class_name}.#{method_name}") do
      mod.send(method_name)
    end
  end
  true
end

.configure(funnel: nil, safe_const_get: nil) ⇒ Object



283
284
285
286
# File 'lib/ractor_rails_shim/roles/freezers.rb', line 283

def self.configure(funnel: nil, safe_const_get: nil)
  @funnel = funnel
  @safe_const_get = safe_const_get
end

.funnelObject



293
294
295
# File 'lib/ractor_rails_shim/roles/freezers.rb', line 293

def self.funnel
  @funnel || default_funnel
end

.reset_configurationObject



288
289
290
291
# File 'lib/ractor_rails_shim/roles/freezers.rb', line 288

def self.reset_configuration
  @funnel = nil
  @safe_const_get = nil
end

.safe_const_getObject



297
298
299
# File 'lib/ractor_rails_shim/roles/freezers.rb', line 297

def self.safe_const_get
  @safe_const_get || default_safe_const_get
end