Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds Module#synchronize for easier method-level synchronization.
- Loading branch information
1 parent
5879b15
commit 3eb6824
Showing
3 changed files
with
93 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
activesupport/lib/active_support/core_ext/module/synchronization.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 changes: 57 additions & 0 deletions
57
activesupport/test/core_ext/module/synchronization_test.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |