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