aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/synchronization.rb36
-rw-r--r--activesupport/test/core_ext/module/synchronization_test.rb85
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