lock_method 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/README.rdoc +46 -0
- data/Rakefile +22 -0
- data/lib/lock_method.rb +63 -0
- data/lib/lock_method/config.rb +46 -0
- data/lib/lock_method/lock.rb +116 -0
- data/lib/lock_method/storage.rb +60 -0
- data/lib/lock_method/storage/default_storage_client.rb +47 -0
- data/lib/lock_method/version.rb +3 -0
- data/lock_method.gemspec +29 -0
- data/test/helper.rb +54 -0
- data/test/shared_tests.rb +172 -0
- data/test/test_default_storage.rb +10 -0
- data/test/test_memcached_storage.rb +12 -0
- data/test/test_redis_storage.rb +16 -0
- metadata +142 -0
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/README.rdoc
    ADDED
    
    | @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            = lock_method
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            It's like <tt>alias_method</tt>, but it's <tt>lock_method</tt>!
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            == Example
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                require 'lock_method'
         | 
| 8 | 
            +
                class Blog
         | 
| 9 | 
            +
                  # [...]
         | 
| 10 | 
            +
                  def get_latest_entries
         | 
| 11 | 
            +
                    sleep 5
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                  # [...]
         | 
| 14 | 
            +
                  lock_method :get_latest_entries
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Then you can do
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                my_blog.get_latest_entries => it will start...
         | 
| 20 | 
            +
                my_blog.get_latest_entries => this will raise LockMethod::Locked if you try to run it before the other call finishes
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            Just in case, you can clear them
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                my_blog.clear_lock :get_latest_entries
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            == Configuration (and supported cache clients)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            The default is to use filesystem lockfiles, usually in <tt>/tmp/lock_method/*</tt>.
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            If you want to share locks among various machines, you can use a Memcached or Redis client:
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                LockMethod.config.storage = Memcached.new '127.0.0.1:11211'
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            or
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                LockMethod.config.storage = Redis.new
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            or this might even work...
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                LockMethod.config.storage = Rails.cache
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            See <tt>Config</tt> for the full list of supported caches.
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            == Copyright
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            Copyright 2011 Seamus Abshere
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            require 'bundler'
         | 
| 2 | 
            +
            Bundler::GemHelper.install_tasks
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'rake'
         | 
| 5 | 
            +
            require 'rake/testtask'
         | 
| 6 | 
            +
            Rake::TestTask.new(:test) do |test|
         | 
| 7 | 
            +
              test.libs << 'lib' << 'test'
         | 
| 8 | 
            +
              test.pattern = 'test/**/test_*.rb'
         | 
| 9 | 
            +
              test.verbose = true
         | 
| 10 | 
            +
            end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            task :default => :test
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require 'rake/rdoctask'
         | 
| 15 | 
            +
            Rake::RDocTask.new do |rdoc|
         | 
| 16 | 
            +
              version = File.exist?('VERSION') ? File.read('VERSION') : ""
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              rdoc.rdoc_dir = 'rdoc'
         | 
| 19 | 
            +
              rdoc.title = "lock_method #{version}"
         | 
| 20 | 
            +
              rdoc.rdoc_files.include('README*')
         | 
| 21 | 
            +
              rdoc.rdoc_files.include('lib/**/*.rb')
         | 
| 22 | 
            +
            end
         | 
    
        data/lib/lock_method.rb
    ADDED
    
    | @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            require 'lock_method/version'
         | 
| 2 | 
            +
            # See the README.rdoc for more info!
         | 
| 3 | 
            +
            module LockMethod
         | 
| 4 | 
            +
              autoload :Config, 'lock_method/config'
         | 
| 5 | 
            +
              autoload :Storage, 'lock_method/storage'
         | 
| 6 | 
            +
              autoload :Lock, 'lock_method/lock'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              # This is what gets raised when you try to run a locked method.
         | 
| 9 | 
            +
              class Locked < ::StandardError
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def self.config #:nodoc:
         | 
| 13 | 
            +
                Config.instance
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
              
         | 
| 16 | 
            +
              def self.storage #:nodoc:
         | 
| 17 | 
            +
                Storage.instance
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
                
         | 
| 20 | 
            +
              # All Objects, including instances and Classes, get the <tt>#clear_lock</tt> method.
         | 
| 21 | 
            +
              module InstanceMethods
         | 
| 22 | 
            +
                # Clear the lock for a particular method.
         | 
| 23 | 
            +
                #
         | 
| 24 | 
            +
                # Example:
         | 
| 25 | 
            +
                #     my_blog.clear_lock :get_latest_entries
         | 
| 26 | 
            +
                def clear_lock(method_id)
         | 
| 27 | 
            +
                  lock = ::LockMethod::Lock.new :obj => self, :method_id => method_id
         | 
| 28 | 
            +
                  lock.delete
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              # All Classes (but not instances), get the <tt>.lock_method</tt> method.
         | 
| 33 | 
            +
              module ClassMethods
         | 
| 34 | 
            +
                # Lock a method. TTL in seconds, defaults to whatever's in LockMethod.config.default_ttl
         | 
