ractor-rails-shim 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +140 -2
- data/README.md +90 -27
- data/lib/ractor_rails_shim/patches/action_controller.rb +10 -1
- data/lib/ractor_rails_shim/patches/action_dispatch.rb +1 -1
- data/lib/ractor_rails_shim/patches/action_view.rb +2 -2
- data/lib/ractor_rails_shim/patches/active_model_attribute.rb +17 -0
- data/lib/ractor_rails_shim/patches/active_support.rb +20 -20
- data/lib/ractor_rails_shim/patches/activerecord.rb +102 -20
- data/lib/ractor_rails_shim/patches/class_attribute.rb +17 -4
- data/lib/ractor_rails_shim/patches/core.rb +121 -51
- data/lib/ractor_rails_shim/patches/devise.rb +1 -1
- data/lib/ractor_rails_shim/patches/execution_wrapper.rb +1 -1
- data/lib/ractor_rails_shim/patches/kaminari.rb +1 -1
- data/lib/ractor_rails_shim/patches/make_shareable.rb +126 -37
- data/lib/ractor_rails_shim/patches/mattr_accessor.rb +2 -2
- data/lib/ractor_rails_shim/patches/orm_adapter.rb +1 -1
- data/lib/ractor_rails_shim/patches/rack.rb +5 -5
- data/lib/ractor_rails_shim/patches/rails_module.rb +13 -6
- data/lib/ractor_rails_shim/patches/zeitwerk_registry.rb +8 -1
- data/lib/ractor_rails_shim/version.rb +1 -1
- data/lib/ractor_rails_shim/version_check.rb +10 -6
- metadata +3 -3
|
@@ -349,8 +349,16 @@ module RactorRailsShim
|
|
|
349
349
|
# calls `#to_proc` and then requires the result to be a real Proc.
|
|
350
350
|
# Return a frozen no-op lambda so the implicit conversion succeeds and
|
|
351
351
|
# the (side-effect-free) call is a true no-op, matching `#call`.
|
|
352
|
+
#
|
|
353
|
+
# The lambda is a shareable constant (frozen at class load), NOT
|
|
354
|
+
# memoized on `@_to_proc`: NoOpProc instances are deep-frozen by
|
|
355
|
+
# `Ractor.make_shareable` during `make_app_shareable!`, so an
|
|
356
|
+
# `@_to_proc ||= ...` write would raise FrozenError on the frozen
|
|
357
|
+
# instance. A constant avoids the write entirely and is safe to share.
|
|
358
|
+
NO_OP_LAMBDA = ->(*) { nil }.freeze
|
|
359
|
+
Ractor.make_shareable(NO_OP_LAMBDA)
|
|
352
360
|
def to_proc
|
|
353
|
-
|
|
361
|
+
NO_OP_LAMBDA
|
|
354
362
|
end
|
|
355
363
|
end
|
|
356
364
|
class Callable
|
|
@@ -539,7 +547,18 @@ module RactorRailsShim
|
|
|
539
547
|
# Doesn't dedup procs — must replace every occurrence.
|
|
540
548
|
def _replace_unshareable_procs!(app)
|
|
541
549
|
mw = (app.instance_variable_get(:@app) rescue nil)
|
|
542
|
-
|
|
550
|
+
# Replace every Proc in the graph. The same Proc object can live in
|
|
551
|
+
# many containers (e.g. deprecation behaviors shared across
|
|
552
|
+
# deprecators), and replacing one occurrence doesn't replace the
|
|
553
|
+
# others — so we loop until a fixed point (no Procs left). A safety
|
|
554
|
+
# cap guards against a pathological graph where replacement keeps
|
|
555
|
+
# introducing new Procs (the replacements themselves are NoOpProc/
|
|
556
|
+
# Callable instances, not Procs, so this shouldn't happen, but the
|
|
557
|
+
# cap prevents an infinite loop if a future callable class leaks a
|
|
558
|
+
# Proc). 3 passes was the original magic number; observed real graphs
|
|
559
|
+
# converge in 2.
|
|
560
|
+
max_passes = 8
|
|
561
|
+
max_passes.times do
|
|
543
562
|
procs = _collect_procs(app)
|
|
544
563
|
break if procs.empty?
|
|
545
564
|
procs.each { |proc_obj, _path, parent, ivar| _replace_one_proc(proc_obj, parent, ivar, mw) }
|
|
@@ -573,28 +592,76 @@ module RactorRailsShim
|
|
|
573
592
|
next if seen[o.object_id]
|
|
574
593
|
seen[o.object_id] = true
|
|
575
594
|
next if o.is_a?(Mutex) || o.is_a?(Monitor)
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
rescue => e
|
|
582
|
-
# Some objects (BasicObject) don't support instance_variables
|
|
583
|
-
end
|
|
584
|
-
if o.is_a?(Array)
|
|
585
|
-
o.each_with_index { |e, i| stack << [e, "#{_path}[#{i}]", o, nil] if e }
|
|
586
|
-
elsif o.is_a?(Hash)
|
|
587
|
-
o.each do |k, val|
|
|
588
|
-
stack << [k, "#{_path}.key", o, nil] if k
|
|
589
|
-
stack << [val, "#{_path}[#{k.inspect}]", o, nil] if val
|
|
595
|
+
_each_ivar_and_child(o, _path) do |child, child_path, child_ivar|
|
|
596
|
+
if child_ivar == :__default_proc__
|
|
597
|
+
procs << [child, child_path, o, :__default_proc__]
|
|
598
|
+
else
|
|
599
|
+
stack << [child, child_path, o, child_ivar] if child
|
|
590
600
|
end
|
|
591
|
-
dp = o.default_proc
|
|
592
|
-
procs << [dp, "#{_path}.default_proc", o, :__default_proc__] if dp
|
|
593
601
|
end
|
|
594
602
|
end
|
|
595
603
|
procs
|
|
596
604
|
end
|
|
597
605
|
|
|
606
|
+
# Enumerate every child reference of `o` for the graph traversals:
|
|
607
|
+
# - instance variables (yields [value, "path.iv", iv])
|
|
608
|
+
# - Array / Set / Enumerable members (yields [member, "path[i]", nil])
|
|
609
|
+
# - Hash pairs (yields [key, "path.key", nil] and [val, "path[k]", nil])
|
|
610
|
+
# - Hash#default_proc (yields [proc, "path.default_proc", :__default_proc__])
|
|
611
|
+
# - Struct members (yields [value, "path.member", nil] via #each_pair)
|
|
612
|
+
#
|
|
613
|
+
# Pre-fix the traversals only handled Array and Hash, so a Mutex or Proc
|
|
614
|
+
# nested inside a Set (Rails uses Set in several caches, e.g.
|
|
615
|
+
# ActionDispatch::Journey::Routes) or a Struct was missed and left
|
|
616
|
+
# unshareable. Centralized here so _collect_procs and
|
|
617
|
+
# _replace_locks_and_concurrent_maps! share the same container coverage.
|
|
618
|
+
def _each_ivar_and_child(o, path)
|
|
619
|
+
begin
|
|
620
|
+
o.instance_variables.each do |iv|
|
|
621
|
+
begin; v = o.instance_variable_get(iv); rescue; next; end
|
|
622
|
+
yield v, "#{path}.#{iv}", iv
|
|
623
|
+
end
|
|
624
|
+
rescue => e
|
|
625
|
+
# BasicObject or frozen objects don't support instance_variables
|
|
626
|
+
end
|
|
627
|
+
if o.is_a?(Hash)
|
|
628
|
+
o.each do |k, val|
|
|
629
|
+
yield k, "#{path}.key", nil
|
|
630
|
+
yield val, "#{path}[#{k.inspect}]", nil
|
|
631
|
+
end
|
|
632
|
+
dp = o.default_proc
|
|
633
|
+
yield dp, "#{path}.default_proc", :__default_proc__ if dp
|
|
634
|
+
elsif o.is_a?(Array)
|
|
635
|
+
o.each_with_index { |e, i| yield e, "#{path}[#{i}]", nil }
|
|
636
|
+
elsif o.is_a?(::Set)
|
|
637
|
+
o.each_with_index { |e, i| yield e, "#{path}.set[#{i}]", nil }
|
|
638
|
+
elsif o.is_a?(::Struct)
|
|
639
|
+
o.each_pair { |name, val| yield val, "#{path}.#{name}", nil }
|
|
640
|
+
elsif _enumerable_but_not_basic?(o)
|
|
641
|
+
# Generic Enumerable fallback (Range, Enumerator, custom Enumerable
|
|
642
|
+
# mixes). Skip String/Hash/Array/Set/Struct — already handled. Best
|
|
643
|
+
# effort; rescue per-element in case #each raises for some members.
|
|
644
|
+
idx = -1
|
|
645
|
+
o.each do |e|
|
|
646
|
+
idx += 1
|
|
647
|
+
yield e, "#{path}.each[#{idx}]", nil
|
|
648
|
+
end rescue nil
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
# True if `o` is Enumerable but NOT one of the container types with a
|
|
653
|
+
# dedicated branch in _each_ivar_and_child. Used to gate the generic
|
|
654
|
+
# Enumerable fallback so we don't double-walk Array/Hash/etc.
|
|
655
|
+
def _enumerable_but_not_basic?(o)
|
|
656
|
+
return false unless o.is_a?(::Enumerable)
|
|
657
|
+
return false if o.is_a?(::Array) || o.is_a?(::Hash) || o.is_a?(::Set) || o.is_a?(::Struct)
|
|
658
|
+
return false if o.is_a?(::String) # String is Enumerable (chars) but not a container of refs we want
|
|
659
|
+
true
|
|
660
|
+
rescue NoMethodError
|
|
661
|
+
# BasicObject without Kernel — not Enumerable.
|
|
662
|
+
false
|
|
663
|
+
end
|
|
664
|
+
|
|
598
665
|
def _replace_one_proc(proc_obj, parent, ivar, mw)
|
|
599
666
|
src = proc_obj.source_location&.first || ""
|
|
600
667
|
replacement =
|
|
@@ -619,8 +686,18 @@ module RactorRailsShim
|
|
|
619
686
|
elsif src.end_with?(DEVISE_SCOPE_LOC)
|
|
620
687
|
_devise_mapping_replacement(proc_obj, parent)
|
|
621
688
|
elsif src.end_with?(MAPPER_LOC) && ivar == :@strategy
|
|
622
|
-
|
|
623
|
-
|
|
689
|
+
# Identify SERVE vs CALL by OBJECT IDENTITY against the actual
|
|
690
|
+
# ActionDispatch constants, NOT by source_location line number.
|
|
691
|
+
# The value stored in @strategy IS the constant object (Rails
|
|
692
|
+
# assigns it via `@strategy = strategy` where `strategy` is passed
|
|
693
|
+
# as Constraints::SERVE or Constraints::CALL), so `equal?` is the
|
|
694
|
+
# robust identifier — it survives any Rails patch release that
|
|
695
|
+
# shifts the constant definitions by a line or two, where the old
|
|
696
|
+
# `line == 32` check would silently swap the two strategies and
|
|
697
|
+
# break routing. Falls through to NoOpProc if the Proc isn't
|
|
698
|
+
# either constant (defensive: shouldn't happen, but never
|
|
699
|
+
# mis-route).
|
|
700
|
+
_strategy_replacement_for(proc_obj)
|
|
624
701
|
else
|
|
625
702
|
NoOpProc.new
|
|
626
703
|
end
|
|
@@ -653,6 +730,22 @@ module RactorRailsShim
|
|
|
653
730
|
# DeviseMappingCallable) now lives in warden.rb; `_find_files_server`
|
|
654
731
|
# (Rack::Files target for the asset stack) now lives in rack.rb.
|
|
655
732
|
|
|
733
|
+
# Identify which ActionDispatch strategy a Proc is and return its
|
|
734
|
+
# shareable replacement. The Proc stored in
|
|
735
|
+
# `ActionDispatch::Routing::Mapper::Constraints#@strategy` is the
|
|
736
|
+
# constant `SERVE` or `CALL` (assigned by reference), so `equal?` is
|
|
737
|
+
# the robust identifier — independent of the source line number, which
|
|
738
|
+
# any Rails patch release can shift. Returns NoOpProc for an unknown
|
|
739
|
+
# Proc (defensive; never mis-route).
|
|
740
|
+
def _strategy_replacement_for(proc_obj)
|
|
741
|
+
constraints = defined?(::ActionDispatch::Routing::Mapper::Constraints) ?
|
|
742
|
+
::ActionDispatch::Routing::Mapper::Constraints : nil
|
|
743
|
+
return NoOpProc.new unless constraints
|
|
744
|
+
return StrategyServe.new if proc_obj.equal?(constraints::SERVE)
|
|
745
|
+
return StrategyCall.new if proc_obj.equal?(constraints::CALL)
|
|
746
|
+
NoOpProc.new
|
|
747
|
+
end
|
|
748
|
+
|
|
656
749
|
def _replace_locks_and_concurrent_maps!(app)
|
|
657
750
|
seen = {}
|
|
658
751
|
stack = [[app, "app", nil, nil]]
|
|
@@ -663,24 +756,20 @@ module RactorRailsShim
|
|
|
663
756
|
next if seen[o.object_id]
|
|
664
757
|
seen[o.object_id] = true
|
|
665
758
|
next if o.is_a?(Mutex) || o.is_a?(Monitor)
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
759
|
+
_each_ivar_and_child(o, _p) do |child, child_path, child_ivar|
|
|
760
|
+
next if child_ivar == :__default_proc__
|
|
761
|
+
if child.is_a?(Mutex) || child.is_a?(Monitor)
|
|
762
|
+
o.instance_variable_set(child_ivar, NoOpLock.new) rescue nil if child_ivar
|
|
763
|
+
# If the lock is in an Array/Set/Hash (no ivar), we can't swap
|
|
764
|
+
# it in place here — leave it; make_shareable will handle the
|
|
765
|
+
# frozen container. The ivar case is the load-bearing one.
|
|
766
|
+
elsif defined?(::Concurrent::Map) && child.is_a?(::Concurrent::Map) && child_ivar
|
|
767
|
+
hash_copy = {}
|
|
768
|
+
child.each_pair { |k, val| hash_copy[k] = val }
|
|
769
|
+
o.instance_variable_set(child_ivar, hash_copy) rescue nil
|
|
770
|
+
elsif child
|
|
771
|
+
stack << [child, child_path, o, child_ivar]
|
|
678
772
|
end
|
|
679
|
-
rescue => e
|
|
680
|
-
# BasicObject or frozen objects don't support instance_variables
|
|
681
|
-
end
|
|
682
|
-
if o.is_a?(Array); o.each_with_index { |e, i| stack << [e, "#{_p}[#{i}]", o, nil] if e }
|
|
683
|
-
elsif o.is_a?(Hash); o.each { |k, val| stack << [k, "#{_p}.key", o, nil] if k; stack << [val, "#{_p}[#{k.inspect}]", o, nil] if val }
|
|
684
773
|
end
|
|
685
774
|
end
|
|
686
775
|
end
|
|
@@ -81,7 +81,7 @@ module RactorRailsShim
|
|
|
81
81
|
singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
82
82
|
def #{sym}
|
|
83
83
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
84
|
-
return v
|
|
84
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
85
85
|
|
|
86
86
|
if #{!!shareable}
|
|
87
87
|
if class_variable_defined?(#{cv_str})
|
|
@@ -128,7 +128,7 @@ module RactorRailsShim
|
|
|
128
128
|
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
129
129
|
def #{sym}
|
|
130
130
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
131
|
-
return v
|
|
131
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
132
132
|
if Ractor.main?
|
|
133
133
|
self.class.class_variable_defined?(#{cv_str}) ? self.class.class_variable_get(#{cv_str}) : nil
|
|
134
134
|
else
|
|
@@ -21,7 +21,7 @@ module RactorRailsShim
|
|
|
21
21
|
def to_adapter
|
|
22
22
|
key = :"ractor_rails_shim_orm_adapter_\#{object_id}"
|
|
23
23
|
v = ActiveSupport::IsolatedExecutionState[key]
|
|
24
|
-
return v
|
|
24
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(key)
|
|
25
25
|
adapter = self::OrmAdapter.new(self)
|
|
26
26
|
ActiveSupport::IsolatedExecutionState[key] = adapter
|
|
27
27
|
adapter
|
|
@@ -56,7 +56,7 @@ module RactorRailsShim
|
|
|
56
56
|
req.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
57
57
|
def forwarded_priority
|
|
58
58
|
v = ActiveSupport::IsolatedExecutionState[#{fp_key_str}]
|
|
59
|
-
return v
|
|
59
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{fp_key_str})
|
|
60
60
|
if Ractor.main? && instance_variable_defined?(:@forwarded_priority)
|
|
61
61
|
@forwarded_priority
|
|
62
62
|
else
|
|
@@ -70,7 +70,7 @@ module RactorRailsShim
|
|
|
70
70
|
end
|
|
71
71
|
def x_forwarded_proto_priority
|
|
72
72
|
v = ActiveSupport::IsolatedExecutionState[#{xp_key_str}]
|
|
73
|
-
return v
|
|
73
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{xp_key_str})
|
|
74
74
|
if Ractor.main? && instance_variable_defined?(:@x_forwarded_proto_priority)
|
|
75
75
|
@x_forwarded_proto_priority
|
|
76
76
|
else
|
|
@@ -105,7 +105,7 @@ module RactorRailsShim
|
|
|
105
105
|
u.singleton_class.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
106
106
|
def default_query_parser
|
|
107
107
|
v = ActiveSupport::IsolatedExecutionState[#{dqp_key_str}]
|
|
108
|
-
return v
|
|
108
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{dqp_key_str})
|
|
109
109
|
if Ractor.main? && instance_variable_defined?(:@default_query_parser)
|
|
110
110
|
v = @default_query_parser
|
|
111
111
|
ActiveSupport::IsolatedExecutionState[#{dqp_key_str}] = v
|
|
@@ -116,7 +116,7 @@ module RactorRailsShim
|
|
|
116
116
|
end
|
|
117
117
|
def multipart_total_part_limit
|
|
118
118
|
v = ActiveSupport::IsolatedExecutionState[#{mtp_key_str}]
|
|
119
|
-
return v
|
|
119
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{mtp_key_str})
|
|
120
120
|
if Ractor.main? && instance_variable_defined?(:@multipart_total_part_limit)
|
|
121
121
|
v = @multipart_total_part_limit
|
|
122
122
|
ActiveSupport::IsolatedExecutionState[#{mtp_key_str}] = v
|
|
@@ -127,7 +127,7 @@ module RactorRailsShim
|
|
|
127
127
|
end
|
|
128
128
|
def multipart_file_limit
|
|
129
129
|
v = ActiveSupport::IsolatedExecutionState[#{mfl_key_str}]
|
|
130
|
-
return v
|
|
130
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{mfl_key_str})
|
|
131
131
|
if Ractor.main? && instance_variable_defined?(:@multipart_file_limit)
|
|
132
132
|
v = @multipart_file_limit
|
|
133
133
|
ActiveSupport::IsolatedExecutionState[#{mfl_key_str}] = v
|
|
@@ -36,6 +36,13 @@ module RactorRailsShim
|
|
|
36
36
|
# :class fires when `rails.rb` opens `module Rails` (module bodies fire
|
|
37
37
|
# as :class); once the constant is assigned, we patch once and disable
|
|
38
38
|
# the hook.
|
|
39
|
+
#
|
|
40
|
+
# Two flags are intentional and guard *different* things:
|
|
41
|
+
# @rails_load_hook_installed — the TracePoint one-shot is armed (so we
|
|
42
|
+
# don't stack multiple TracePoints on repeated `install` calls).
|
|
43
|
+
# @rails_module_patched — the patch itself has been applied (checked
|
|
44
|
+
# again inside the TracePoint block and in `patch_rails_module!`
|
|
45
|
+
# because the immediate-path caller also goes through that method).
|
|
39
46
|
def install_rails_load_hook
|
|
40
47
|
return if @rails_load_hook_installed
|
|
41
48
|
@rails_load_hook_installed = true
|
|
@@ -89,7 +96,7 @@ module RactorRailsShim
|
|
|
89
96
|
# slot is empty until they boot their own app).
|
|
90
97
|
def application
|
|
91
98
|
v = ActiveSupport::IsolatedExecutionState[#{k[:application].inspect}]
|
|
92
|
-
return v
|
|
99
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:application].inspect})
|
|
93
100
|
if Ractor.main?
|
|
94
101
|
super
|
|
95
102
|
else
|
|
@@ -108,7 +115,7 @@ module RactorRailsShim
|
|
|
108
115
|
# value at make_app_shareable! time) when their own IES slot is empty.
|
|
109
116
|
def app_class
|
|
110
117
|
v = ActiveSupport::IsolatedExecutionState[#{k[:app_class].inspect}]
|
|
111
|
-
return v
|
|
118
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:app_class].inspect})
|
|
112
119
|
return super if Ractor.main?
|
|
113
120
|
RactorRailsShim::SHAREABLE_FALLBACK[#{k[:app_class].inspect}]
|
|
114
121
|
end
|
|
@@ -120,7 +127,7 @@ module RactorRailsShim
|
|
|
120
127
|
|
|
121
128
|
def cache
|
|
122
129
|
v = ActiveSupport::IsolatedExecutionState[#{k[:cache].inspect}]
|
|
123
|
-
return v
|
|
130
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:cache].inspect})
|
|
124
131
|
return super if Ractor.main?
|
|
125
132
|
RactorRailsShim::SHAREABLE_FALLBACK[#{k[:cache].inspect}]
|
|
126
133
|
end
|
|
@@ -132,7 +139,7 @@ module RactorRailsShim
|
|
|
132
139
|
|
|
133
140
|
def logger
|
|
134
141
|
v = ActiveSupport::IsolatedExecutionState[#{k[:logger].inspect}]
|
|
135
|
-
return v
|
|
142
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:logger].inspect})
|
|
136
143
|
return super if Ractor.main?
|
|
137
144
|
# Loggers are intrinsically mutable (formatters hold tag stacks,
|
|
138
145
|
# logdevs hold IO + Mutex) and can't be shared read-only. Build a
|
|
@@ -152,7 +159,7 @@ module RactorRailsShim
|
|
|
152
159
|
|
|
153
160
|
def backtrace_cleaner
|
|
154
161
|
v = ActiveSupport::IsolatedExecutionState[#{k[:backtrace_cleaner].inspect}]
|
|
155
|
-
return v
|
|
162
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:backtrace_cleaner].inspect})
|
|
156
163
|
return super if Ractor.main?
|
|
157
164
|
RactorRailsShim::SHAREABLE_FALLBACK[#{k[:backtrace_cleaner].inspect}]
|
|
158
165
|
end
|
|
@@ -167,7 +174,7 @@ module RactorRailsShim
|
|
|
167
174
|
# lazily builds and caches in @_env.
|
|
168
175
|
def env
|
|
169
176
|
v = ActiveSupport::IsolatedExecutionState[#{k[:env].inspect}]
|
|
170
|
-
return v
|
|
177
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{k[:env].inspect})
|
|
171
178
|
if Ractor.main?
|
|
172
179
|
super
|
|
173
180
|
else
|
|
@@ -23,6 +23,13 @@ module RactorRailsShim
|
|
|
23
23
|
else
|
|
24
24
|
# Defer until Zeitwerk loads. A TracePoint(:class) fires when
|
|
25
25
|
# `module Registry` opens. One-shot.
|
|
26
|
+
#
|
|
27
|
+
# Two flags guard different things (mirrors `rails_module.rb`):
|
|
28
|
+
# @zeitwerk_patched — `install_zeitwerk_registry` ran
|
|
29
|
+
# (so we don't arm a second TracePoint).
|
|
30
|
+
# @zeitwerk_registry_patched — the actual patch was applied (checked
|
|
31
|
+
# again inside the TracePoint block and
|
|
32
|
+
# in `patch_zeitwerk_registry!`).
|
|
26
33
|
@zw_tp = TracePoint.new(:class) do |trace|
|
|
27
34
|
if defined?(::Zeitwerk::Registry) && !@zeitwerk_registry_patched
|
|
28
35
|
@zw_tp.disable
|
|
@@ -69,7 +76,7 @@ module RactorRailsShim
|
|
|
69
76
|
reader_patch.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
70
77
|
def #{ivar}
|
|
71
78
|
v = ActiveSupport::IsolatedExecutionState[#{key_str}]
|
|
72
|
-
return v
|
|
79
|
+
return v if ActiveSupport::IsolatedExecutionState.key?(#{key_str})
|
|
73
80
|
if Ractor.main?
|
|
74
81
|
existing = instance_variable_get(#{ivar_str}) if instance_variable_defined?(#{ivar_str})
|
|
75
82
|
if existing
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
# exactly what applied to their runtime.
|
|
24
24
|
module RactorRailsShim
|
|
25
25
|
module Version
|
|
26
|
-
# Ruby version the shim was developed against (major.minor). Ractor
|
|
27
|
-
# semantics are still settling;
|
|
28
|
-
|
|
26
|
+
# Ruby version the shim was developed against (major.minor.patch). Ractor
|
|
27
|
+
# semantics are still settling; the frozen-iseq call-cache fix (#22075) and
|
|
28
|
+
# the cross-ractor env-string fix that the shim relies on both shipped in
|
|
29
|
+
# 4.0.6, so earlier 4.0.x patch releases are NOT supported.
|
|
30
|
+
SUPPORTED_RUBY = "4.0.6"
|
|
29
31
|
# Rails versions each patch was tested against. Patches are registered
|
|
30
32
|
# with one or more entries from this list; the dispatcher applies a patch
|
|
31
33
|
# only if the runtime Rails version matches one of its tags. To add 7.x
|
|
@@ -33,7 +35,7 @@ module RactorRailsShim
|
|
|
33
35
|
TESTED_RAILS = ["8.1"].freeze
|
|
34
36
|
|
|
35
37
|
class << self
|
|
36
|
-
# Runtime Ruby version as a Gem::Version (e.g. "4.0.
|
|
38
|
+
# Runtime Ruby version as a Gem::Version (e.g. "4.0.6"). Always
|
|
37
39
|
# available — Ruby is obviously loaded.
|
|
38
40
|
def ruby
|
|
39
41
|
Gem::Version.new(RUBY_VERSION)
|
|
@@ -64,9 +66,11 @@ module RactorRailsShim
|
|
|
64
66
|
"#{v.segments[0]}.#{v.segments[1]}"
|
|
65
67
|
end
|
|
66
68
|
|
|
67
|
-
# True if the runtime Ruby
|
|
69
|
+
# True if the runtime Ruby is >= the supported version (4.0.6). Uses a
|
|
70
|
+
# full Gem::Version compare (not a segment match) so the minimum patch
|
|
71
|
+
# release is enforced — the shim relies on fixes that shipped in 4.0.6.
|
|
68
72
|
def supported_ruby?
|
|
69
|
-
|
|
73
|
+
ruby >= Gem::Version.new(SUPPORTED_RUBY)
|
|
70
74
|
end
|
|
71
75
|
|
|
72
76
|
# True if the runtime Rails major.minor is in the tested set (or Rails
|
metadata
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ractor-rails-shim
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Daniil Kachur
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
@@ -99,7 +99,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
99
99
|
requirements:
|
|
100
100
|
- - ">="
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
|
-
version:
|
|
102
|
+
version: 4.0.6
|
|
103
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
104
|
requirements:
|
|
105
105
|
- - ">="
|