Skip to content

Commit 75f1ea0

Browse files
skipkayhilk0kubun
authored andcommitted
Fix Ractor compatibility regression, add tests
The recent change to use `BasicObject.instance_method(:equal?)` broke the ability to share frozen ERB templates across Ractors because `UnboundMethod` isn't shareable. Freezing the `UnboundMethod` _may_ fix the issue (dependong on Ruby version), but replacing the constant with an inline call to the `singleton_class` is simpler (and still avoids calling `equal?` on `@init`). There have previously been many contributions to make ERB Ractor safe, but no tests added to ensure it continues to be Ractor safe, so this commit also adds some regression tests.
1 parent 26f674e commit 75f1ea0

2 files changed

Lines changed: 41 additions & 4 deletions

File tree

lib/erb.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -815,9 +815,6 @@
815815
# [template processor]: https://en.wikipedia.org/wiki/Template_processor
816816
#
817817
class ERB
818-
IDENTITY_METHOD = BasicObject.instance_method(:equal?) # :nodoc:
819-
private_constant :IDENTITY_METHOD
820-
821818
# :markup: markdown
822819
#
823820
# :call-seq:
@@ -1117,7 +1114,7 @@ def new_toplevel(vars = nil)
11171114
private :new_toplevel
11181115

11191116
def initialized_by_new? # :nodoc:
1120-
IDENTITY_METHOD.bind_call(@_init, self.class.singleton_class)
1117+
self.class.singleton_class.equal? @_init
11211118
end
11221119
private :initialized_by_new?
11231120

test/erb/test_erb.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,3 +740,43 @@ def teardown
740740
ERB::Compiler::Scanner.instance_variable_set('@scanner_map', @save_map)
741741
end
742742
end
743+
744+
class TestERBRactor < Test::Unit::TestCase
745+
def test_compile_and_result_in_ractor
746+
assert_ractor(<<~RUBY, require: 'erb')
747+
r = Ractor.new do
748+
ERB.new("Hello, <%= 'world' %>!").result(binding)
749+
end
750+
assert_equal("Hello, world!", r.value)
751+
RUBY
752+
end
753+
754+
def test_trim_mode_in_ractor
755+
assert_ractor(<<~RUBY, require: 'erb')
756+
src = "<% [1, 2].each do |i| %>\\n<%= i %>\\n<% end %>\\n"
757+
r = Ractor.new(src) { |s| ERB.new(s, trim_mode: '-').result(binding) }
758+
assert_equal("\\n1\\n\\n2\\n\\n", r.value)
759+
760+
r = Ractor.new(src) { |s| ERB.new(s, trim_mode: '<>').result(binding) }
761+
assert_equal("12", r.value)
762+
RUBY
763+
end
764+
765+
def test_frozen_erb_instance_reused_across_ractors
766+
assert_ractor(<<~RUBY, require: 'erb')
767+
erb = ERB.new("<%= 1 + 1 %>")
768+
erb.freeze
769+
rs = 2.times.map { Ractor.new(erb) { |e| e.result(binding) } }
770+
assert_equal(["2", "2"], rs.map(&:value))
771+
RUBY
772+
end
773+
774+
def test_util_html_escape_in_ractor
775+
assert_ractor(<<~RUBY, require: 'erb')
776+
r = Ractor.new do
777+
ERB::Util.html_escape("<script>")
778+
end
779+
assert_equal("&lt;script&gt;", r.value)
780+
RUBY
781+
end
782+
end

0 commit comments

Comments
 (0)