| 35 | 
            +
                #
         | 
| 36 | 
            +
                # Note 2: Check out LockMethod.config.default_ttl... the default is 24 hours!
         | 
| 37 | 
            +
                #
         | 
| 38 | 
            +
                # Example:
         | 
| 39 | 
            +
                #     class Blog
         | 
| 40 | 
            +
                #       # [...]
         | 
| 41 | 
            +
                #       def get_latest_entries
         | 
| 42 | 
            +
                #         sleep 5
         | 
| 43 | 
            +
                #       end
         | 
| 44 | 
            +
                #       # [...]
         | 
| 45 | 
            +
                #       lock_method :get_latest_entries
         | 
| 46 | 
            +
                #       # if you wanted a different ttl...
         | 
| 47 | 
            +
                #       # lock_method :get_latest_entries, 800 #seconds
         | 
| 48 | 
            +
                #     end
         | 
| 49 | 
            +
                def lock_method(method_id, ttl = nil)
         | 
| 50 | 
            +
                  original_method_id = "_unlocked_#{method_id}"
         | 
| 51 | 
            +
                  alias_method original_method_id, method_id
         | 
| 52 | 
            +
                  define_method method_id do |*args|
         | 
| 53 | 
            +
                    lock = ::LockMethod::Lock.new :obj => self, :method_id => method_id, :original_method_id => original_method_id, :args => args, :ttl => ttl
         | 
| 54 | 
            +
                    lock.call_original_method
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            unless ::Object.method_defined? :lock_method
         | 
| 61 | 
            +
              ::Object.send :include, ::LockMethod::InstanceMethods
         | 
| 62 | 
            +
              ::Object.extend ::LockMethod::ClassMethods
         | 
| 63 | 
            +
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            require 'singleton'
         | 
| 2 | 
            +
            module LockMethod
         | 
| 3 | 
            +
              # Here's where you set config options.
         | 
| 4 | 
            +
              #
         | 
| 5 | 
            +
              # Example:
         | 
| 6 | 
            +
              #     LockMethod.config.storage = Memcached.new '127.0.0.1:11211'
         | 
| 7 | 
            +
              #
         | 
| 8 | 
            +
              # You'd probably put this in your Rails config/initializers, for example.
         | 
| 9 | 
            +
              class Config
         | 
| 10 | 
            +
                include ::Singleton
         | 
| 11 | 
            +
                
         | 
| 12 | 
            +
                # Storage for keeping lockfiles.
         | 
| 13 | 
            +
                #
         | 
| 14 | 
            +
                # Defaults to using the filesystem's temp dir.
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # Supported memcached clients:
         | 
| 17 | 
            +
                # * memcached[https://github.com/fauna/memcached] (either a Memcached or a Memcached::Rails)
         | 
| 18 | 
            +
                # * dalli[https://github.com/mperham/dalli] (either a Dalli::Client or an ActiveSupport::Cache::DalliStore)
         | 
| 19 | 
            +
                # * memcache-storage[https://github.com/mperham/memcache-storage] (MemCache, the one commonly used by Rails)
         | 
| 20 | 
            +
                #
         | 
| 21 | 
            +
                # Supported Redis clients:
         | 
| 22 | 
            +
                # * redis[https://github.com/ezmobius/redis-rb] (NOTE: AUTOMATIC CACHE EXPIRATION NOT SUPPORTED)
         | 
| 23 | 
            +
                #
         | 
| 24 | 
            +
                # Example:
         | 
| 25 | 
            +
                #     LockMethod.config.storage = Memcached.new '127.0.0.1:11211'
         | 
| 26 | 
            +
                def storage=(storage)
         | 
| 27 | 
            +
                  @storage = storage
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def storage #:nodoc:
         | 
| 31 | 
            +
                  @storage ||= Storage::DefaultStorageClient.new
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
                
         | 
| 34 | 
            +
                # TTL for method caches. Defaults to 24 hours.
         | 
| 35 | 
            +
                #
         | 
| 36 | 
            +
                # Example:
         | 
| 37 | 
            +
                #     LockMethod.config.default_ttl = 120 # seconds
         | 
| 38 | 
            +
                def default_ttl=(seconds)
         | 
| 39 | 
            +
                  @default_ttl = seconds
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
                
         | 
| 42 | 
            +
                def default_ttl #:nodoc:
         | 
| 43 | 
            +
                  @default_ttl || 86_400
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| @@ -0,0 +1,116 @@ | |
| 1 | 
            +
            module LockMethod
         | 
| 2 | 
            +
              class Lock
         | 
| 3 | 
            +
                class << self
         | 
| 4 | 
            +
                  def find(method_signature)
         | 
| 5 | 
            +
                    if hsh = Storage.instance.get(method_signature)
         | 
| 6 | 
            +
                      new hsh
         | 
| 7 | 
            +
                    end
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
                  def klass_name(obj)
         | 
| 10 | 
            +
                    obj.is_a?(::Class) ? obj.to_s : obj.class.to_s
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                  def method_delimiter(obj)
         | 
