distributed_mutex 1.0.0
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 +1 -0
- data/README.rdoc +25 -0
- data/Rakefile +134 -0
- data/VERSION +1 -0
- data/init.rb +1 -0
- data/lib/distributed_mutex.rb +88 -0
- data/lib/global_mutex.rb +36 -0
- data/lib/mutex_lock_timeout.rb +6 -0
- data/lib/mysql_mutex.rb +35 -0
- data/spec/config/database.stub +10 -0
- data/spec/global_mutex_spec.rb +130 -0
- data/spec/mysql_mutex_spec.rb +136 -0
- data/spec/spec_helper.rb +8 -0
- metadata +88 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
spec/config/database.yml
|
data/README.rdoc
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
= distributed_mutex
|
2
|
+
|
3
|
+
by Birkir A. Barkarson <birkirb@stoicviking.net>
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
A rails plugin proving an abstract distributed mutex along with a MySQL implementation that uses
|
8
|
+
mysql named locks. Others distributed locks using Memcached etc could be implemented using the same
|
9
|
+
framework.
|
10
|
+
|
11
|
+
== Installation from source
|
12
|
+
|
13
|
+
git clone git://github.com/birkirb/distributed_mutex.git
|
14
|
+
cd distributed_mutex
|
15
|
+
rake install
|
16
|
+
|
17
|
+
=== Rails Plugin Installation
|
18
|
+
|
19
|
+
script/plugin install git://github.com/birkirb/distributed_mutex.git
|
20
|
+
|
21
|
+
== Copyright
|
22
|
+
|
23
|
+
Author:: Birkir A. Barkarson <birkirb@stoicviking.net>
|
24
|
+
Copyright:: Copyright (c) 2009
|
25
|
+
License:: MIT License
|
data/Rakefile
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |s|
|
6
|
+
s.name = %q{distributed_mutex}
|
7
|
+
s.authors = ["Birkir A. Barkarson"]
|
8
|
+
s.description = %q{Framework for using a distributed mutex. Implementation of a mutex stored on a MySQL database.}
|
9
|
+
s.summary = s.description
|
10
|
+
s.email = %q{birkirb@stoicviking.net}
|
11
|
+
s.has_rdoc = true
|
12
|
+
s.homepage = %q{http://github.com/birkirb/distributed_mutex}
|
13
|
+
#s.rubyforge_project = %q{serializable}
|
14
|
+
#s.rubygems_version = %q{1.3.1}
|
15
|
+
#s.required_rubygems_version = "1.3.1"
|
16
|
+
s.add_dependency(%q<activerecord>, [">= 1.2"])
|
17
|
+
end
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'spec/rake/spectask'
|
24
|
+
|
25
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
26
|
+
t.spec_opts = ["-f specdoc", "-c"]
|
27
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
rescue LoadError
|
31
|
+
desc 'Spec rake task not available'
|
32
|
+
task :spec do
|
33
|
+
abort 'Spec rake task is not available. Be sure to install rspec as a gem or plugin'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# begin
|
38
|
+
# require 'cucumber'
|
39
|
+
# require 'cucumber/rake/task'
|
40
|
+
|
41
|
+
|
42
|
+
# desc "Run Cucumber feature tests"
|
43
|
+
# Cucumber::Rake::Task.new(:features) do |t|
|
44
|
+
# t.cucumber_opts = "--format pretty"
|
45
|
+
# end
|
46
|
+
|
47
|
+
# rescue LoadError
|
48
|
+
# desc 'Cucumber rake task not available'
|
49
|
+
# task :features do
|
50
|
+
# abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
|
54
|
+
begin
|
55
|
+
require 'spec/rake/spectask'
|
56
|
+
# require 'cucumber'
|
57
|
+
# require 'cucumber/rake/task'
|
58
|
+
require 'spec/rake/verify_rcov'
|
59
|
+
|
60
|
+
task :test do
|
61
|
+
Rake::Task[:spec].invoke
|
62
|
+
# Rake::Task[:features].invoke
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "Run tests with RCov"
|
66
|
+
namespace :rcov do
|
67
|
+
rm "coverage.data" if File.exist?("coverage.data")
|
68
|
+
|
69
|
+
# desc "Run Features with RCov"
|
70
|
+
# Cucumber::Rake::Task.new(:features) do |t|
|
71
|
+
# t.rcov = true
|
72
|
+
# t.rcov_opts = %w{ --exclude osx\/objc,gems\/,spec\/,features\/ --aggregate coverage.data}
|
73
|
+
# t.rcov_opts << %[-o "coverage"]
|
74
|
+
# end
|
75
|
+
|
76
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
77
|
+
t.spec_opts = ["-f specdoc", "-c"]
|
78
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
79
|
+
t.rcov = true
|
80
|
+
t.rcov_opts = %w{--exclude "spec/*,gems/*,features/*" --aggregate "coverage.data"}
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "Run both specs and features to generate aggregated coverage"
|
84
|
+
task :all do |t|
|
85
|
+
Rake::Task["rcov:spec"].invoke
|
86
|
+
# Rake::Task["rcov:features"].invoke
|
87
|
+
end
|
88
|
+
|
89
|
+
RCov::VerifyTask.new(:verify => 'rcov:all') do |t|
|
90
|
+
t.threshold = 93.98
|
91
|
+
t.index_html = 'coverage/index.html'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
rescue LoadError
|
95
|
+
desc 'Rcov rake task not available'
|
96
|
+
task :rcov do
|
97
|
+
abort 'rcov rake task is not available. Be sure to install rspec, rcov and cucumber as a gem or plugin'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
begin
|
102
|
+
require 'metric_fu'
|
103
|
+
|
104
|
+
MetricFu::Configuration.run do |config|
|
105
|
+
#define which metrics you want to use
|
106
|
+
config.metrics = [:churn, :flog, :flay, :reek, :roodi]
|
107
|
+
config.flay = { :dirs_to_flay => ['app', 'lib'] }
|
108
|
+
config.flog = { :dirs_to_flog => ['app', 'lib'] }
|
109
|
+
config.reek = { :dirs_to_reek => ['app', 'lib'] }
|
110
|
+
config.roodi = { :dirs_to_roodi => ['app', 'lib'] }
|
111
|
+
config.saikuro = { :output_directory => 'scratch_directory/saikuro',
|
112
|
+
:input_directory => ['app', 'lib'],
|
113
|
+
:cyclo => "",
|
114
|
+
:filter_cyclo => "0",
|
115
|
+
:warn_cyclo => "5",
|
116
|
+
:error_cyclo => "7",
|
117
|
+
:formater => "text"} #this needs to be set to "text"
|
118
|
+
config.churn = { :start_date => "1 year ago", :minimum_churn_count => 10}
|
119
|
+
config.rcov = { :test_files => ['test/**/*_test.rb',
|
120
|
+
'spec/**/*_spec.rb'],
|
121
|
+
:rcov_opts => ["--sort coverage",
|
122
|
+
"--no-html",
|
123
|
+
"--text-coverage",
|
124
|
+
"--no-color",
|
125
|
+
"--profile",
|
126
|
+
"--rails",
|
127
|
+
"--exclude /gems/,/Library/,spec"]}
|
128
|
+
end
|
129
|
+
|
130
|
+
rescue LoadError
|
131
|
+
# Too bad
|
132
|
+
end
|
133
|
+
|
134
|
+
task :default => [:spec]
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'mysql_mutex'
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'mutex_lock_timeout'
|
2
|
+
|
3
|
+
class DistributedMutex < Mutex
|
4
|
+
|
5
|
+
DEFAULT_TIMEOUT = 1
|
6
|
+
DEFAULT_EXCEPTION_ON_TIMEOUT = false
|
7
|
+
|
8
|
+
attr_reader :key, :timeout, :exception_on_timeout
|
9
|
+
alias excluse_unlock unlock
|
10
|
+
|
11
|
+
def initialize(key, timeout = DEFAULT_TIMEOUT, exception_on_timeout = DEFAULT_EXCEPTION_ON_TIMEOUT)
|
12
|
+
@key = key
|
13
|
+
@timeout = timeout
|
14
|
+
@locked = false
|
15
|
+
@exception_on_timeout = exception_on_timeout
|
16
|
+
end
|
17
|
+
|
18
|
+
def lock
|
19
|
+
if @locked = get_lock
|
20
|
+
true
|
21
|
+
else
|
22
|
+
if @exception_on_timeout
|
23
|
+
raise MutexLockTimeout.new
|
24
|
+
else
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def locked?
|
31
|
+
@locked
|
32
|
+
end
|
33
|
+
|
34
|
+
def synchronize(&block)
|
35
|
+
if self.lock
|
36
|
+
begin
|
37
|
+
yield
|
38
|
+
ensure
|
39
|
+
self.unlock
|
40
|
+
end
|
41
|
+
true
|
42
|
+
else
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def try_lock
|
48
|
+
was_locked = nil
|
49
|
+
begin
|
50
|
+
self.lock
|
51
|
+
was_locked = locked?
|
52
|
+
ensure
|
53
|
+
self.unlock
|
54
|
+
end
|
55
|
+
was_locked
|
56
|
+
end
|
57
|
+
|
58
|
+
def unlock
|
59
|
+
if @locked
|
60
|
+
if release_lock
|
61
|
+
@locked = false
|
62
|
+
true
|
63
|
+
else
|
64
|
+
false
|
65
|
+
end
|
66
|
+
else
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.synchronize(key, timeout = DEFAULT_TIMEOUT, exception_on_timeout = DEFAULT_EXCEPTION_ON_TIMEOUT, &block)
|
72
|
+
mutex = new(key, timeout, exception_on_timeout)
|
73
|
+
mutex.synchronize(&block)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Return true if and only if a lock is obtained
|
79
|
+
def get_lock
|
80
|
+
raise 'Method not implemented'
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return true if and only if a lock is released
|
84
|
+
def release_lock
|
85
|
+
raise 'Method not implemented'
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
data/lib/global_mutex.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'distributed_mutex'
|
2
|
+
|
3
|
+
# Simple global variable mutex, not production quality more
|
4
|
+
# as a test implementation for the super class.
|
5
|
+
class GlobalMutex < DistributedMutex
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def get_lock
|
10
|
+
if @timeout && @timeout > 1
|
11
|
+
1.upto(@timeout) do
|
12
|
+
if set_global_mutex
|
13
|
+
return true
|
14
|
+
else
|
15
|
+
sleep(1)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
return set_global_mutex
|
20
|
+
end
|
21
|
+
|
22
|
+
def release_lock
|
23
|
+
eval("$#{@key} = nil")
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_global_mutex
|
28
|
+
if nil == eval("$#{@key}")
|
29
|
+
eval("$#{@key} = 1")
|
30
|
+
true
|
31
|
+
else
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/mysql_mutex.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'distributed_mutex'
|
3
|
+
|
4
|
+
class MySQLMutex < DistributedMutex
|
5
|
+
|
6
|
+
def initialize(key, timeout = DEFAULT_TIMEOUT, exception_on_timeout = DEFAULT_EXCEPTION_ON_TIMEOUT, connection = ActiveRecord::Base.connection)
|
7
|
+
@connection = connection
|
8
|
+
@lock_was_free = false
|
9
|
+
@get_sql = ActiveRecord::Base.send(:sanitize_sql_array,["SELECT IS_FREE_LOCK(?), GET_LOCK(?,?)", key, key, timeout])
|
10
|
+
@release_sql = ActiveRecord::Base.send(:sanitize_sql_array,["SELECT RELEASE_LOCK(?)", key])
|
11
|
+
super(key, timeout, exception_on_timeout)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.synchronize(key, timeout = DEFAULT_TIMEOUT, exception_on_timeout = DEFAULT_TIMEOUT, con = ActiveRecord::Base.connection, &block)
|
15
|
+
mutex = new(key, timeout, exception_on_timeout, con)
|
16
|
+
mutex.synchronize(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def get_lock
|
22
|
+
is_free_lock, get_lock = @connection.select_rows(@get_sql).first
|
23
|
+
@lock_was_free = ('1' == is_free_lock)
|
24
|
+
'1' == get_lock
|
25
|
+
end
|
26
|
+
|
27
|
+
def release_lock
|
28
|
+
if @lock_was_free
|
29
|
+
'1' == @connection.select_value(@release_sql)
|
30
|
+
else
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
require 'global_mutex'
|
3
|
+
|
4
|
+
describe GlobalMutex, 'when created with a key and timeout' do
|
5
|
+
|
6
|
+
it 'should give access to the key and timeout values' do
|
7
|
+
mutex = GlobalMutex.new('test', 1)
|
8
|
+
mutex.timeout.should == 1
|
9
|
+
mutex.key.should == 'test'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should allow querying on lock state' do
|
13
|
+
mutex = GlobalMutex.new('test', 1)
|
14
|
+
mutex.locked?.should == false
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should allow locking and unlocking and report the success or failure of these operations' do
|
18
|
+
mutex = GlobalMutex.new('test', 1)
|
19
|
+
mutex.lock.should == true
|
20
|
+
mutex.locked?.should == true
|
21
|
+
|
22
|
+
mutex_2 = GlobalMutex.new('test', 0)
|
23
|
+
mutex_2.locked?.should == false
|
24
|
+
mutex_2.lock.should == false
|
25
|
+
mutex_2.locked?.should == false
|
26
|
+
mutex_2.unlock.should == false
|
27
|
+
|
28
|
+
mutex.unlock.should == true
|
29
|
+
mutex.locked?.should == false
|
30
|
+
|
31
|
+
mutex_2.lock.should == true
|
32
|
+
mutex_2.locked?.should == true
|
33
|
+
mutex.locked?.should == false
|
34
|
+
mutex_2.unlock.should == true
|
35
|
+
mutex_2.locked?.should == false
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should allow lock attempts and report the success or failure of the operation' do
|
39
|
+
mutex = GlobalMutex.new('test', 1)
|
40
|
+
mutex.try_lock.should == true
|
41
|
+
mutex.locked?.should == false
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should allow locked operations in a block via #synchronize' do
|
45
|
+
block_assigned_variable = 0
|
46
|
+
|
47
|
+
mutex = GlobalMutex.new('test', 1)
|
48
|
+
mutex.locked?.should == false
|
49
|
+
success = mutex.synchronize do
|
50
|
+
block_assigned_variable = 1
|
51
|
+
mutex.locked?.should == true
|
52
|
+
end
|
53
|
+
success.should == true
|
54
|
+
mutex.locked?.should == false
|
55
|
+
block_assigned_variable.should == 1
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should report failed block operationds via #synchronize' do
|
59
|
+
block_assigned_variable = 0
|
60
|
+
|
61
|
+
mutex_locked = GlobalMutex.new('test', 1)
|
62
|
+
mutex_locked.lock
|
63
|
+
mutex_locked.locked?.should == true
|
64
|
+
|
65
|
+
mutex = GlobalMutex.new('test', 1)
|
66
|
+
mutex.locked?.should == false
|
67
|
+
success = mutex.synchronize do
|
68
|
+
block_assigned_variable = 1
|
69
|
+
end
|
70
|
+
|
71
|
+
success.should == false
|
72
|
+
mutex.locked?.should == false
|
73
|
+
block_assigned_variable.should == 0
|
74
|
+
mutex_locked.unlock.should == true
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should pass up error in synchronize' do
|
78
|
+
mutex = GlobalMutex.new('test', 1)
|
79
|
+
lambda do
|
80
|
+
mutex.synchronize do
|
81
|
+
raise 'some_error'
|
82
|
+
end
|
83
|
+
end.should raise_error('some_error')
|
84
|
+
mutex.locked?.should == false
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should wait until the timeout passes before giving up' do
|
88
|
+
mutex_locked = GlobalMutex.new('test', 1)
|
89
|
+
mutex_locked.lock
|
90
|
+
|
91
|
+
time_start = Time.now
|
92
|
+
mutex = GlobalMutex.new('test', 3)
|
93
|
+
mutex.lock.should == false
|
94
|
+
time_end = Time.now
|
95
|
+
(time_end - time_start + 0.05).should > 3
|
96
|
+
|
97
|
+
mutex_locked.unlock.should == true
|
98
|
+
|
99
|
+
mutex = GlobalMutex.new('test', 3)
|
100
|
+
mutex.lock.should == true
|
101
|
+
mutex.unlock.should == true
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should throw an exception when so enabled on mutex timeout' do
|
105
|
+
mutex_locked = GlobalMutex.new('test', 1)
|
106
|
+
mutex_locked.lock
|
107
|
+
|
108
|
+
mutex = GlobalMutex.new('test', 0, true)
|
109
|
+
lambda { mutex.lock }.should raise_error(MutexLockTimeout, 'Mutex lock operation timed out')
|
110
|
+
mutex.locked?.should == false
|
111
|
+
mutex_locked.unlock.should == true
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
describe GlobalMutex, 'when access via a class synchronized method' do
|
117
|
+
|
118
|
+
it 'should use key and time values for direct access to the #synchronize' do
|
119
|
+
block_assigned_variable = 0
|
120
|
+
|
121
|
+
success = GlobalMutex.synchronize('test', 1) do
|
122
|
+
GlobalMutex.new('test', 0).try_lock.should == false
|
123
|
+
block_assigned_variable = 1
|
124
|
+
end
|
125
|
+
success.should == true
|
126
|
+
block_assigned_variable.should == 1
|
127
|
+
GlobalMutex.new('test', 0).try_lock.should == true
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
require 'mysql_mutex'
|
3
|
+
|
4
|
+
describe MySQLMutex, 'with a lock on an open mysql connection' do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
ActiveRecord::Base.configurations = database_config
|
8
|
+
end
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
$output = ""
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should work with a synchronized block in one thread' do
|
15
|
+
thread_1 = Thread.new do
|
16
|
+
ActiveRecord::Base.establish_connection(:test)
|
17
|
+
MySQLMutex.synchronize('test', 1) do
|
18
|
+
$output += "1\n"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
thread_1.join
|
22
|
+
$output.should == "1\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should work with multible threads' do
|
26
|
+
$output = Array.new
|
27
|
+
threads = Array.new
|
28
|
+
1.upto(3) do |number|
|
29
|
+
threads << Thread.new(number) do |number|
|
30
|
+
con = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations['test'])
|
31
|
+
MySQLMutex.synchronize('test', 5, con) do
|
32
|
+
$output << number
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
threads.each { |t| t.join }
|
38
|
+
|
39
|
+
$output.sort.should == [1, 2, 3]
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should work with two excluding threads' do
|
43
|
+
thread_1_mutex = nil
|
44
|
+
thread_1 = Thread.new do
|
45
|
+
con = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations['test'])
|
46
|
+
thread_1_mutex = MySQLMutex.new('test', 1, false, con)
|
47
|
+
$output += "1-RUN"
|
48
|
+
$output += "-LOCK" if thread_1_mutex.lock
|
49
|
+
end
|
50
|
+
|
51
|
+
thread_1.join
|
52
|
+
|
53
|
+
thread_2 = Thread.new do
|
54
|
+
con = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations['test'])
|
55
|
+
mutex = MySQLMutex.new('test', 1, false, con)
|
56
|
+
$output += "-2-RUN"
|
57
|
+
$output += "-LOCK" if mutex.lock
|
58
|
+
end
|
59
|
+
|
60
|
+
thread_2.join
|
61
|
+
$output.should == "1-RUN-LOCK-2-RUN"
|
62
|
+
thread_1_mutex.unlock.should == true
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should not be released by a nested lock on the same connection' do
|
66
|
+
thread_1_mutex = nil
|
67
|
+
thread_2_mutex = nil
|
68
|
+
|
69
|
+
con = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations['test'])
|
70
|
+
thread_1_mutex_1 = MySQLMutex.new('test', 1, false, con)
|
71
|
+
|
72
|
+
thread_1_mutex_1.lock.should == true
|
73
|
+
|
74
|
+
thread_1_mutex_2 = MySQLMutex.new('test', 1, false, con)
|
75
|
+
|
76
|
+
thread_1_mutex_2.lock.should == true
|
77
|
+
thread_1_mutex_2.unlock.should == true
|
78
|
+
|
79
|
+
thread_2 = Thread.new do
|
80
|
+
con = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations['test'])
|
81
|
+
thread_2_mutex = MySQLMutex.new('test', 1, false, con)
|
82
|
+
thread_2_mutex.lock.should == false # Should still be locked.
|
83
|
+
end
|
84
|
+
|
85
|
+
thread_2.join
|
86
|
+
thread_1_mutex_1.unlock.should == true
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should not be released by a nested synchronized lock on the same connection' do
|
90
|
+
con = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations['test'])
|
91
|
+
sub_thread_executed = false
|
92
|
+
MySQLMutex.synchronize('test', 1, true, con) do
|
93
|
+
MySQLMutex.synchronize('test', 1, false, con) do
|
94
|
+
sub_thread_executed = true
|
95
|
+
end
|
96
|
+
|
97
|
+
thread_2 = Thread.new do
|
98
|
+
con = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations['test'])
|
99
|
+
MySQLMutex.synchronize('test', 1, false, con) do
|
100
|
+
fail
|
101
|
+
end
|
102
|
+
end
|
103
|
+
thread_2.join
|
104
|
+
end
|
105
|
+
|
106
|
+
sub_thread_executed.should == true
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should released nested synchronized locks when an error occurs' do
|
110
|
+
con = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations['test'])
|
111
|
+
sub_thread_executed = false
|
112
|
+
begin
|
113
|
+
lambda do
|
114
|
+
MySQLMutex.synchronize('test', 1, true, con) do
|
115
|
+
MySQLMutex.synchronize('test', 1, false, con) do
|
116
|
+
raise 'TestError'
|
117
|
+
fail
|
118
|
+
end
|
119
|
+
fail
|
120
|
+
end
|
121
|
+
end.should raise_error(RuntimeError, 'TestError')
|
122
|
+
|
123
|
+
thread_2 = Thread.new do
|
124
|
+
con = ActiveRecord::Base.mysql_connection(ActiveRecord::Base.configurations['test'])
|
125
|
+
MySQLMutex.synchronize('test', 1, false, con) do
|
126
|
+
sub_thread_executed = true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
thread_2.join
|
130
|
+
rescue => err
|
131
|
+
fail(err)
|
132
|
+
end
|
133
|
+
|
134
|
+
sub_thread_executed.should == true
|
135
|
+
end
|
136
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: distributed_mutex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Birkir A. Barkarson
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-05 00:00:00 +09:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activerecord
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 2
|
30
|
+
version: "1.2"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
description: Framework for using a distributed mutex. Implementation of a mutex stored on a MySQL database.
|
34
|
+
email: birkirb@stoicviking.net
|
35
|
+
executables: []
|
36
|
+
|
37
|
+
extensions: []
|
38
|
+
|
39
|
+
extra_rdoc_files:
|
40
|
+
- README.rdoc
|
41
|
+
files:
|
42
|
+
- .gitignore
|
43
|
+
- README.rdoc
|
44
|
+
- Rakefile
|
45
|
+
- VERSION
|
46
|
+
- init.rb
|
47
|
+
- lib/distributed_mutex.rb
|
48
|
+
- lib/global_mutex.rb
|
49
|
+
- lib/mutex_lock_timeout.rb
|
50
|
+
- lib/mysql_mutex.rb
|
51
|
+
- spec/config/database.stub
|
52
|
+
- spec/global_mutex_spec.rb
|
53
|
+
- spec/mysql_mutex_spec.rb
|
54
|
+
- spec/spec_helper.rb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://github.com/birkirb/distributed_mutex
|
57
|
+
licenses: []
|
58
|
+
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options:
|
61
|
+
- --charset=UTF-8
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.6
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Framework for using a distributed mutex. Implementation of a mutex stored on a MySQL database.
|
85
|
+
test_files:
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
- spec/mysql_mutex_spec.rb
|
88
|
+
- spec/global_mutex_spec.rb
|