Module: RactorRailsShim::WorkerAppFactory

Defined in:
lib/ractor_rails_shim/roles/worker_app_factory.rb

Class Method Summary collapse

Class Method Details

.autoloadersObject



40
41
42
# File 'lib/ractor_rails_shim/roles/worker_app_factory.rb', line 40

def self.autoloaders
  @autoloaders
end

.build(frozen_app) ⇒ Object

Build the shareable Rack app handed to kino. Captures the application's constants in the main Ractor and wraps the frozen, shareable app in a WorkerApp that rebinds those constants (and initializes the worker's ActiveRecord connection) on the first request served by each worker Ractor. Returns a shareable WorkerApp instance — frozen and Ractor.make_shareable'd — so it can be passed directly to Ractor.new(WorkerAppFactory.build(app)) { |a| a.call(env) } without the caller having to know the shareability contract.



117
118
119
120
121
122
# File 'lib/ractor_rails_shim/roles/worker_app_factory.rb', line 117

def self.build(frozen_app)
  bindings = capture_constants!
  wa = worker_app_class.new(frozen_app, bindings)
  wa.freeze
  Ractor.make_shareable(wa)
end

.capture_constants!Object

Capture a frozen name -> object map for every constant the application's Zeitwerk loaders manage. Runs in the MAIN Ractor, after eager load, where all app constants are defined. The map travels to worker Ractors, which use it to (re)bind the constant names into their own namespaces.

Why this is needed: a Ractor boundary does NOT share top-level constant names — only the class/module objects reachable from the frozen shared app graph cross the boundary. A worker Ractor therefore sees RactorRailsShim, ActiveRecord, ApplicationRecord, the controllers, etc. (objects reachable from the app), but NOT the application's own model constants (e.g. Post): the object is in the graph, but its name is not bound in the worker, so PostsController#index's Post reference raises NameError. Rebinding the captured names fixes it without re-running autoloading (which is itself impossible in a worker, since Zeitwerk::Loader.new raises IsolationError off the main Ractor).



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/ractor_rails_shim/roles/worker_app_factory.rb', line 69

def self.capture_constants!
  map = {}
  al = autoloaders
  cget = const_get_callable || Object.method(:const_get)
  if al.nil?
    # Backward compat: fall back to Rails.autoloaders when no explicit
    # autoloaders injected (seam not yet called).
    if defined?(::Rails) && Rails.respond_to?(:autoloaders)
      al = Rails.autoloaders
    else
      return map.freeze
    end
  end
  loaders =
    if al.respond_to?(:main) && al.respond_to?(:once)
      [al.main, al.once]
    elsif al.respond_to?(:each)
      al.to_a
    else
      []
    end
  loaders.each do |loader|
    next unless loader && loader.respond_to?(:all_expected_cpaths)
    begin
      loader.all_expected_cpaths.values.each do |cpath|
        obj = cget.call(cpath) rescue next
        begin
          Ractor.make_shareable(obj) unless Ractor.shareable?(obj)
        rescue StandardError
          next
        end
        map[cpath] = obj if Ractor.shareable?(obj)
      end
    rescue StandardError => e
      warn "[ractor_rails_shim] capture_app_constants!: #{e.class}: #{e.message}"
    end
  end
  map.freeze
end

.configure(autoloaders: nil, const_get: nil, worker_app_class: nil) ⇒ Object



28
29
30
31
32
# File 'lib/ractor_rails_shim/roles/worker_app_factory.rb', line 28

def self.configure(autoloaders: nil, const_get: nil, worker_app_class: nil)
  @autoloaders = autoloaders
  @const_get_callable = const_get
  @worker_app_class = worker_app_class
end

.const_get_callableObject



44
45
46
# File 'lib/ractor_rails_shim/roles/worker_app_factory.rb', line 44

def self.const_get_callable
  @const_get_callable
end

.reset_configurationObject



34
35
36
37
38
# File 'lib/ractor_rails_shim/roles/worker_app_factory.rb', line 34

def self.reset_configuration
  @autoloaders = nil
  @const_get_callable = nil
  @worker_app_class = nil
end

.worker_app_classObject



48
49
50
# File 'lib/ractor_rails_shim/roles/worker_app_factory.rb', line 48

def self.worker_app_class
  @worker_app_class || WorkerApp
end