| 13 | 
            +
                    obj.is_a?(::Class) ? '.' : '#'
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                  def method_signature(obj, method_id)
         | 
| 16 | 
            +
                    [ klass_name(obj), method_id ].join method_delimiter(obj)
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                  def process_alive?(pid)
         | 
| 19 | 
            +
                    ::Process.kill 0, pid
         | 
| 20 | 
            +
                  rescue ::Errno::ESRCH
         | 
| 21 | 
            +
                    false
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                  def thread_alive?(thread_object_id)
         | 
| 24 | 
            +
                    if thr = ::Thread.list.detect { |t| t.object_id == thread_object_id }
         | 
| 25 | 
            +
                      thr.status == 'sleep' or thr.status == 'run'
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def initialize(options = {})
         | 
| 31 | 
            +
                  options.each do |k, v|
         | 
| 32 | 
            +
                    instance_variable_set "@#{k}", v
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                attr_reader :obj
         | 
| 37 | 
            +
                attr_reader :method_id
         | 
| 38 | 
            +
                attr_reader :original_method_id
         | 
| 39 | 
            +
                attr_reader :args
         | 
| 40 | 
            +
                    
         | 
| 41 | 
            +
                def method_signature
         | 
| 42 | 
            +
                  @method_signature ||= Lock.method_signature(obj, method_id)
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
                
         | 
| 45 | 
            +
                def ttl
         | 
| 46 | 
            +
                  @ttl ||= Config.instance.default_ttl
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
                
         | 
| 49 | 
            +
                def pid
         | 
| 50 | 
            +
                  @pid ||= ::Process.pid
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
                
         | 
| 53 | 
            +
                def thread_object_id
         | 
| 54 | 
            +
                  @thread_object_id ||= ::Thread.current.object_id
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
                
         | 
| 57 | 
            +
                def expiry
         | 
| 58 | 
            +
                  @expiry ||= ::Time.now + ttl
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
                
         | 
| 61 | 
            +
                def delete
         | 
| 62 | 
            +
                  Storage.instance.delete method_signature
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
                
         | 
| 65 | 
            +
                def save
         | 
| 66 | 
            +
                  # make sure these are set
         | 
| 67 | 
            +
                  self.pid
         | 
| 68 | 
            +
                  self.thread_object_id
         | 
| 69 | 
            +
                  self.expiry
         | 
| 70 | 
            +
                  # --
         | 
| 71 | 
            +
                  Storage.instance.set method_signature, to_hash, ttl
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
                
         | 
| 74 | 
            +
                def to_hash
         | 
| 75 | 
            +
                  instance_variables.inject({}) do |memo, ivar_name|
         | 
| 76 | 
            +
                    memo[ivar_name.to_s.sub('@', '')] = instance_variable_get ivar_name
         | 
| 77 | 
            +
                    memo
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
                
         | 
| 81 | 
            +
                def locked?
         | 
| 82 | 
            +
                  if existing_lock = Lock.find(method_signature)
         | 
| 83 | 
            +
                    existing_lock.in_force?
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
                
         | 
| 87 | 
            +
                def in_force?
         | 
| 88 | 
            +
                  not expired? and process_and_thread_still_exist?
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
                
         | 
| 91 | 
            +
                def expired?
         | 
| 92 | 
            +
                  expiry.to_f < ::Time.now.to_f
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
                
         | 
| 95 | 
            +
                def process_and_thread_still_exist?
         | 
| 96 | 
            +
                  if pid == ::Process.pid
         | 
| 97 | 
            +
                    Lock.thread_alive? thread_object_id
         | 
| 98 | 
            +
                  else
         | 
| 99 | 
            +
                    Lock.process_alive? pid
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
                
         | 
| 103 | 
            +
                def call_original_method
         | 
| 104 | 
            +
                  if locked?
         | 
| 105 | 
            +
                    raise Locked
         | 
| 106 | 
            +
                  else
         | 
| 107 | 
            +
                    begin
         | 
| 108 | 
            +
                      save
         | 
| 109 | 
            +
                      obj.send original_method_id, *args
         | 
| 110 | 
            +
                    ensure
         | 
| 111 | 
            +
                      delete
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
            end
         | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            require 'singleton'
         | 
| 2 | 
            +
            module LockMethod
         | 
| 3 | 
            +
              # All storage requests go through a clearinghouse.
         | 
| 4 | 
            +
              class Storage #:nodoc: all
         | 
| 5 | 
            +
                autoload :DefaultStorageClient, 'lock_method/storage/default_storage_client'
         | 
| 6 | 
            +
                
         | 
| 7 | 
            +
                include ::Singleton
         | 
| 8 | 
            +
                
         | 
| 9 | 
            +
                def delete(k)
         | 
| 10 | 
            +
                  if defined?(::Memcached) and bare_storage.is_a?(::Memcached)
         | 
| 11 | 
            +
                    begin; bare_storage.delete(k); rescue ::Memcached::NotFound; nil; end
         | 
| 12 | 
            +
                  elsif defined?(::Redis) and bare_storage.is_a?(::Redis)
         | 
