lock_method 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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