diff options
-rw-r--r-- | arel.gemspec | 1 | ||||
-rw-r--r-- | arel.gemspec.erb | 1 | ||||
-rw-r--r-- | test/visitors/test_dispatch_contamination.rb | 49 |
3 files changed, 51 insertions, 0 deletions
diff --git a/arel.gemspec b/arel.gemspec index 9cbf991ab7..1e3fbfd49a 100644 --- a/arel.gemspec +++ b/arel.gemspec @@ -23,4 +23,5 @@ Gem::Specification.new do |s| s.add_development_dependency('minitest', '~> 5.4') s.add_development_dependency('rdoc', '~> 4.0') s.add_development_dependency('rake') + s.add_development_dependency('concurrent-ruby', '~> 1.0') end diff --git a/arel.gemspec.erb b/arel.gemspec.erb index f7ff99469c..58f07898d3 100644 --- a/arel.gemspec.erb +++ b/arel.gemspec.erb @@ -23,4 +23,5 @@ Gem::Specification.new do |s| s.add_development_dependency('minitest', '~> 5.4') s.add_development_dependency('rdoc', '~> 4.0') s.add_development_dependency('rake') + s.add_development_dependency('concurrent-ruby', '~> 1.0') end diff --git a/test/visitors/test_dispatch_contamination.rb b/test/visitors/test_dispatch_contamination.rb index 6422a6dff3..71792594d6 100644 --- a/test/visitors/test_dispatch_contamination.rb +++ b/test/visitors/test_dispatch_contamination.rb @@ -1,8 +1,42 @@ # frozen_string_literal: true require 'helper' +require 'concurrent' module Arel module Visitors + class DummyVisitor < Visitor + def initialize + super + @barrier = Concurrent::CyclicBarrier.new(2) + end + + def visit_Arel_Visitors_DummySuperNode node + 42 + end + + # This is terrible, but it's the only way to reliably reproduce + # the possible race where two threads attempt to correct the + # dispatch hash at the same time. + def send *args + super + rescue + # Both threads try (and fail) to dispatch to the subclass's name + @barrier.wait + raise + ensure + # Then one thread successfully completes (updating the dispatch + # table in the process) before the other finishes raising its + # exception. + Thread.current[:delay].wait if Thread.current[:delay] + end + end + + class DummySuperNode + end + + class DummySubNode < DummySuperNode + end + describe 'avoiding contamination between visitor dispatch tables' do before do @connection = Table.engine.connection @@ -17,6 +51,21 @@ module Arel assert_equal "( TRUE UNION FALSE )", node.to_sql end + + it 'is threadsafe when implementing superclass fallback' do + visitor = DummyVisitor.new + main_thread_finished = Concurrent::Event.new + + racing_thread = Thread.new do + Thread.current[:delay] = main_thread_finished + visitor.accept DummySubNode.new + end + + assert_equal 42, visitor.accept(DummySubNode.new) + main_thread_finished.set + + assert_equal 42, racing_thread.value + end end end end |