mega_mutex-dalli 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
@@ -0,0 +1,4 @@
1
+ == 0.3.0
2
+
3
+ Bugfixes:
4
+ * #with_distributed_mutex was not returning the result of the block
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.
@@ -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.
@@ -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
@@ -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
@@ -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