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 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
@@ -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
@@ -0,0 +1,6 @@
1
+ class MutexLockTimeout < StandardError
2
+
3
+ def message
4
+ 'Mutex lock operation timed out'
5
+ end
6
+ end
@@ -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,10 @@
1
+ development: &defaults
2
+ adapter: mysql
3
+ database: mysql
4
+ username:
5
+ password:
6
+ host: localhost
7
+ encoding: utf8
8
+
9
+ test:
10
+ <<: *defaults
@@ -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
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ $:.unshift "#{File.dirname(__FILE__)}/../lib"
5
+
6
+ def database_config
7
+ YAML.load_file('spec/config/database.yml')
8
+ end
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