| 13 | 
            +
                    bare_storage.del k
         | 
| 14 | 
            +
                  else
         | 
| 15 | 
            +
                    bare_storage.delete k
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
                
         | 
| 19 | 
            +
                def flush
         | 
| 20 | 
            +
                  bare_storage.send %w{ flush flush_all clear flushdb }.detect { |c| bare_storage.respond_to? c }
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
                
         | 
| 23 | 
            +
                def get(k)
         | 
| 24 | 
            +
                  if defined?(::Memcached) and bare_storage.is_a?(::Memcached)
         | 
| 25 | 
            +
                    begin; bare_storage.get(k); rescue ::Memcached::NotFound; nil; end
         | 
| 26 | 
            +
                  elsif defined?(::Redis) and bare_storage.is_a?(::Redis)
         | 
| 27 | 
            +
                    if cached_v = bare_storage.get(k) and cached_v.is_a?(::String)
         | 
| 28 | 
            +
                      ::Marshal.load cached_v
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
                  elsif bare_storage.respond_to?(:get)
         | 
| 31 | 
            +
                    bare_storage.get k
         | 
| 32 | 
            +
                  elsif bare_storage.respond_to?(:read)
         | 
| 33 | 
            +
                    bare_storage.read k
         | 
| 34 | 
            +
                  else
         | 
| 35 | 
            +
                    raise "Don't know how to work with #{bare_storage.inspect}"
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
                    
         | 
| 39 | 
            +
                def set(k, v, ttl)
         | 
| 40 | 
            +
                  ttl ||= ::LockMethod.config.default_ttl
         | 
| 41 | 
            +
                  if defined?(::Redis) and bare_storage.is_a?(::Redis)
         | 
| 42 | 
            +
                    bare_storage.setex k, ttl, ::Marshal.dump(v)
         | 
| 43 | 
            +
                  elsif bare_storage.respond_to?(:set)
         | 
| 44 | 
            +
                    bare_storage.set k, v, ttl
         | 
| 45 | 
            +
                  elsif bare_storage.respond_to?(:write)
         | 
| 46 | 
            +
                    if ttl == 0
         | 
| 47 | 
            +
                      bare_storage.write k, v # never expire
         | 
| 48 | 
            +
                    else
         | 
| 49 | 
            +
                      bare_storage.write k, v, :expires_in => ttl
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  else
         | 
| 52 | 
            +
                    raise "Don't know how to work with #{bare_storage.inspect}"
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
                
         | 
| 56 | 
            +
                def bare_storage
         | 
| 57 | 
            +
                  Config.instance.storage
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            require 'tmpdir'
         | 
| 2 | 
            +
            require 'fileutils'
         | 
| 3 | 
            +
            require 'thread'
         | 
| 4 | 
            +
            module LockMethod
         | 
| 5 | 
            +
              class Storage
         | 
| 6 | 
            +
                class DefaultStorageClient
         | 
| 7 | 
            +
                  def get(k)
         | 
| 8 | 
            +
                    return unless ::File.exist? path(k)
         | 
| 9 | 
            +
                    ::Marshal.load ::File.read(path(k))
         | 
| 10 | 
            +
                  rescue ::Errno::ENOENT
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                
         | 
| 13 | 
            +
                  def set(k, v, ttl)
         | 
| 14 | 
            +
                    semaphore.synchronize do
         | 
| 15 | 
            +
                      ::File.open(path(k), ::File::RDWR|::File::CREAT) do |f|
         | 
| 16 | 
            +
                        f.flock ::File::LOCK_EX
         | 
| 17 | 
            +
                        f.write ::Marshal.dump(v)
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                
         | 
| 22 | 
            +
                  def delete(k)
         | 
| 23 | 
            +
                    ::FileUtils.rm_f path(k)
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                  def flush
         | 
| 27 | 
            +
                    ::FileUtils.rm_rf dir
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                
         | 
| 30 | 
            +
                  private
         | 
| 31 | 
            +
                
         | 
| 32 | 
            +
                  def semaphore
         | 
| 33 | 
            +
                    @semaphore ||= ::Mutex.new
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                
         | 
| 36 | 
            +
                  def path(k)
         | 
| 37 | 
            +
                    ::File.join dir, k
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                
         | 
| 40 | 
            +
                  def dir
         | 
| 41 | 
            +
                    dir = ::File.expand_path(::File.join(::Dir.tmpdir, 'lock_method'))
         | 
| 42 | 
            +
                    ::FileUtils.mkdir_p dir unless ::File.directory? dir
         | 
| 43 | 
            +
                    dir
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
    
        data/lock_method.gemspec
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
            $:.push File.expand_path("../lib", __FILE__)
         | 
| 3 | 
            +
            require "lock_method/version"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Gem::Specification.new do |s|
         | 
| 6 | 
            +
              s.name        = "lock_method"
         | 
| 7 | 
            +
              s.version     = LockMethod::VERSION
         | 
| 8 | 
            +
              s.platform    = Gem::Platform::RUBY
         | 
| 9 | 
            +
              s.authors     = ["Seamus Abshere"]
         | 
