mirror of
https://github.com/github/rails.git
synced 2026-04-04 03:00:58 -04:00
Adds Module#synchronize for easier method-level synchronization.
This commit is contained in:
@@ -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'
|
||||
|
||||
class Module
|
||||
include ActiveSupport::CoreExt::Module::ModelNaming
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
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.each do |method|
|
||||
if instance_methods.include?("#{method}_without_synchronization")
|
||||
raise ArgumentError, "#{method} is already synchronized. Double synchronization is not currently supported."
|
||||
end
|
||||
module_eval(<<-EOS, __FILE__, __LINE__)
|
||||
def #{method}_with_synchronization(*args, &block)
|
||||
#{with}.synchronize do
|
||||
#{method}_without_synchronization(*args,&block)
|
||||
end
|
||||
end
|
||||
EOS
|
||||
alias_method_chain method, :synchronization
|
||||
end
|
||||
end
|
||||
end
|
||||
57
activesupport/test/core_ext/module/synchronization_test.rb
Normal file
57
activesupport/test/core_ext/module/synchronization_test.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
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 test_mutex_is_entered_during_method_call
|
||||
dummy = Object.new
|
||||
def dummy.synchronize
|
||||
@sync_count ||= 0
|
||||
@sync_count += 1
|
||||
yield
|
||||
end
|
||||
def dummy.sync_count; @sync_count; end
|
||||
@target.mutex = dummy
|
||||
@target.synchronize :to_s, :with => :mutex
|
||||
@instance.to_s
|
||||
@instance.to_s
|
||||
assert_equal 2, dummy.sync_count
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user