From 5d92c1dbae1397ee138d4cd678bb427465a9566b Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Fri, 15 Apr 2011 14:03:26 +0100 Subject: [PATCH] created Redistat::Synchronize mixin to help with thread-safety --- lib/redistat.rb | 10 ++++++ lib/redistat/synchronize.rb | 51 +++++++++++++++++++++++++++++ spec/synchronize_spec.rb | 64 +++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 lib/redistat/synchronize.rb create mode 100644 spec/synchronize_spec.rb diff --git a/lib/redistat.rb b/lib/redistat.rb index 11d63db..a2505bd 100644 --- a/lib/redistat.rb +++ b/lib/redistat.rb @@ -3,6 +3,7 @@ require 'rubygems' require 'date' require 'time' require 'digest/sha1' +require 'monitor' # Active Support 2.x or 3.x require 'active_support' @@ -16,6 +17,7 @@ require 'redis' require 'json' require 'redistat/options' +require 'redistat/synchronize' require 'redistat/connection' require 'redistat/database' require 'redistat/collection' @@ -47,6 +49,14 @@ module Redistat class << self + def thread_safe + Synchronize.thread_safe + end + + def thread_safe=(value) + Synchronize.thread_safe = value + end + def connection(ref = nil) Connection.get(ref) end diff --git a/lib/redistat/synchronize.rb b/lib/redistat/synchronize.rb new file mode 100644 index 0000000..2d20b89 --- /dev/null +++ b/lib/redistat/synchronize.rb @@ -0,0 +1,51 @@ +require 'monitor' + +module Redistat + module Synchronize + + class << self + def included(base) + base.send(:include, InstanceMethods) + end + + def monitor + @monitor ||= Monitor.new + end + + def thread_safe + monitor.synchronize do + @thread_safe ||= false + end + end + + def thread_safe=(value) + monitor.synchronize do + @thread_safe = value + end + end + end # << self + + module InstanceMethods + def thread_safe + Synchronize.thread_safe + end + + def thread_safe=(value) + Synchronize.thread_safe = value + end + + def monitor + Synchronize.monitor + end + + def synchronize(&block) + if thread_safe + monitor.synchronize(&block) + else + block.call + end + end + end # InstanceMethods + + end +end \ No newline at end of file diff --git a/spec/synchronize_spec.rb b/spec/synchronize_spec.rb new file mode 100644 index 0000000..56e8f26 --- /dev/null +++ b/spec/synchronize_spec.rb @@ -0,0 +1,64 @@ +require "spec_helper" + +describe Redistat::Synchronize do + it { should respond_to(:monitor) } + it { should respond_to(:thread_safe) } + it { should respond_to(:thread_safe=) } + + describe "instanciated class with Redistat::Synchronize included" do + subject { SynchronizeSpecHelper.new } + it { should respond_to(:monitor) } + it { should respond_to(:thread_safe) } + it { should respond_to(:thread_safe=) } + it { should respond_to(:synchronize) } + + end + + describe "#synchronize method" do + + before(:each) do + Redistat::Synchronize.instance_variable_set("@thread_safe", nil) + @obj = SynchronizeSpecHelper.new + end + + it "should share single Monitor object across all objects" do + @obj.monitor.should == Redistat::Synchronize.monitor + end + + it "should share thread_safe option across all objects" do + obj2 = SynchronizeSpecHelper.new + Redistat::Synchronize.thread_safe.should be_false + @obj.thread_safe.should be_false + obj2.thread_safe.should be_false + @obj.thread_safe = true + Redistat::Synchronize.thread_safe.should be_true + @obj.thread_safe.should be_true + obj2.thread_safe.should be_true + end + + it "should not synchronize when thread_safe is disabled" do + # monitor receives :synchronize twice cause #thread_safe is _always_ synchronized + Redistat::Synchronize.monitor.should_receive(:synchronize).twice + @obj.thread_safe.should be_false # first #synchronize call + @obj.synchronize { 'foo' } # one #synchronize call while checking #thread_safe + end + + it "should synchronize when thread_safe is enabled" do + Monitor.class_eval { + # we're stubbing synchronize to ensure it's being called correctly, but still need it :P + alias :real_synchronize :synchronize + } + Redistat::Synchronize.monitor.should_receive(:synchronize).with.exactly(4).times.and_return { |block| + Redistat::Synchronize.monitor.real_synchronize(&block) + } + @obj.thread_safe.should be_false # first synchronize call + Redistat::Synchronize.thread_safe = true # second synchronize call + @obj.synchronize { 'foo' } # two synchronize calls, once while checking thread_safe, once to call black + end + end + +end + +class SynchronizeSpecHelper + include Redistat::Synchronize +end