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
|