| 10 | 
            +
              s.email       = ["seamus@abshere.net"]
         | 
| 11 | 
            +
              s.homepage    = "https://github.com/seamusabshere/lock_method"
         | 
| 12 | 
            +
              s.summary     = %q{Lets you lock methods (to memcached, redis, etc.) as though you had a lockfile for each one}
         | 
| 13 | 
            +
              s.description = %q{Like alias_method, but it's lock_method!}
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              s.rubyforge_project = "lock_method"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              s.files         = `git ls-files`.split("\n")
         | 
| 18 | 
            +
              s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
         | 
| 19 | 
            +
              s.require_paths = ["lib"]
         | 
| 20 | 
            +
              
         | 
| 21 | 
            +
              s.add_development_dependency 'test-unit'
         | 
| 22 | 
            +
              s.add_development_dependency 'memcached'
         | 
| 23 | 
            +
              s.add_development_dependency 'redis'
         | 
| 24 | 
            +
              if RUBY_VERSION >= '1.9'
         | 
| 25 | 
            +
                s.add_development_dependency 'ruby-debug19'
         | 
| 26 | 
            +
              else
         | 
| 27 | 
            +
                s.add_development_dependency 'ruby-debug'
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
    
        data/test/helper.rb
    ADDED
    
    | @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            require 'rubygems'
         | 
| 2 | 
            +
            require 'bundler'
         | 
| 3 | 
            +
            Bundler.setup
         | 
| 4 | 
            +
            require 'test/unit'
         | 
| 5 | 
            +
            require 'ruby-debug'
         | 
| 6 | 
            +
            require 'shared_tests'
         | 
| 7 | 
            +
            $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
         | 
| 8 | 
            +
            $LOAD_PATH.unshift(File.dirname(__FILE__))
         | 
| 9 | 
            +
            require 'lock_method'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            class Blog1
         | 
| 12 | 
            +
              attr_reader :name
         | 
| 13 | 
            +
              attr_reader :url
         | 
| 14 | 
            +
              def initialize(name, url)
         | 
| 15 | 
            +
                @name = name
         | 
| 16 | 
            +
                @url = url
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
              def get_latest_entries
         | 
| 19 | 
            +
                sleep 8
         | 
| 20 | 
            +
                ["hello from #{name}"]
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
              lock_method :get_latest_entries
         | 
| 23 | 
            +
              def get_latest_entries2
         | 
| 24 | 
            +
                sleep 8
         | 
| 25 | 
            +
                ["voo vaa #{name}"]
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
              lock_method :get_latest_entries2, 5 # second
         | 
| 28 | 
            +
            end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            class Blog2
         | 
| 31 | 
            +
              class << self
         | 
| 32 | 
            +
                def get_latest_entries
         | 
| 33 | 
            +
                  sleep 8
         | 
| 34 | 
            +
                  'danke schoen'
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
                lock_method :get_latest_entries
         | 
| 37 | 
            +
                def get_latest_entries2
         | 
| 38 | 
            +
                  sleep 8
         | 
| 39 | 
            +
                  ["voo vaa #{name}"]
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
                lock_method :get_latest_entries2, 5 # second
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            def new_instance_of_my_blog
         | 
| 46 | 
            +
              Blog1.new 'my_blog', 'http://my_blog.example.com'
         | 
| 47 | 
            +
            end
         | 
| 48 | 
            +
            def new_instance_of_another_blog
         | 
| 49 | 
            +
              Blog1.new 'another_blog', 'http://another_blog.example.com'
         | 
| 50 | 
            +
            end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            class Test::Unit::TestCase
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,172 @@ | |
| 1 | 
            +
            module SharedTests
         | 
| 2 | 
            +
              def test_locked_method_return_value
         | 
| 3 | 
            +
                assert_equal ["hello from my_blog"], new_instance_of_my_blog.get_latest_entries
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
              
         | 
| 6 | 
            +
              def test_locked_by_normally_terminating_process
         | 
| 7 | 
            +
                pid = Kernel.fork { Blog2.get_latest_entries }
         | 
| 8 | 
            +
              
         | 
| 9 | 
            +
                # give it a bit of time to lock
         | 
| 10 | 
            +
                sleep 1
         | 
| 11 | 
            +
                
         | 
| 12 | 
            +
                # the blocker won't have finished
         | 
| 13 | 
            +
                assert_raises(LockMethod::Locked) do
         | 
| 14 | 
            +
                  Blog2.get_latest_entries
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              
         | 
| 17 | 
            +
                # let the blocker finish
         | 
| 18 | 
            +
                Process.wait pid
         | 
| 19 | 
            +
                
         | 
| 20 | 
            +
                assert_nothing_raised do
         | 
| 21 | 
            +
                  Blog2.get_latest_entries
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
              
         | 
| 25 | 
            +
              def test_locked_by_SIGKILLed_process
         | 
| 26 | 
            +
                pid = Kernel.fork { Blog2.get_latest_entries }
         | 
| 27 | 
            +
                
         | 
| 28 | 
            +
                # give it a bit of time to lock
         | 
| 29 | 
            +
                sleep 1
         | 
| 30 | 
            +
                
         | 
| 31 | 
            +
                # the blocker won't have finished
         | 
| 32 | 
            +
                assert_raises(LockMethod::Locked) do
         | 
| 33 | 
            +
                  Blog2.get_latest_entries
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              
         | 
| 36 | 
            +
                # kill it and then wait for it to be reaped
         | 
| 37 | 
            +
                Process.detach pid
         | 
| 38 | 
            +
                Process.kill 9, pid
         | 
| 39 | 
            +
                sleep 1
         | 
| 40 | 
            +
                
         | 
| 41 | 
            +
                # now we're sure
         | 
| 42 | 
            +
                assert_nothing_raised do
         | 
| 43 | 
            +
                  Blog2.get_latest_entries
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
              
         | 
| 47 | 
            +
              def test_locked_by_killed_thread
         | 
| 48 | 
            +
                blocker = Thread.new { Blog2.get_latest_entries }
         | 
| 49 | 
            +
              
         | 
| 50 | 
            +
                # give it a bit of time to lock
         | 
| 51 | 
            +
                sleep 1
         | 
| 52 | 
            +
                
         | 
| 53 | 
            +
                # the blocker won't have finished
         | 
| 54 | 
            +
                assert_raises(LockMethod::Locked) do
         | 
| 55 | 
            +
                  Blog2.get_latest_entries
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              
         | 
| 58 | 
            +
                # kinda like a SIGKILL
         | 
| 59 | 
            +
                blocker.kill
         | 
| 60 | 
            +
                
         | 
| 61 | 
            +
                # now we're sure
         | 
| 62 | 
            +
                assert_nothing_raised do
         | 
| 63 | 
            +
                  Blog2.get_latest_entries
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
              
         | 
| 67 | 
            +
              def test_locked_by_normally_finishing_thread
         | 
| 68 | 
            +
                blocker = Thread.new { Blog2.get_latest_entries }
         | 
| 69 | 
            +
              
         | 
| 70 | 
            +
                # give it a bit of time to lock
         | 
| 71 | 
            +
                sleep 1
         | 
| 72 | 
            +
                
         | 
| 73 | 
            +
                # the blocker won't have finished
         | 
| 74 | 
            +
                assert_raises(LockMethod::Locked) do
         | 
| 75 | 
            +
                  Blog2.get_latest_entries
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
              
         | 
| 78 | 
            +
                # wait to finish
         | 
| 79 | 
            +
                blocker.join
         | 
| 80 | 
            +
                
         | 
| 81 | 
            +
                # now we're sure
         | 
| 82 | 
            +
                assert_nothing_raised do
         | 
| 83 | 
            +
                  Blog2.get_latest_entries
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
              
         | 
| 87 | 
            +
              def test_lock_instance_method
         | 
| 88 | 
            +
                pid = Kernel.fork { new_instance_of_my_blog.get_latest_entries }
         | 
| 89 | 
            +
              
         | 
| 90 | 
            +
                # give it a bit of time to lock
         | 
| 91 | 
            +
                sleep 1
         | 
| 92 | 
            +
                
         | 
| 93 | 
            +
                # the blocker won't have finished
         | 
| 94 | 
            +
                assert_raises(LockMethod::Locked) do
         | 
| 95 | 
            +
                  new_instance_of_my_blog.get_latest_entries
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              
         | 
| 98 | 
            +
                # wait for it
         | 
| 99 | 
            +
                Process.wait pid
         | 
| 100 | 
            +
                
         | 
| 101 | 
            +
                # ok now
         | 
| 102 | 
            +
                assert_nothing_raised do
         | 
| 103 | 
            +
                  new_instance_of_my_blog.get_latest_entries
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
              
         | 
| 107 | 
            +
              def test_clear_instance_method_lock
         | 
| 108 | 
            +
                pid = Kernel.fork { new_instance_of_my_blog.get_latest_entries }
         | 
| 109 | 
            +
              
         | 
| 110 | 
            +
                # give it a bit of time to lock
         | 
| 111 | 
            +
                sleep 1
         | 
| 112 | 
            +
                
         | 
| 113 | 
            +
                # the blocker won't have finished
         | 
| 114 | 
            +
                assert_raises(LockMethod::Locked) do
         | 
| 115 | 
            +
                  new_instance_of_my_blog.get_latest_entries
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
              
         | 
| 118 | 
            +
                # but now we clear the lock
         | 
| 119 | 
            +
                new_instance_of_my_blog.clear_lock :get_latest_entries
         | 
| 120 | 
            +
                assert_nothing_raised do
         | 
| 121 | 
            +
                  new_instance_of_my_blog.get_latest_entries
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
                
         | 
| 124 | 
            +
                Process.wait pid
         | 
| 125 | 
            +
              end
         | 
| 126 | 
            +
              
         | 
| 127 | 
            +
              def test_clear_class_method_lock
         | 
| 128 | 
            +
                pid = Kernel.fork { Blog2.get_latest_entries }
         | 
| 129 | 
            +
              
         | 
| 130 | 
            +
                # give it a bit of time to lock
         | 
| 131 | 
            +
                sleep 1
         | 
| 132 | 
            +
                
         | 
| 133 | 
            +
                # the blocker won't have finished
         | 
| 134 | 
            +
                assert_raises(LockMethod::Locked) do
         | 
| 135 | 
            +
                  Blog2.get_latest_entries
         | 
| 136 | 
            +
                end
         | 
| 137 | 
            +
                
         | 
| 138 | 
            +
                # but now we clear the lock
         | 
| 139 | 
            +
                Blog2.clear_lock :get_latest_entries
         | 
| 140 | 
            +
                assert_nothing_raised do
         | 
| 141 | 
            +
                  Blog2.get_latest_entries
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
                
         | 
| 144 | 
            +
                Process.wait pid
         | 
| 145 | 
            +
              end
         | 
| 146 | 
            +
              
         | 
| 147 | 
            +
              def test_expiring_lock
         | 
| 148 | 
            +
                pid = Kernel.fork { Blog2.get_latest_entries2 }
         | 
| 149 | 
            +
              
         | 
| 150 | 
            +
                # give it a bit of time to lock
         | 
| 151 | 
            +
                sleep 1
         | 
| 152 | 
            +
                
         | 
| 153 | 
            +
                # the blocker won't have finished
         | 
| 154 | 
            +
                assert_raises(LockMethod::Locked) do
         | 
| 155 | 
            +
                  Blog2.get_latest_entries2
         | 
| 156 | 
            +
                end
         | 
| 157 | 
            +
              
         | 
| 158 | 
            +
                # still no...
         | 
| 159 | 
            +
                sleep 1
         | 
| 160 | 
            +
                assert_raises(LockMethod::Locked) do
         | 
| 161 | 
            +
                  Blog2.get_latest_entries2
         | 
| 162 | 
            +
                end
         | 
| 163 | 
            +
                
         | 
| 164 | 
            +
                # but the lock expiry is 1 second, so by 1.2&change we're done
         | 
| 165 | 
            +
                sleep 5
         | 
| 166 | 
            +
                assert_nothing_raised do
         | 
| 167 | 
            +
                  Blog2.get_latest_entries2
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
                
         | 
| 170 | 
            +
                Process.wait pid
         | 
| 171 | 
            +
              end
         | 
| 172 | 
            +
            end
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            require 'helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            if ENV['REDIS_URL']
         | 
| 4 | 
            +
              require 'redis'
         | 
| 5 | 
            +
              require 'uri'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              class TestRedisStorage < Test::Unit::TestCase
         | 
| 8 | 
            +
                def setup
         | 
| 9 | 
            +
                  uri = URI.parse(ENV["REDIS_URL"])
         | 
| 10 | 
            +
                  LockMethod.config.storage = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
         | 
| 11 | 
            +
                  LockMethod.storage.flush
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
                
         | 
| 14 | 
            +
                include SharedTests
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,142 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification 
         | 
| 2 | 
            +
            name: lock_method
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            +
              hash: 29
         | 
| 5 | 
            +
              prerelease: false
         | 
| 6 | 
            +
              segments: 
         | 
| 7 | 
            +
              - 0
         | 
| 8 | 
            +
              - 0
         | 
| 9 | 
            +
              - 1
         | 
| 10 | 
            +
              version: 0.0.1
         | 
| 11 | 
            +
            platform: ruby
         | 
| 12 | 
            +
            authors: 
         | 
| 13 | 
            +
            - Seamus Abshere
         | 
| 14 | 
            +
            autorequire: 
         | 
| 15 | 
            +
            bindir: bin
         | 
| 16 | 
            +
            cert_chain: []
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            date: 2011-02-16 00:00:00 -06:00
         | 
| 19 | 
            +
            default_executable: 
         | 
| 20 | 
            +
            dependencies: 
         | 
| 21 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 22 | 
            +
              name: test-unit
         | 
| 23 | 
            +
              prerelease: false
         | 
| 24 | 
            +
              requirement: &id001 !ruby/object:Gem::Requirement 
         | 
| 25 | 
            +
                none: false
         | 
| 26 | 
            +
                requirements: 
         | 
| 27 | 
            +
                - - ">="
         | 
| 28 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 29 | 
            +
                    hash: 3
         | 
| 30 | 
            +
                    segments: 
         | 
| 31 | 
            +
                    - 0
         | 
| 32 | 
            +
                    version: "0"
         | 
| 33 | 
            +
              type: :development
         | 
| 34 | 
            +
              version_requirements: *id001
         | 
| 35 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 36 | 
            +
              name: memcached
         | 
| 37 | 
            +
              prerelease: false
         | 
| 38 | 
            +
              requirement: &id002 !ruby/object:Gem::Requirement 
         | 
| 39 | 
            +
                none: false
         | 
| 40 | 
            +
                requirements: 
         | 
| 41 | 
            +
                - - ">="
         | 
| 42 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 43 | 
            +
                    hash: 3
         | 
| 44 | 
            +
                    segments: 
         | 
| 45 | 
            +
                    - 0
         | 
| 46 | 
            +
                    version: "0"
         | 
| 47 | 
            +
              type: :development
         | 
| 48 | 
            +
              version_requirements: *id002
         | 
| 49 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 50 | 
            +
              name: redis
         | 
| 51 | 
            +
              prerelease: false
         | 
| 52 | 
            +
              requirement: &id003 !ruby/object:Gem::Requirement 
         | 
| 53 | 
            +
                none: false
         | 
| 54 | 
            +
                requirements: 
         | 
| 55 | 
            +
                - - ">="
         | 
| 56 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 57 | 
            +
                    hash: 3
         | 
| 58 | 
            +
                    segments: 
         | 
| 59 | 
            +
                    - 0
         | 
| 60 | 
            +
                    version: "0"
         | 
| 61 | 
            +
              type: :development
         | 
| 62 | 
            +
              version_requirements: *id003
         | 
| 63 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 64 | 
            +
              name: ruby-debug
         | 
| 65 | 
            +
              prerelease: false
         | 
| 66 | 
            +
              requirement: &id004 !ruby/object:Gem::Requirement 
         | 
| 67 | 
            +
                none: false
         | 
| 68 | 
            +
                requirements: 
         | 
| 69 | 
            +
                - - ">="
         | 
| 70 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 71 | 
            +
                    hash: 3
         | 
| 72 | 
            +
                    segments: 
         | 
| 73 | 
            +
                    - 0
         | 
| 74 | 
            +
                    version: "0"
         | 
| 75 | 
            +
              type: :development
         | 
| 76 | 
            +
              version_requirements: *id004
         | 
| 77 | 
            +
            description: Like alias_method, but it's lock_method!
         | 
| 78 | 
            +
            email: 
         | 
| 79 | 
            +
            - seamus@abshere.net
         | 
| 80 | 
            +
            executables: []
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            extensions: []
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            extra_rdoc_files: []
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            files: 
         | 
| 87 | 
            +
            - .gitignore
         | 
| 88 | 
            +
            - Gemfile
         | 
| 89 | 
            +
            - README.rdoc
         | 
| 90 | 
            +
            - Rakefile
         | 
| 91 | 
            +
            - lib/lock_method.rb
         | 
| 92 | 
            +
            - lib/lock_method/config.rb
         | 
| 93 | 
            +
            - lib/lock_method/lock.rb
         | 
| 94 | 
            +
            - lib/lock_method/storage.rb
         | 
| 95 | 
            +
            - lib/lock_method/storage/default_storage_client.rb
         | 
| 96 | 
            +
            - lib/lock_method/version.rb
         | 
| 97 | 
            +
            - lock_method.gemspec
         | 
| 98 | 
            +
            - test/helper.rb
         | 
| 99 | 
            +
            - test/shared_tests.rb
         | 
| 100 | 
            +
            - test/test_default_storage.rb
         | 
| 101 | 
            +
            - test/test_memcached_storage.rb
         | 
| 102 | 
            +
            - test/test_redis_storage.rb
         | 
| 103 | 
            +
            has_rdoc: true
         | 
| 104 | 
            +
            homepage: https://github.com/seamusabshere/lock_method
         | 
| 105 | 
            +
            licenses: []
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            post_install_message: 
         | 
| 108 | 
            +
            rdoc_options: []
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            require_paths: 
         | 
| 111 | 
            +
            - lib
         | 
| 112 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         | 
| 113 | 
            +
              none: false
         | 
| 114 | 
            +
              requirements: 
         | 
| 115 | 
            +
              - - ">="
         | 
| 116 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 117 | 
            +
                  hash: 3
         | 
| 118 | 
            +
                  segments: 
         | 
| 119 | 
            +
                  - 0
         | 
| 120 | 
            +
                  version: "0"
         | 
| 121 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         | 
| 122 | 
            +
              none: false
         | 
| 123 | 
            +
              requirements: 
         | 
| 124 | 
            +
              - - ">="
         | 
| 125 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 126 | 
            +
                  hash: 3
         | 
| 127 | 
            +
                  segments: 
         | 
| 128 | 
            +
                  - 0
         | 
| 129 | 
            +
                  version: "0"
         | 
| 130 | 
            +
            requirements: []
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            rubyforge_project: lock_method
         | 
| 133 | 
            +
            rubygems_version: 1.3.7
         | 
| 134 | 
            +
            signing_key: 
         | 
| 135 | 
            +
            specification_version: 3
         | 
| 136 | 
            +
            summary: Lets you lock methods (to memcached, redis, etc.) as though you had a lockfile for each one
         | 
| 137 | 
            +
            test_files: 
         | 
| 138 | 
            +
            - test/helper.rb
         | 
| 139 | 
            +
            - test/shared_tests.rb
         | 
| 140 | 
            +
            - test/test_default_storage.rb
         | 
| 141 | 
            +
            - test/test_memcached_storage.rb
         | 
| 142 | 
            +
            - test/test_redis_storage.rb
         |