Skip to content

Commit

Permalink
Make ActiveRecord::Delegation#method_missing threadsafe
Browse files Browse the repository at this point in the history
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
jonleighton committed Nov 10, 2012
1 parent 8ec5166 commit 12d6d3a
Showing 1 changed file with 21 additions and 3 deletions.
24 changes: 21 additions & 3 deletions activerecord/lib/active_record/relation/delegation.rb
@@ -1,3 +1,4 @@
require 'thread'

module ActiveRecord
module Delegation # :nodoc:
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 12d6d3a

Please sign in to comment.