mega_mutex 0.1.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/LICENSE +20 -0
- data/README.markdown +43 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/mega_mutex/cross_process_mutex.rb +100 -0
- data/lib/mega_mutex.rb +61 -0
- data/mega_mutex.gemspec +55 -0
- data/spec/lib/mega_mutex_spec.rb +129 -0
- metadata +85 -0
data/.document
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,43 @@
|
|
1
|
+
# mega_mutex
|
2
|
+
|
3
|
+
## Why
|
4
|
+
|
5
|
+
Sometimes I need to do this:
|
6
|
+
|
7
|
+
unless enough_things?
|
8
|
+
make_more_things
|
9
|
+
end
|
10
|
+
|
11
|
+
Sometimes though, if I'm running lots of processes in parallel, I 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.
|
12
|
+
|
13
|
+
## How
|
14
|
+
|
15
|
+
Suppose you have a ThingMaker:
|
16
|
+
|
17
|
+
class ThingMaker
|
18
|
+
include MegaMutex
|
19
|
+
|
20
|
+
def ensure_just_enough_things
|
21
|
+
with_cross_process_mutex("ThingMaker Mutex ID") do
|
22
|
+
unless enough_things?
|
23
|
+
make_more_things
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
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.
|
30
|
+
|
31
|
+
## Configuration
|
32
|
+
|
33
|
+
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.
|
34
|
+
|
35
|
+
By default, MegaMutex will attempt to connect to a memcache on the local machine, but you can configure any number of servers like so:
|
36
|
+
|
37
|
+
MegaMutex.configure do |config|
|
38
|
+
config.memcache_servers = ['mc1', 'mc2']
|
39
|
+
end
|
40
|
+
|
41
|
+
## Copyright
|
42
|
+
|
43
|
+
Copyright (c) 2009 Songkick.com. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "mega_mutex"
|
8
|
+
gem.summary = %Q{Cross-process mutex using MemCache}
|
9
|
+
gem.description = %Q{Cross-process mutex using MemCache}
|
10
|
+
gem.email = "developers@songkick.com"
|
11
|
+
gem.homepage = "http://github.com/songkick/mega_mutex"
|
12
|
+
gem.authors = ["Matt Johnson", "Matt Wynne"]
|
13
|
+
gem.add_dependency 'memcache-client', '>= 1.7.4'
|
14
|
+
gem.add_dependency 'logging', '>= 1.1.4'
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
require 'rake/rdoctask'
|
39
|
+
Rake::RDocTask.new do |rdoc|
|
40
|
+
if File.exist?('VERSION')
|
41
|
+
version = File.read('VERSION')
|
42
|
+
else
|
43
|
+
version = ""
|
44
|
+
end
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "mega_mutex #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'logging'
|
2
|
+
require 'memcache'
|
3
|
+
|
4
|
+
module MegaMutex
|
5
|
+
class TimeoutError < Exception; end
|
6
|
+
|
7
|
+
class CrossProcessMutex
|
8
|
+
|
9
|
+
class Configuration
|
10
|
+
attr_accessor :memcache_servers
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@memcache_servers = 'localhost'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def configure
|
19
|
+
yield configuration
|
20
|
+
end
|
21
|
+
|
22
|
+
def configuration
|
23
|
+
@configuration ||= Configuration.new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(key, timeout = nil)
|
28
|
+
@key = key
|
29
|
+
@timeout = timeout
|
30
|
+
end
|
31
|
+
|
32
|
+
def logger
|
33
|
+
Logging::Logger[self]
|
34
|
+
end
|
35
|
+
|
36
|
+
def run(&block)
|
37
|
+
@start_time = Time.now
|
38
|
+
log "Attempting to lock cross-process mutex..."
|
39
|
+
lock!
|
40
|
+
log "Locked. Running critical section..."
|
41
|
+
yield
|
42
|
+
log "Critical section complete. Unlocking..."
|
43
|
+
ensure
|
44
|
+
unlock!
|
45
|
+
log "Unlocking Mutex."
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def timeout?
|
51
|
+
return false unless @timeout
|
52
|
+
Time.now > @start_time + @timeout
|
53
|
+
end
|
54
|
+
|
55
|
+
def log(message)
|
56
|
+
logger.debug do
|
57
|
+
"(key:#{@key}) (lock_id:#{my_lock_id}) #{message}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def lock!
|
62
|
+
until timeout?
|
63
|
+
return if attempt_to_lock == my_lock_id
|
64
|
+
sleep 0.1
|
65
|
+
end
|
66
|
+
raise TimeoutError.new("Failed to obtain a lock within #{@timeout} seconds.")
|
67
|
+
end
|
68
|
+
|
69
|
+
def attempt_to_lock
|
70
|
+
if current_lock.nil?
|
71
|
+
set_current_lock my_lock_id
|
72
|
+
end
|
73
|
+
current_lock
|
74
|
+
end
|
75
|
+
|
76
|
+
def unlock!
|
77
|
+
cache.delete(@key) if locked_by_me?
|
78
|
+
end
|
79
|
+
|
80
|
+
def locked_by_me?
|
81
|
+
current_lock == my_lock_id
|
82
|
+
end
|
83
|
+
|
84
|
+
def current_lock
|
85
|
+
cache.get(@key)
|
86
|
+
end
|
87
|
+
|
88
|
+
def set_current_lock(new_lock)
|
89
|
+
cache.add(@key, my_lock_id)
|
90
|
+
end
|
91
|
+
|
92
|
+
def my_lock_id
|
93
|
+
@my_lock_id ||= "#{Process.pid.to_s}.#{self.object_id.to_s}.#{Time.now.to_i.to_s}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def cache
|
97
|
+
@cache ||= MemCache.new self.class.configuration.memcache_servers, :namespace => 'mega_mutex'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/mega_mutex.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
$:.push File.expand_path(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'mega_mutex/cross_process_mutex'
|
5
|
+
|
6
|
+
# == Why
|
7
|
+
#
|
8
|
+
# Sometimes I need to do this:
|
9
|
+
#
|
10
|
+
# unless enough_things?
|
11
|
+
# make_more_things
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Sometimes though, if I'm running lots of processes in parallel, I 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.
|
15
|
+
#
|
16
|
+
# == How
|
17
|
+
#
|
18
|
+
# Suppose you have a ThingMaker:
|
19
|
+
#
|
20
|
+
# class ThingMaker
|
21
|
+
# include MegaMutex
|
22
|
+
#
|
23
|
+
# def ensure_just_enough_things
|
24
|
+
# with_cross_process_mutex("ThingMaker Mutex ID") do
|
25
|
+
# unless enough_things?
|
26
|
+
# make_more_things
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# 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.
|
33
|
+
#
|
34
|
+
# == Configuration
|
35
|
+
#
|
36
|
+
# MegaMutex uses http://seattlerb.rubyforge.org/memcache-client/ to store the mutex, so your infrastructure must be set up to use memcache servers.
|
37
|
+
#
|
38
|
+
# By default, MegaMutex will attempt to connect to a memcache on the local machine, but you can configure any number of servers like so:
|
39
|
+
#
|
40
|
+
# MegaMutex.configure do |config|
|
41
|
+
# config.memcache_servers = ['mc1', 'mc2']
|
42
|
+
# end
|
43
|
+
module MegaMutex
|
44
|
+
|
45
|
+
##
|
46
|
+
# Wraps code that should only be run when the mutex has been obtained.
|
47
|
+
#
|
48
|
+
# The mutex_id uniquely identifies the section of code being run.
|
49
|
+
#
|
50
|
+
# You can optionally specify a :timeout to control how long to wait for the lock to be released
|
51
|
+
# before raising a MegaMutex::TimeoutError
|
52
|
+
#
|
53
|
+
# with_cross_process_mutex('my_mutex_id_1234', :timeout => 20) do
|
54
|
+
# do_something!
|
55
|
+
# end
|
56
|
+
def with_cross_process_mutex(mutex_id, options = {}, &block)
|
57
|
+
mutex = CrossProcessMutex.new(mutex_id, options[:timeout])
|
58
|
+
mutex.run(&block)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
data/mega_mutex.gemspec
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mega_mutex}
|
8
|
+
s.version = "0.1.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"]
|
12
|
+
s.date = %q{2009-08-18}
|
13
|
+
s.description = %q{Cross-process mutex using MemCache}
|
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
|
+
"LICENSE",
|
23
|
+
"README.markdown",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/mega_mutex.rb",
|
27
|
+
"lib/mega_mutex/cross_process_mutex.rb",
|
28
|
+
"mega_mutex.gemspec",
|
29
|
+
"spec/lib/mega_mutex_spec.rb"
|
30
|
+
]
|
31
|
+
s.homepage = %q{http://github.com/songkick/mega_mutex}
|
32
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
33
|
+
s.require_paths = ["lib"]
|
34
|
+
s.rubygems_version = %q{1.3.4}
|
35
|
+
s.summary = %q{Cross-process mutex using MemCache}
|
36
|
+
s.test_files = [
|
37
|
+
"spec/lib/mega_mutex_spec.rb"
|
38
|
+
]
|
39
|
+
|
40
|
+
if s.respond_to? :specification_version then
|
41
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
42
|
+
s.specification_version = 3
|
43
|
+
|
44
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
45
|
+
s.add_runtime_dependency(%q<memcache-client>, [">= 1.7.4"])
|
46
|
+
s.add_runtime_dependency(%q<logging>, [">= 1.1.4"])
|
47
|
+
else
|
48
|
+
s.add_dependency(%q<memcache-client>, [">= 1.7.4"])
|
49
|
+
s.add_dependency(%q<logging>, [">= 1.1.4"])
|
50
|
+
end
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<memcache-client>, [">= 1.7.4"])
|
53
|
+
s.add_dependency(%q<logging>, [">= 1.1.4"])
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../lib/mega_mutex')
|
2
|
+
|
3
|
+
# Logging::Logger[:root].add_appenders(Logging::Appenders.stdout)
|
4
|
+
|
5
|
+
module MegaMutex
|
6
|
+
describe MegaMutex do
|
7
|
+
def logger
|
8
|
+
Logging::Logger['Specs']
|
9
|
+
end
|
10
|
+
|
11
|
+
before(:all) do
|
12
|
+
@old_abort_on_exception_value = Thread.abort_on_exception
|
13
|
+
Thread.abort_on_exception = true
|
14
|
+
end
|
15
|
+
after(:all) do
|
16
|
+
Thread.abort_on_exception = @old_abort_on_exception_value
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "two blocks, one fast, one slow" do
|
20
|
+
before(:each) do
|
21
|
+
@errors = []
|
22
|
+
@mutually_exclusive_block = lambda do
|
23
|
+
@errors << "Someone else is running this code!" if @running
|
24
|
+
@running = true
|
25
|
+
sleep 0.5
|
26
|
+
@running = nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "with no lock" do
|
31
|
+
it "trying to run the block twice should raise an error" do
|
32
|
+
threads = []
|
33
|
+
threads << Thread.new(&@mutually_exclusive_block)
|
34
|
+
threads << Thread.new(&@mutually_exclusive_block)
|
35
|
+
threads.each{ |t| t.join }
|
36
|
+
@errors.should_not be_empty
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "with the same lock key" do
|
41
|
+
before(:each) do
|
42
|
+
MemCache.new('localhost').delete(mutex_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def mutex_id
|
46
|
+
'tests-mutex-key'
|
47
|
+
end
|
48
|
+
|
49
|
+
include MegaMutex
|
50
|
+
|
51
|
+
[2, 20].each do |n|
|
52
|
+
describe "when #{n} blocks try to run at the same instant in the same process" do
|
53
|
+
it "should run each in turn" do
|
54
|
+
threads = []
|
55
|
+
n.times do
|
56
|
+
threads << Thread.new{ with_cross_process_mutex(mutex_id, &@mutually_exclusive_block) }
|
57
|
+
end
|
58
|
+
threads.each{ |t| t.join }
|
59
|
+
@errors.should be_empty
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "when the first block raises an exception" do
|
65
|
+
before(:each) do
|
66
|
+
with_cross_process_mutex(mutex_id) do
|
67
|
+
raise "Something went wrong in my code"
|
68
|
+
end rescue nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "the second block should find that the lock is clear and it can run" do
|
72
|
+
@success = nil
|
73
|
+
with_cross_process_mutex(mutex_id) do
|
74
|
+
@success = true
|
75
|
+
end
|
76
|
+
@success.should be_true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "when two blocks try to run at the same instant in different processes" do
|
81
|
+
before(:each) do
|
82
|
+
@lock_file = File.expand_path(File.dirname(__FILE__) + '/tmp_lock')
|
83
|
+
@errors_file = File.expand_path(File.dirname(__FILE__) + '/tmp_errors')
|
84
|
+
@mutually_exclusive_block = lambda {
|
85
|
+
File.open(@errors_file, 'w').puts "Someone else is running this code!" if File.exists?(@lock_file)
|
86
|
+
FileUtils.touch @lock_file
|
87
|
+
sleep 1
|
88
|
+
File.delete @lock_file
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
after(:each) do
|
93
|
+
File.delete @lock_file if File.exists?(@lock_file)
|
94
|
+
File.delete @errors_file if File.exists?(@errors_file)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should run each in turn" do
|
98
|
+
pids = []
|
99
|
+
pids << fork { with_cross_process_mutex(mutex_id, &@mutually_exclusive_block); Kernel.exit! }
|
100
|
+
pids << fork { with_cross_process_mutex(mutex_id, &@mutually_exclusive_block); Kernel.exit! }
|
101
|
+
pids.each{ |p| Process.wait(p) }
|
102
|
+
if File.exists?(@errors_file)
|
103
|
+
raise "Expected no errors but found #{File.read(@errors_file)}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "with a timeout" do
|
112
|
+
include MegaMutex
|
113
|
+
it "should raise an error if the code blocks for longer than the timeout" do
|
114
|
+
@success = false
|
115
|
+
threads = []
|
116
|
+
threads << Thread.new{ with_cross_process_mutex('foo'){ sleep 2 } }
|
117
|
+
threads << Thread.new do
|
118
|
+
begin
|
119
|
+
with_cross_process_mutex('foo', :timeout => 1 ){ puts 'nobody will ever hear me scream' }
|
120
|
+
rescue MegaMutex::TimeoutError
|
121
|
+
@success = true
|
122
|
+
end
|
123
|
+
end
|
124
|
+
threads.each{ |t| t.join }
|
125
|
+
@success.should be_true
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mega_mutex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Johnson
|
8
|
+
- Matt Wynne
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-08-18 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: memcache-client
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 1.7.4
|
25
|
+
version:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: logging
|
28
|
+
type: :runtime
|
29
|
+
version_requirement:
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.1.4
|
35
|
+
version:
|
36
|
+
description: Cross-process mutex using MemCache
|
37
|
+
email: developers@songkick.com
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- LICENSE
|
44
|
+
- README.markdown
|
45
|
+
files:
|
46
|
+
- .document
|
47
|
+
- .gitignore
|
48
|
+
- LICENSE
|
49
|
+
- README.markdown
|
50
|
+
- Rakefile
|
51
|
+
- VERSION
|
52
|
+
- lib/mega_mutex.rb
|
53
|
+
- lib/mega_mutex/cross_process_mutex.rb
|
54
|
+
- mega_mutex.gemspec
|
55
|
+
- spec/lib/mega_mutex_spec.rb
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: http://github.com/songkick/mega_mutex
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options:
|
62
|
+
- --charset=UTF-8
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.3.4
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Cross-process mutex using MemCache
|
84
|
+
test_files:
|
85
|
+
- spec/lib/mega_mutex_spec.rb
|