mega_mutex-dalli 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/History.txt +4 -0
- data/LICENSE +20 -0
- data/README.markdown +54 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/lib/mega_mutex.rb +106 -0
- data/lib/mega_mutex/distributed_mutex.rb +94 -0
- data/mega_mutex-dalli.gemspec +62 -0
- data/spec/lib/mega_mutex_spec.rb +133 -0
- data/spec/spec_helper.rb +32 -0
- metadata +129 -0
data/.document
ADDED
data/History.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Songkick.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# mega_mutex
|
2
|
+
|
3
|
+
A distributed mutex for Ruby.
|
4
|
+
|
5
|
+
## Why
|
6
|
+
|
7
|
+
Sometimes I need to do this:
|
8
|
+
|
9
|
+
unless enough_things?
|
10
|
+
make_more_things
|
11
|
+
end
|
12
|
+
|
13
|
+
If I'm running several processes in parallel, I can get a race condition that means two of the processes both think there are not enough things. So we go and make some more, even though we don't need to.
|
14
|
+
|
15
|
+
## How
|
16
|
+
|
17
|
+
Suppose you have a ThingMaker:
|
18
|
+
|
19
|
+
class ThingMaker
|
20
|
+
include MegaMutex
|
21
|
+
|
22
|
+
def ensure_just_enough_things
|
23
|
+
with_distributed_mutex("ThingMaker Mutex ID") do
|
24
|
+
unless enough_things?
|
25
|
+
make_more_things
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Now, thanks to the magic of MegaMutex, you can be sure that all processes trying to run this code will wait their turn, so each one will have the chance to make exactly the right number of things, without anyone else poking their nose in.
|
32
|
+
|
33
|
+
## Install
|
34
|
+
|
35
|
+
sudo gem install mega_mutex
|
36
|
+
|
37
|
+
|
38
|
+
## Configure
|
39
|
+
|
40
|
+
MegaMutex uses [memcache-client](http://seattlerb.rubyforge.org/memcache-client/) to store the mutex, so your infrastructure must be set up to use memcache servers.
|
41
|
+
|
42
|
+
By default, MegaMutex will attempt to connect to a memcache on the local machine, but you can configure any number of servers like so:
|
43
|
+
|
44
|
+
MegaMutex.configure do |config|
|
45
|
+
config.memcache_servers = ['mc1', 'mc2']
|
46
|
+
end
|
47
|
+
|
48
|
+
## Help
|
49
|
+
|
50
|
+
MegaMutex was built by the [Songkick.com](http://www.songkick.com) development team. Come chat to us on [#songkick](irc://chat.freenode.net/#songkick) on freenode.net.
|
51
|
+
|
52
|
+
## Copyright
|
53
|
+
|
54
|
+
Copyright (c) 2009 Songkick.com. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "mega_mutex-dalli"
|
8
|
+
gem.summary = %Q{Distributed mutex for Ruby using the Dalli memcached client}
|
9
|
+
gem.description = %Q{Distributed mutex for Ruby using Dalli memcached client}
|
10
|
+
gem.email = "developers@songkick.com"
|
11
|
+
gem.homepage = "http://github.com/msaffitz/mega_mutex-dalli"
|
12
|
+
gem.authors = ["Matt Johnson", "Matt Wynne", "Michael Saffitz"]
|
13
|
+
gem.add_dependency 'dalli', '>= 1.0.0'
|
14
|
+
gem.add_dependency 'logging', '>= 1.1.4'
|
15
|
+
gem.add_dependency 'retryable', ">= 0.1.0"
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
namespace :github do
|
24
|
+
task :push do
|
25
|
+
remotes = `git remote`.split("\n")
|
26
|
+
unless remotes.include?('github')
|
27
|
+
sh('git remote add github git@github.com:msaffitz/mega_mutex-dalli.git')
|
28
|
+
end
|
29
|
+
sh('git push github master')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require 'rspec/core/rake_task'
|
34
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
end
|
37
|
+
|
38
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
39
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
40
|
+
spec.rcov = true
|
41
|
+
end
|
42
|
+
|
43
|
+
task :spec => :check_dependencies
|
44
|
+
|
45
|
+
task :default => [:spec, 'github:push', 'gemcutter:release']
|
46
|
+
|
47
|
+
require 'rake/rdoctask'
|
48
|
+
Rake::RDocTask.new do |rdoc|
|
49
|
+
if File.exist?('VERSION')
|
50
|
+
version = File.read('VERSION')
|
51
|
+
else
|
52
|
+
version = ""
|
53
|
+
end
|
54
|
+
|
55
|
+
rdoc.rdoc_dir = 'rdoc'
|
56
|
+
rdoc.title = "mega_mutex-dalli #{version}"
|
57
|
+
rdoc.rdoc_files.include('README*')
|
58
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
59
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
data/lib/mega_mutex.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
$:.push File.expand_path(File.dirname(__FILE__)) unless $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
require 'mega_mutex/distributed_mutex'
|
4
|
+
|
5
|
+
# == Why
|
6
|
+
#
|
7
|
+
# Sometimes I need to do this:
|
8
|
+
#
|
9
|
+
# unless enough_things?
|
10
|
+
# make_more_things
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# If I'm running several processes in parallel, I can get a race condition that means two of the processes both think there are not enough things. So we go and make some more, even though we don't need to.
|
14
|
+
#
|
15
|
+
# == How
|
16
|
+
#
|
17
|
+
# Suppose you have a ThingMaker:
|
18
|
+
#
|
19
|
+
# class ThingMaker
|
20
|
+
# include MegaMutex
|
21
|
+
#
|
22
|
+
# def ensure_just_enough_things
|
23
|
+
# with_cross_process_mutex("ThingMaker Mutex ID") do
|
24
|
+
# unless enough_things?
|
25
|
+
# make_more_things
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# Now, thanks to the magic of MegaMutex, you can be sure that all processes trying to run this code will wait their turn, so each one will have the chance to make exactly the right number of things, without anyone else poking their nose in.
|
32
|
+
#
|
33
|
+
# == Configuration
|
34
|
+
#
|
35
|
+
# MegaMutex uses https://github.com/mperham/dalli to store the mutex, so your infrastructure must be set up to use memcache servers.
|
36
|
+
#
|
37
|
+
# By default, MegaMutex will attempt to connect to either ENV['MEMCACHE_SERVERS'] or localhost, but you can configure any number of servers like so:
|
38
|
+
#
|
39
|
+
# MegaMutex.configure do |config|
|
40
|
+
# config.memcache_servers = ['mc1', 'mc2']
|
41
|
+
# end
|
42
|
+
module MegaMutex
|
43
|
+
|
44
|
+
def self.get_current_lock(mutex_id)
|
45
|
+
DistributedMutex.new(mutex_id).current_lock
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Wraps code that should only be run when the mutex has been obtained.
|
50
|
+
#
|
51
|
+
# The mutex_id uniquely identifies the section of code being run.
|
52
|
+
#
|
53
|
+
# You can optionally specify a :timeout to control how long to wait for the lock to be released
|
54
|
+
# before raising a MegaMutex::TimeoutError
|
55
|
+
#
|
56
|
+
# with_distributed_mutex('my_mutex_id_1234', :timeout => 20) do
|
57
|
+
# do_something!
|
58
|
+
# end
|
59
|
+
def with_distributed_mutex(mutex_id, options = {}, &block)
|
60
|
+
mutex = DistributedMutex.new(mutex_id, options[:timeout])
|
61
|
+
begin
|
62
|
+
mutex.run(&block)
|
63
|
+
rescue Object => e
|
64
|
+
mega_mutex_insert_into_backtrace(
|
65
|
+
e,
|
66
|
+
/mega_mutex\.rb.*with_(distributed|cross_process)_mutex/,
|
67
|
+
"MegaMutex lock #{mutex_id}"
|
68
|
+
)
|
69
|
+
raise e
|
70
|
+
end
|
71
|
+
end
|
72
|
+
alias :with_cross_process_mutex :with_distributed_mutex
|
73
|
+
|
74
|
+
# inserts a line into a backtrace at the correct location
|
75
|
+
def mega_mutex_insert_into_backtrace(exception, re, newline)
|
76
|
+
loc = nil
|
77
|
+
exception.backtrace.each_with_index do |line, index|
|
78
|
+
if line =~ re
|
79
|
+
loc = index
|
80
|
+
break
|
81
|
+
end
|
82
|
+
end
|
83
|
+
if loc
|
84
|
+
exception.backtrace.insert(loc, newline)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class Configuration
|
89
|
+
attr_accessor :memcache_servers, :namespace
|
90
|
+
|
91
|
+
def initialize
|
92
|
+
@namespace = 'mega_mutex'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class << self
|
97
|
+
def configure
|
98
|
+
yield configuration
|
99
|
+
end
|
100
|
+
|
101
|
+
def configuration
|
102
|
+
@configuration ||= Configuration.new
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'logging'
|
2
|
+
require 'dalli'
|
3
|
+
require 'retryable'
|
4
|
+
|
5
|
+
module MegaMutex
|
6
|
+
class TimeoutError < Exception; end
|
7
|
+
|
8
|
+
class DistributedMutex
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def cache
|
12
|
+
@cache ||= Dalli::Client.new MegaMutex.configuration.memcache_servers, :namespace => MegaMutex.configuration.namespace
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(key, timeout = nil)
|
17
|
+
@key = key
|
18
|
+
@timeout = timeout
|
19
|
+
end
|
20
|
+
|
21
|
+
def logger
|
22
|
+
Logging::Logger[self]
|
23
|
+
end
|
24
|
+
|
25
|
+
def run(&block)
|
26
|
+
@start_time = Time.now
|
27
|
+
log "Attempting to lock mutex..."
|
28
|
+
lock!
|
29
|
+
log "Locked. Running critical section..."
|
30
|
+
result = yield
|
31
|
+
log "Critical section complete. Unlocking..."
|
32
|
+
result
|
33
|
+
ensure
|
34
|
+
unlock!
|
35
|
+
log "Unlocking Mutex."
|
36
|
+
end
|
37
|
+
|
38
|
+
def current_lock
|
39
|
+
cache.get(@key)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def timeout?
|
45
|
+
return false unless @timeout
|
46
|
+
Time.now > @start_time + @timeout
|
47
|
+
end
|
48
|
+
|
49
|
+
def log(message)
|
50
|
+
logger.debug do
|
51
|
+
"(key:#{@key}) (lock_id:#{my_lock_id}) #{message}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def lock!
|
56
|
+
until timeout?
|
57
|
+
retryable(:tries => 5, :sleep => 30, :on => Dalli::NetworkError) do
|
58
|
+
return if attempt_to_lock == my_lock_id
|
59
|
+
sleep 0.1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
raise TimeoutError.new("Failed to obtain a lock within #{@timeout} seconds.")
|
63
|
+
end
|
64
|
+
|
65
|
+
def attempt_to_lock
|
66
|
+
if current_lock.nil?
|
67
|
+
set_current_lock my_lock_id
|
68
|
+
end
|
69
|
+
current_lock
|
70
|
+
end
|
71
|
+
|
72
|
+
def unlock!
|
73
|
+
retryable(:tries => 5, :sleep => 30, :on => Dalli::NetworkError) do
|
74
|
+
cache.delete(@key) if locked_by_me?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def locked_by_me?
|
79
|
+
current_lock == my_lock_id
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_current_lock(new_lock)
|
83
|
+
cache.add(@key, my_lock_id)
|
84
|
+
end
|
85
|
+
|
86
|
+
def my_lock_id
|
87
|
+
@my_lock_id ||= "#{Process.pid.to_s}.#{self.object_id.to_s}.#{Time.now.to_i.to_s}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def cache
|
91
|
+
self.class.cache
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mega_mutex-dalli}
|
8
|
+
s.version = "0.3.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Matt Johnson", "Matt Wynne", "Michael Saffitz"]
|
12
|
+
s.date = %q{2011-01-24}
|
13
|
+
s.description = %q{Distributed mutex for Ruby using Dalli memcached client}
|
14
|
+
s.email = %q{developers@songkick.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"History.txt",
|
23
|
+
"LICENSE",
|
24
|
+
"README.markdown",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"lib/mega_mutex.rb",
|
28
|
+
"lib/mega_mutex/distributed_mutex.rb",
|
29
|
+
"mega_mutex-dalli.gemspec",
|
30
|
+
"spec/lib/mega_mutex_spec.rb",
|
31
|
+
"spec/spec_helper.rb"
|
32
|
+
]
|
33
|
+
s.homepage = %q{http://github.com/msaffitz/mega_mutex-dalli}
|
34
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = %q{1.3.7}
|
37
|
+
s.summary = %q{Distributed mutex for Ruby using the Dalli memcached client}
|
38
|
+
s.test_files = [
|
39
|
+
"spec/lib/mega_mutex_spec.rb",
|
40
|
+
"spec/spec_helper.rb"
|
41
|
+
]
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
45
|
+
s.specification_version = 3
|
46
|
+
|
47
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
48
|
+
s.add_runtime_dependency(%q<dalli>, [">= 1.0.0"])
|
49
|
+
s.add_runtime_dependency(%q<logging>, [">= 1.1.4"])
|
50
|
+
s.add_runtime_dependency(%q<retryable>, [">= 0.1.0"])
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<dalli>, [">= 1.0.0"])
|
53
|
+
s.add_dependency(%q<logging>, [">= 1.1.4"])
|
54
|
+
s.add_dependency(%q<retryable>, [">= 0.1.0"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<dalli>, [">= 1.0.0"])
|
58
|
+
s.add_dependency(%q<logging>, [">= 1.1.4"])
|
59
|
+
s.add_dependency(%q<retryable>, [">= 0.1.0"])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
module MegaMutex
|
4
|
+
describe MegaMutex do
|
5
|
+
include MegaMutex
|
6
|
+
|
7
|
+
def logger
|
8
|
+
Logging::Logger['Specs']
|
9
|
+
end
|
10
|
+
|
11
|
+
abort_on_thread_exceptions
|
12
|
+
|
13
|
+
describe "#with_distributed_mutex" do
|
14
|
+
it "returns the value returned by the block" do
|
15
|
+
result = with_distributed_mutex("mutex-key") { 12345 }
|
16
|
+
result.should == 12345
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "two blocks, one fast, one slow" do
|
21
|
+
before(:each) do
|
22
|
+
@errors = []
|
23
|
+
@mutually_exclusive_block = lambda do
|
24
|
+
@errors << "Someone else is running this code!" if @running
|
25
|
+
@running = true
|
26
|
+
sleep 0.5
|
27
|
+
@running = nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "with no lock" do
|
32
|
+
it "trying to run the block twice should raise an error" do
|
33
|
+
threads << Thread.new(&@mutually_exclusive_block)
|
34
|
+
threads << Thread.new(&@mutually_exclusive_block)
|
35
|
+
wait_for_threads_to_finish
|
36
|
+
@errors.should_not be_empty
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "with the same lock key" do
|
41
|
+
before(:each) do
|
42
|
+
Dalli::Client.new('localhost').delete(mutex_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def mutex_id
|
46
|
+
'tests-mutex-key'
|
47
|
+
end
|
48
|
+
|
49
|
+
[2, 20].each do |n|
|
50
|
+
describe "when #{n} blocks try to run at the same instant in the same process" do
|
51
|
+
it "should run each in turn" do
|
52
|
+
n.times do
|
53
|
+
threads << Thread.new{ with_distributed_mutex(mutex_id, &@mutually_exclusive_block) }
|
54
|
+
end
|
55
|
+
wait_for_threads_to_finish
|
56
|
+
@errors.should be_empty
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "when the first block raises an exception" do
|
62
|
+
before(:each) do
|
63
|
+
with_distributed_mutex(mutex_id) do
|
64
|
+
raise "Something went wrong in my code"
|
65
|
+
end rescue nil
|
66
|
+
end
|
67
|
+
|
68
|
+
it "the second block should find that the lock is clear and it can run" do
|
69
|
+
@success = nil
|
70
|
+
with_distributed_mutex(mutex_id) do
|
71
|
+
@success = true
|
72
|
+
end
|
73
|
+
@success.should be_true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "when two blocks try to run at the same instant in different processes" do
|
78
|
+
before(:each) do
|
79
|
+
@lock_file = File.expand_path(File.dirname(__FILE__) + '/tmp_lock')
|
80
|
+
@errors_file = File.expand_path(File.dirname(__FILE__) + '/tmp_errors')
|
81
|
+
@mutually_exclusive_block = lambda {
|
82
|
+
File.open(@errors_file, 'w').puts "Someone else is running this code!" if File.exists?(@lock_file)
|
83
|
+
FileUtils.touch @lock_file
|
84
|
+
sleep 1
|
85
|
+
File.delete @lock_file
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
after(:each) do
|
90
|
+
File.delete @lock_file if File.exists?(@lock_file)
|
91
|
+
File.delete @errors_file if File.exists?(@errors_file)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should run each in turn" do
|
95
|
+
pids = []
|
96
|
+
pids << fork { with_distributed_mutex(mutex_id, &@mutually_exclusive_block); Kernel.exit! }
|
97
|
+
pids << fork { with_distributed_mutex(mutex_id, &@mutually_exclusive_block); Kernel.exit! }
|
98
|
+
pids.each{ |p| Process.wait(p) }
|
99
|
+
if File.exists?(@errors_file)
|
100
|
+
raise "Expected no errors but found #{File.read(@errors_file)}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "with a timeout" do
|
109
|
+
|
110
|
+
it "should raise an error if the code blocks for longer than the timeout" do
|
111
|
+
@exception = nil
|
112
|
+
@first_thread_has_started = false
|
113
|
+
threads << Thread.new do
|
114
|
+
with_distributed_mutex('foo') do
|
115
|
+
@first_thread_has_started = true
|
116
|
+
sleep 0.2
|
117
|
+
end
|
118
|
+
end
|
119
|
+
threads << Thread.new do
|
120
|
+
sleep 0.1 until @first_thread_has_started
|
121
|
+
begin
|
122
|
+
with_distributed_mutex('foo', :timeout => 0.1 ) do
|
123
|
+
raise 'this code should never run'
|
124
|
+
end
|
125
|
+
rescue Exception => @exception
|
126
|
+
end
|
127
|
+
end
|
128
|
+
wait_for_threads_to_finish
|
129
|
+
assert @exception.is_a?(MegaMutex::TimeoutError), "Expected TimeoutError to be raised, but wasn't"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/mega_mutex')
|
2
|
+
require 'test/unit/assertions'
|
3
|
+
|
4
|
+
# Logging::Logger[:root].add_appenders(Logging::Appenders.stdout)
|
5
|
+
|
6
|
+
module ThreadHelper
|
7
|
+
def abort_on_thread_exceptions
|
8
|
+
before(:all) do
|
9
|
+
@old_abort_on_exception_value = Thread.abort_on_exception
|
10
|
+
Thread.abort_on_exception = true
|
11
|
+
end
|
12
|
+
after(:all) do
|
13
|
+
Thread.abort_on_exception = @old_abort_on_exception_value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ThreadExampleHelper
|
19
|
+
def threads
|
20
|
+
@threads ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait_for_threads_to_finish
|
24
|
+
threads.each{ |t| t.join }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Runner.configure do |config|
|
29
|
+
config.extend ThreadHelper
|
30
|
+
config.include ThreadExampleHelper
|
31
|
+
config.include Test::Unit::Assertions
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mega_mutex-dalli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
version: 0.3.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Matt Johnson
|
14
|
+
- Matt Wynne
|
15
|
+
- Michael Saffitz
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
|
20
|
+
date: 2011-01-24 00:00:00 -08:00
|
21
|
+
default_executable:
|
22
|
+
dependencies:
|
23
|
+
- !ruby/object:Gem::Dependency
|
24
|
+
name: dalli
|
25
|
+
prerelease: false
|
26
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
hash: 23
|
32
|
+
segments:
|
33
|
+
- 1
|
34
|
+
- 0
|
35
|
+
- 0
|
36
|
+
version: 1.0.0
|
37
|
+
type: :runtime
|
38
|
+
version_requirements: *id001
|
39
|
+
- !ruby/object:Gem::Dependency
|
40
|
+
name: logging
|
41
|
+
prerelease: false
|
42
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
hash: 27
|
48
|
+
segments:
|
49
|
+
- 1
|
50
|
+
- 1
|
51
|
+
- 4
|
52
|
+
version: 1.1.4
|
53
|
+
type: :runtime
|
54
|
+
version_requirements: *id002
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: retryable
|
57
|
+
prerelease: false
|
58
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 27
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
- 1
|
67
|
+
- 0
|
68
|
+
version: 0.1.0
|
69
|
+
type: :runtime
|
70
|
+
version_requirements: *id003
|
71
|
+
description: Distributed mutex for Ruby using Dalli memcached client
|
72
|
+
email: developers@songkick.com
|
73
|
+
executables: []
|
74
|
+
|
75
|
+
extensions: []
|
76
|
+
|
77
|
+
extra_rdoc_files:
|
78
|
+
- LICENSE
|
79
|
+
- README.markdown
|
80
|
+
files:
|
81
|
+
- .document
|
82
|
+
- .gitignore
|
83
|
+
- History.txt
|
84
|
+
- LICENSE
|
85
|
+
- README.markdown
|
86
|
+
- Rakefile
|
87
|
+
- VERSION
|
88
|
+
- lib/mega_mutex.rb
|
89
|
+
- lib/mega_mutex/distributed_mutex.rb
|
90
|
+
- mega_mutex-dalli.gemspec
|
91
|
+
- spec/lib/mega_mutex_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
has_rdoc: true
|
94
|
+
homepage: http://github.com/msaffitz/mega_mutex-dalli
|
95
|
+
licenses: []
|
96
|
+
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options:
|
99
|
+
- --charset=UTF-8
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
version: "0"
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.3.7
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: Distributed mutex for Ruby using the Dalli memcached client
|
127
|
+
test_files:
|
128
|
+
- spec/lib/mega_mutex_spec.rb
|
129
|
+
- spec/spec_helper.rb
|