Permalink
Browse files

Make ActiveRecord::Delegation#method_missing threadsafe

Two threads may be in method_missing at the same time. If so, they might
both try to define the same delegator method.

Such a situation probably wouldn't result in a particularly spectacular
bug as one method would probably just be overridden by an identical
method, but it could cause warnings to pop up. (It could be worse if
method definition is non-atomic in a particular implementation.)

(We will also need this mutex shortly anyway, see #8127.)
  • Loading branch information...
1 parent 8ec5166 commit 12d6d3a6f8914d272529d79cba461759e2dedf09 @jonleighton jonleighton committed Nov 10, 2012
Showing with 21 additions and 3 deletions.
  1. +21 −3 activerecord/lib/active_record/relation/delegation.rb
@@ -1,3 +1,4 @@
+require 'thread'
module ActiveRecord
module Delegation # :nodoc:
@@ -6,6 +7,8 @@ module Delegation # :nodoc:
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
+ @@delegation_mutex = Mutex.new
+
def self.delegate_to_scoped_klass(method)
if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
module_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -32,13 +35,28 @@ def respond_to?(method, include_private = false)
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
- ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
+ end
+ end
+
scoping { @klass.send(method, *args, &block) }
elsif Array.method_defined?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ end
+ end
+
to_a.send(method, *args, &block)
elsif arel.respond_to?(method)
- ::ActiveRecord::Delegation.delegate method, :to => :arel
+ @@delegation_mutex.synchronize do
+ unless ::ActiveRecord::Delegation.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :arel
+ end
+ end
+
arel.send(method, *args, &block)
else
super

0 comments on commit 12d6d3a

Please sign in to comment.