diff options
Diffstat (limited to 'activesupport')
3 files changed, 122 insertions, 0 deletions
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index 641bb59eca..da8d28ec13 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -7,6 +7,7 @@ require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/module/loading' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/model_naming' +require 'active_support/core_ext/module/synchronization' module ActiveSupport module CoreExtensions diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb new file mode 100644 index 0000000000..6253594dfa --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/synchronization.rb @@ -0,0 +1,36 @@ +class Module + # Synchronize access around a method, delegating synchronization to a + # particular mutex. A mutex (either a Mutex, or any object that responds to + # #synchronize and yields to a block) must be provided as a final :with option. + # The :with option should be a symbol or string, and can represent a method, + # constant, or instance or class variable. + # Example: + # class SharedCache + # @@lock = Mutex.new + # def expire + # ... + # end + # synchronize :expire, :with => :@@lock + # end + def synchronize(*methods) + options = methods.extract_options! + unless options.is_a?(Hash) && with = options[:with] + raise ArgumentError, "Synchronization needs a mutex. Supply an options hash with a :with key as the last argument (e.g. synchronize :hello, :with => :@mutex)." + end + + methods.flatten.each do |method| + aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 + if instance_methods.include?("#{aliased_method}_without_synchronization#{punctuation}") + raise ArgumentError, "#{method} is already synchronized. Double synchronization is not currently supported." + end + module_eval(<<-EOS, __FILE__, __LINE__) + def #{aliased_method}_with_synchronization#{punctuation}(*args, &block) + #{with}.synchronize do + #{aliased_method}_without_synchronization#{punctuation}(*args, &block) + end + end + EOS + alias_method_chain method, :synchronization + end + end +end
\ No newline at end of file diff --git a/activesupport/test/core_ext/module/synchronization_test.rb b/activesupport/test/core_ext/module/synchronization_test.rb new file mode 100644 index 0000000000..b1d4bc5e06 --- /dev/null +++ b/activesupport/test/core_ext/module/synchronization_test.rb @@ -0,0 +1,85 @@ +require 'abstract_unit' + +class SynchronizationTest < Test::Unit::TestCase + def setup + @target = Class.new + @target.cattr_accessor :mutex, :instance_writer => false + @target.mutex = Mutex.new + @instance = @target.new + end + + def test_synchronize_aliases_method_chain_with_synchronize + @target.module_eval do + attr_accessor :value + synchronize :value, :with => :mutex + end + assert @instance.respond_to?(:value_with_synchronization) + assert @instance.respond_to?(:value_without_synchronization) + end + + def test_synchronize_does_not_change_behavior + @target.module_eval do + attr_accessor :value + synchronize :value, :with => :mutex + end + expected = "some state" + @instance.value = expected + assert_equal expected, @instance.value + end + + def test_synchronize_with_no_mutex_raises_an_argument_error + assert_raises(ArgumentError) do + @target.synchronize :to_s + end + end + + def test_double_synchronize_raises_an_argument_error + @target.synchronize :to_s, :with => :mutex + assert_raises(ArgumentError) do + @target.synchronize :to_s, :with => :mutex + end + end + + def dummy_sync + dummy = Object.new + def dummy.synchronize + @sync_count ||= 0 + @sync_count += 1 + yield + end + def dummy.sync_count; @sync_count; end + dummy + end + + def test_mutex_is_entered_during_method_call + @target.mutex = dummy_sync + @target.synchronize :to_s, :with => :mutex + @instance.to_s + @instance.to_s + assert_equal 2, @target.mutex.sync_count + end + + def test_can_synchronize_method_with_punctuation + @target.module_eval do + def dangerous? + @dangerous + end + def dangerous! + @dangerous = true + end + end + @target.synchronize :dangerous?, :dangerous!, :with => :mutex + @instance.dangerous! + assert @instance.dangerous? + end + + def test_can_synchronize_singleton_methods + @target.mutex = dummy_sync + class << @target + synchronize :to_s, :with => :mutex + end + assert @target.respond_to?(:to_s_without_synchronization) + assert_nothing_raised { @target.to_s; @target.to_s } + assert_equal 2, @target.mutex.sync_count + end +end
\ No newline at end of file |