redis-mutex 0.9.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/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +56 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/redis-mutex.rb +5 -0
- data/lib/redis/classy/mutex.rb +45 -0
- data/spec/redis-mutex_spec.rb +7 -0
- data/spec/spec_helper.rb +12 -0
- metadata +115 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.2)
|
5
|
+
git (1.2.5)
|
6
|
+
jeweler (1.5.2)
|
7
|
+
bundler (~> 1.0.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
rake (0.8.7)
|
11
|
+
redis (2.1.1)
|
12
|
+
redis-classy (0.9.0)
|
13
|
+
redis-namespace (~> 0.10.0)
|
14
|
+
redis-namespace (0.10.0)
|
15
|
+
redis (< 3.0.0)
|
16
|
+
rspec (2.5.0)
|
17
|
+
rspec-core (~> 2.5.0)
|
18
|
+
rspec-expectations (~> 2.5.0)
|
19
|
+
rspec-mocks (~> 2.5.0)
|
20
|
+
rspec-core (2.5.1)
|
21
|
+
rspec-expectations (2.5.0)
|
22
|
+
diff-lcs (~> 1.1.2)
|
23
|
+
rspec-mocks (2.5.0)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
bundler (~> 1.0.0)
|
30
|
+
jeweler (~> 1.5.2)
|
31
|
+
redis-classy (~> 0.9.0)
|
32
|
+
rspec (~> 2.5.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Kenn Ejima
|
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.rdoc
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
= Redis Mutex
|
2
|
+
|
3
|
+
Distrubuted non-blocking mutex in Ruby using Redis.
|
4
|
+
|
5
|
+
The idea was taken from - http://redis.io/commands/setnx and http://gist.github.com/457269
|
6
|
+
|
7
|
+
Requires the redis-classy gem.
|
8
|
+
|
9
|
+
== Install
|
10
|
+
|
11
|
+
gem install redis-mutex
|
12
|
+
|
13
|
+
== Usage
|
14
|
+
|
15
|
+
In Gemfile:
|
16
|
+
|
17
|
+
gem "redis-mutex"
|
18
|
+
|
19
|
+
In config/initializers/redis_mutex.rb:
|
20
|
+
|
21
|
+
Redis::Classy.db = Redis.new(:host => 'localhost')
|
22
|
+
|
23
|
+
There are four methods - new, lock, unlock and sweep:
|
24
|
+
|
25
|
+
mutex = Redis::Classy::Mutex.new(some_object)
|
26
|
+
mutex.lock
|
27
|
+
mutex.unlock
|
28
|
+
Redis::Classy::Mutex.sweep
|
29
|
+
|
30
|
+
It takes any Ruby objects that respond to :id, where the key is automatically set as "TheClass:id", or pass any string as a key.
|
31
|
+
|
32
|
+
Here's a sample usage in a Rails app:
|
33
|
+
|
34
|
+
class RoomController < ApplicationController
|
35
|
+
def enter
|
36
|
+
@room = Room.find_by_id(params[:id])
|
37
|
+
|
38
|
+
mutex = Redis::Classy::Mutex.new(@room) # key => "Room:123"
|
39
|
+
|
40
|
+
if mutex.lock # return true when you successfully obtain the lock for the room 123
|
41
|
+
...
|
42
|
+
do_something
|
43
|
+
...
|
44
|
+
mutex.unlock # exit the critical section ASAP
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
The caveat is that the lock just returns false when it fails and do not block.
|
50
|
+
|
51
|
+
If you take a closer look, you find that the actual key is structured in the following form:
|
52
|
+
|
53
|
+
Redis::Classy.keys
|
54
|
+
=> ["Redis::Classy::Mutex:Room:123"]
|
55
|
+
|
56
|
+
For more internal details, refer to https://github.com/kenn/redis-classy
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
gem.name = "redis-mutex"
|
15
|
+
gem.homepage = "http://github.com/kenn/redis-mutex"
|
16
|
+
gem.license = "MIT"
|
17
|
+
gem.summary = "Distrubuted non-blocking mutex in Ruby using Redis"
|
18
|
+
gem.description = "Distrubuted non-blocking mutex in Ruby using Redis"
|
19
|
+
gem.email = "kenn.ejima@gmail.com"
|
20
|
+
gem.authors = ["Kenn Ejima"]
|
21
|
+
end
|
22
|
+
Jeweler::RubygemsDotOrgTasks.new
|
23
|
+
|
24
|
+
require 'rspec/core'
|
25
|
+
require 'rspec/core/rake_task'
|
26
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
27
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "redis-mutex #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.0
|
data/lib/redis-mutex.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
class Redis::Classy::Mutex < Redis::Classy
|
2
|
+
|
3
|
+
TIMEOUT = 10
|
4
|
+
|
5
|
+
def initialize(object, timeout=TIMEOUT)
|
6
|
+
@now = Time.now.to_i
|
7
|
+
@expires_at = @now + timeout
|
8
|
+
super(object.is_a?(String) ? object : "#{object.class.name}:#{object.id}")
|
9
|
+
end
|
10
|
+
|
11
|
+
def lock
|
12
|
+
return true if self.setnx(@expires_at) # Success, the lock was acquired
|
13
|
+
return false if self.get.to_i > @now # Failure, someone took the lock and it is still effective
|
14
|
+
|
15
|
+
# The lock has expired but wasn't released... BAD!
|
16
|
+
return true if self.getset(@expires_at).to_i <= @now # Success, we acquired the previously expired lock!
|
17
|
+
return false # Dammit, it seems that someone else was even faster than us to acquire this lock.
|
18
|
+
end
|
19
|
+
|
20
|
+
def unlock
|
21
|
+
self.del if self.get.to_i == @expires_at # Release the lock if it seems to be yours.
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.sweep(timeout=TIMEOUT)
|
26
|
+
now = Time.now.to_i
|
27
|
+
keys = self.keys
|
28
|
+
|
29
|
+
return 0 if keys.empty?
|
30
|
+
|
31
|
+
values = self.mget(*keys)
|
32
|
+
|
33
|
+
expired_keys = [].tap do |array|
|
34
|
+
keys.each_with_index do |key, i|
|
35
|
+
array << key if !values[i].nil? and values[i].to_i <= now
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
expired_keys.each do |key|
|
40
|
+
self.del(key) if self.getset(key, now + timeout).to_i <= now # Make extra sure someone haven't released the lock yet.
|
41
|
+
end
|
42
|
+
|
43
|
+
expired_keys.size
|
44
|
+
end
|
45
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'redis-mutex'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis-mutex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.9.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kenn Ejima
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-02-28 00:00:00 -08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: redis-classy
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.0
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 2.5.0
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: bundler
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.0
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: jeweler
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.5.2
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
description: Distrubuted non-blocking mutex in Ruby using Redis
|
61
|
+
email: kenn.ejima@gmail.com
|
62
|
+
executables: []
|
63
|
+
|
64
|
+
extensions: []
|
65
|
+
|
66
|
+
extra_rdoc_files:
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.rdoc
|
69
|
+
files:
|
70
|
+
- .document
|
71
|
+
- .rspec
|
72
|
+
- Gemfile
|
73
|
+
- Gemfile.lock
|
74
|
+
- LICENSE.txt
|
75
|
+
- README.rdoc
|
76
|
+
- Rakefile
|
77
|
+
- VERSION
|
78
|
+
- lib/redis-mutex.rb
|
79
|
+
- lib/redis/classy/mutex.rb
|
80
|
+
- spec/redis-mutex_spec.rb
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
has_rdoc: true
|
83
|
+
homepage: http://github.com/kenn/redis-mutex
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
hash: 4175039287226783610
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
version: "0"
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: "0"
|
106
|
+
requirements: []
|
107
|
+
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 1.5.3
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: Distrubuted non-blocking mutex in Ruby using Redis
|
113
|
+
test_files:
|
114
|
+
- spec/redis-mutex_spec.rb
|
115
|
+
- spec/spec_helper.rb
|