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 ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ rdoc/*
6
+ secret.sh
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cache_method.gemspec
4
+ gemspec
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
@@ -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
@@ -0,0 +1,3 @@
1
+ module LockMethod
2
+ VERSION = "0.0.1"
3
+ end
@@ -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,10 @@
1
+ require 'helper'
2
+
3
+ class TestDefaultStorageClient < Test::Unit::TestCase
4
+ def setup
5
+ LockMethod.config.storage = nil
6
+ LockMethod.storage.flush
7
+ end
8
+
9
+ include SharedTests
10
+ end
@@ -0,0 +1,12 @@
1
+ require 'helper'
2
+
3
+ require 'memcached'
4
+
5
+ class TestMemcachedStorage < Test::Unit::TestCase
6
+ def setup
7
+ LockMethod.config.storage = Memcached.new 'localhost:11211'
8
+ LockMethod.storage.flush
9
+ end
10
+
11
+ include SharedTests
12
+ 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