Skip to content

Commit

Permalink
Adds Module#synchronize for easier method-level synchronization.
Browse files Browse the repository at this point in the history
  • Loading branch information
nicksieger committed Aug 29, 2008
1 parent 5879b15 commit 3eb6824
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 0 deletions.
1 change: 1 addition & 0 deletions activesupport/lib/active_support/core_ext/module.rb
Expand Up @@ -7,6 +7,7 @@
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
Expand Down
@@ -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 activesupport/test/core_ext/module/synchronization_test.rb
@@ -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

0 comments on commit 3eb6824

Please sign in to comment.