redis-mutex 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -3
- data/Gemfile.lock +15 -15
- data/README.md +112 -0
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/lib/redis/mutex.rb +45 -33
- data/lib/redis/mutex/macro.rb +41 -0
- data/redis-mutex.gemspec +20 -19
- data/spec/redis_mutex_spec.rb +46 -2
- data/spec/spec_helper.rb +1 -1
- metadata +54 -61
- data/README.rdoc +0 -80
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
diff-lcs (1.1.
|
4
|
+
diff-lcs (1.1.3)
|
5
5
|
git (1.2.5)
|
6
|
-
jeweler (1.6.
|
7
|
-
bundler (~> 1.0
|
6
|
+
jeweler (1.6.4)
|
7
|
+
bundler (~> 1.0)
|
8
8
|
git (>= 1.2.5)
|
9
9
|
rake
|
10
|
-
rake (0.9.
|
11
|
-
redis (2.2.
|
10
|
+
rake (0.9.2.2)
|
11
|
+
redis (2.2.2)
|
12
12
|
redis-classy (1.0.0)
|
13
13
|
redis-namespace (~> 1.0.0)
|
14
14
|
redis-namespace (1.0.3)
|
15
15
|
redis (< 3.0.0)
|
16
|
-
rspec (2.
|
17
|
-
rspec-core (~> 2.
|
18
|
-
rspec-expectations (~> 2.
|
19
|
-
rspec-mocks (~> 2.
|
20
|
-
rspec-core (2.
|
21
|
-
rspec-expectations (2.
|
16
|
+
rspec (2.7.0)
|
17
|
+
rspec-core (~> 2.7.0)
|
18
|
+
rspec-expectations (~> 2.7.0)
|
19
|
+
rspec-mocks (~> 2.7.0)
|
20
|
+
rspec-core (2.7.1)
|
21
|
+
rspec-expectations (2.7.0)
|
22
22
|
diff-lcs (~> 1.1.2)
|
23
|
-
rspec-mocks (2.
|
23
|
+
rspec-mocks (2.7.0)
|
24
24
|
|
25
25
|
PLATFORMS
|
26
26
|
ruby
|
27
27
|
|
28
28
|
DEPENDENCIES
|
29
|
-
bundler
|
30
|
-
jeweler
|
29
|
+
bundler
|
30
|
+
jeweler
|
31
31
|
redis-classy (~> 1.0.0)
|
32
|
-
rspec
|
32
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
Redis Mutex
|
2
|
+
===========
|
3
|
+
|
4
|
+
Distrubuted mutex in Ruby using Redis. Supports both **blocking** and **non-blocking** semantics.
|
5
|
+
|
6
|
+
The idea was taken from [the official SETNX doc](http://redis.io/commands/setnx).
|
7
|
+
|
8
|
+
Synopsis
|
9
|
+
--------
|
10
|
+
|
11
|
+
In the following example, only one thread / process / server can enter the locked block at one time.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
mutex = Redis::Mutex.new(:your_lock_name)
|
15
|
+
mutex.lock do
|
16
|
+
# do something exclusively
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
By default, when one is holding a lock, others wait **1 second** in total, polling **every 100ms** to see if the lock was released.
|
21
|
+
When 1 second has passed, the lock method returns `false`.
|
22
|
+
|
23
|
+
If you want to immediately receive `false` on an unsuccessful locking attempt, you can configure the mutex to work in the non-blocking mode, as explained later.
|
24
|
+
|
25
|
+
Install
|
26
|
+
-------
|
27
|
+
|
28
|
+
gem install redis-mutex
|
29
|
+
|
30
|
+
Usage
|
31
|
+
-----
|
32
|
+
|
33
|
+
In Gemfile:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
gem "redis-mutex"
|
37
|
+
```
|
38
|
+
|
39
|
+
Register the Redis server: (e.g. in `config/initializers/redis_mutex.rb` for Rails)
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
Redis::Classy.db = Redis.new(:host => 'localhost')
|
43
|
+
```
|
44
|
+
|
45
|
+
Note that Redis Mutex uses the `redis-classy` gem internally.
|
46
|
+
|
47
|
+
There are four methods - `new`, `lock`, `unlock` and `sweep`:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
mutex = Redis::Mutex.new(key, options)
|
51
|
+
mutex.lock
|
52
|
+
mutex.unlock
|
53
|
+
Redis::Mutex.sweep
|
54
|
+
```
|
55
|
+
|
56
|
+
For the key, it takes any Ruby objects that respond to :id, where the key is automatically set as "TheClass:id",
|
57
|
+
or pass any string or symbol.
|
58
|
+
|
59
|
+
Also the initialize method takes several options.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
:block => 1 # Specify in seconds how long you want to wait for the lock to be released.
|
63
|
+
# Speficy 0 if you need non-blocking sematics and return false immediately. (default: 1)
|
64
|
+
:sleep => 0.1 # Specify in seconds how long the polling interval should be when :block is given.
|
65
|
+
# It is recommended that you do NOT go below 0.01. (default: 0.1)
|
66
|
+
:expire => 10 # Specify in seconds when the lock should forcibly be removed when something went wrong
|
67
|
+
# with the one who held the lock. (default: 10)
|
68
|
+
```
|
69
|
+
|
70
|
+
The lock method returns `true` when the lock has been successfully obtained, or returns `false` when the attempts
|
71
|
+
failed after the seconds specified with **:block**. It immediately returns `false` when 0 is given to **:block**.
|
72
|
+
|
73
|
+
Here's a sample usage in a Rails app:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class RoomController < ApplicationController
|
77
|
+
def enter
|
78
|
+
@room = Room.find(params[:id])
|
79
|
+
|
80
|
+
mutex = Redis::Mutex.new(@room) # key => "Room:123"
|
81
|
+
mutex.lock do
|
82
|
+
# do something exclusively
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Note that you need to explicitly call the unlock method unless you don't use the block syntax.
|
89
|
+
|
90
|
+
In the following example,
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
def enter
|
94
|
+
mutex = Redis::Mutex.new('non-blocking', :block => 0, :expire => 10.minutes)
|
95
|
+
mutex.lock
|
96
|
+
# do something exclusively
|
97
|
+
mutex.unlock
|
98
|
+
rescue
|
99
|
+
mutex.unlock
|
100
|
+
raise
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
Also note that, internally, the actual key is structured in the following form:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
Redis.new.keys
|
108
|
+
=> ["Redis::Mutex:Room:111", "Redis::Mutex:Room:112", ... ]
|
109
|
+
```
|
110
|
+
|
111
|
+
The automatic prefixing and binding is the feature of `Redis::Classy`.
|
112
|
+
For more internal details, refer to [Redis Classy](https://github.com/kenn/redis-classy).
|
data/Rakefile
CHANGED
@@ -15,8 +15,8 @@ Jeweler::Tasks.new do |gem|
|
|
15
15
|
gem.name = "redis-mutex"
|
16
16
|
gem.homepage = "http://github.com/kenn/redis-mutex"
|
17
17
|
gem.license = "MIT"
|
18
|
-
gem.summary = "Distrubuted
|
19
|
-
gem.description = "Distrubuted
|
18
|
+
gem.summary = "Distrubuted mutex using Redis"
|
19
|
+
gem.description = "Distrubuted mutex using Redis"
|
20
20
|
gem.email = "kenn.ejima@gmail.com"
|
21
21
|
gem.authors = ["Kenn Ejima"]
|
22
22
|
end
|
@@ -31,5 +31,5 @@ end
|
|
31
31
|
|
32
32
|
task :default => :spec
|
33
33
|
task :spec do
|
34
|
-
exec "rspec spec
|
34
|
+
exec "rspec spec"
|
35
35
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0
|
data/lib/redis/mutex.rb
CHANGED
@@ -1,46 +1,51 @@
|
|
1
1
|
class Redis
|
2
2
|
#
|
3
|
-
#
|
3
|
+
# Options
|
4
4
|
#
|
5
5
|
# :block => Specify in seconds how long you want to wait for the lock to be released. Speficy 0
|
6
6
|
# if you need non-blocking sematics and return false immediately. (default: 1)
|
7
7
|
# :sleep => Specify in seconds how long the polling interval should be when :block is given.
|
8
8
|
# It is recommended that you do NOT go below 0.01. (default: 0.1)
|
9
9
|
# :expire => Specify in seconds when the lock should forcibly be removed when something went wrong
|
10
|
-
# with the one who held the lock. (
|
10
|
+
# with the one who held the lock. (default: 10)
|
11
11
|
#
|
12
12
|
class Mutex < Redis::Classy
|
13
|
-
|
13
|
+
autoload :Macro, 'redis/mutex/macro'
|
14
|
+
attr_reader :block, :sleep, :expire, :locking
|
14
15
|
DEFAULT_EXPIRE = 10
|
15
|
-
attr_accessor :options
|
16
16
|
|
17
17
|
def initialize(object, options={})
|
18
18
|
super(object.is_a?(String) || object.is_a?(Symbol) ? object : "#{object.class.name}:#{object.id}")
|
19
|
-
@
|
20
|
-
@options[:
|
21
|
-
@options[:
|
22
|
-
@options[:expire] ||= DEFAULT_EXPIRE
|
19
|
+
@block = options[:block] || 1
|
20
|
+
@sleep = options[:sleep] || 0.1
|
21
|
+
@expire = options[:expire] || DEFAULT_EXPIRE
|
23
22
|
end
|
24
23
|
|
25
24
|
def lock
|
26
|
-
|
25
|
+
@locking = false
|
26
|
+
|
27
|
+
if @block > 0
|
28
|
+
# Blocking mode
|
27
29
|
start_at = Time.now
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
sleep @options[:sleep]
|
30
|
+
while Time.now - start_at < @block
|
31
|
+
@locking = true and break if try_lock
|
32
|
+
Kernel.sleep @sleep
|
32
33
|
end
|
33
34
|
else
|
34
|
-
# Non-blocking
|
35
|
-
|
35
|
+
# Non-blocking mode
|
36
|
+
@locking = try_lock
|
36
37
|
end
|
38
|
+
success = @locking # Backup
|
37
39
|
|
38
|
-
if block_given? and
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
40
|
+
if block_given? and @locking
|
41
|
+
begin
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
# Since it's possible that the yielded operation took a long time, we can't just simply
|
45
|
+
# Release the lock. The unlock method checks if the expires_at remains the same that you
|
46
|
+
# set, and do not release it when the lock timestamp was overwritten.
|
47
|
+
unlock
|
48
|
+
end
|
44
49
|
end
|
45
50
|
|
46
51
|
success
|
@@ -48,7 +53,7 @@ class Redis
|
|
48
53
|
|
49
54
|
def try_lock
|
50
55
|
now = Time.now.to_f
|
51
|
-
@expires_at = now + @
|
56
|
+
@expires_at = now + @expire # Extend in each blocking loop
|
52
57
|
return true if setnx(@expires_at) # Success, the lock has been acquired
|
53
58
|
return false if get.to_f > now # Check if the lock is still effective
|
54
59
|
|
@@ -58,26 +63,33 @@ class Redis
|
|
58
63
|
end
|
59
64
|
|
60
65
|
def unlock(force=false)
|
66
|
+
@locking = false
|
61
67
|
del if get.to_f == @expires_at or force # Release the lock if it seems to be yours
|
62
68
|
end
|
63
69
|
|
64
|
-
|
65
|
-
|
70
|
+
class << self
|
71
|
+
def sweep
|
72
|
+
return 0 if (all_keys = keys).empty?
|
66
73
|
|
67
|
-
|
68
|
-
|
74
|
+
now = Time.now.to_f
|
75
|
+
values = mget(*all_keys)
|
69
76
|
|
70
|
-
|
71
|
-
|
72
|
-
|
77
|
+
expired_keys = [].tap do |array|
|
78
|
+
all_keys.each_with_index do |key, i|
|
79
|
+
array << key if !values[i].nil? and values[i].to_f <= now
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
expired_keys.each do |key|
|
84
|
+
del(key) if getset(key, now + DEFAULT_EXPIRE).to_f <= now # Make extra sure that anyone haven't extended the lock
|
73
85
|
end
|
74
|
-
end
|
75
86
|
|
76
|
-
|
77
|
-
del(key) if getset(key, now + DEFAULT_EXPIRE).to_f <= now # Make extra sure that anyone haven't extended the lock
|
87
|
+
expired_keys.size
|
78
88
|
end
|
79
89
|
|
80
|
-
|
90
|
+
def lock(object, options={}, &block)
|
91
|
+
new(object, options).lock(&block)
|
92
|
+
end
|
81
93
|
end
|
82
94
|
end
|
83
95
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Redis
|
2
|
+
class Mutex
|
3
|
+
module Macro
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
base.class_eval do
|
7
|
+
class << self
|
8
|
+
attr_accessor :auto_mutex_methods
|
9
|
+
end
|
10
|
+
@auto_mutex_methods = {}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def auto_mutex(target, options={})
|
16
|
+
self.auto_mutex_methods[target] = options
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_added(target)
|
20
|
+
return if target.to_s =~ /^auto_mutex_methods.*$/
|
21
|
+
return unless self.auto_mutex_methods[target]
|
22
|
+
without_method = "#{target}_without_auto_mutex"
|
23
|
+
with_method = "#{target}_with_auto_mutex"
|
24
|
+
return if method_defined?(without_method)
|
25
|
+
|
26
|
+
define_method(with_method) do |*args|
|
27
|
+
key = self.class.name << '#' << target.to_s
|
28
|
+
options = self.class.auto_mutex_methods[target]
|
29
|
+
|
30
|
+
Redis::Mutex.lock(key, options) do
|
31
|
+
send(without_method, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
alias_method without_method, target
|
36
|
+
alias_method target, with_method
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/redis-mutex.gemspec
CHANGED
@@ -4,17 +4,17 @@
|
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
-
s.version = "1.
|
7
|
+
s.name = "redis-mutex"
|
8
|
+
s.version = "1.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Kenn Ejima"]
|
12
|
-
s.date =
|
13
|
-
s.description =
|
14
|
-
s.email =
|
12
|
+
s.date = "2011-11-05"
|
13
|
+
s.description = "Distrubuted mutex using Redis"
|
14
|
+
s.email = "kenn.ejima@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE.txt",
|
17
|
-
"README.
|
17
|
+
"README.md"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
@@ -22,40 +22,41 @@ Gem::Specification.new do |s|
|
|
22
22
|
"Gemfile",
|
23
23
|
"Gemfile.lock",
|
24
24
|
"LICENSE.txt",
|
25
|
-
"README.
|
25
|
+
"README.md",
|
26
26
|
"Rakefile",
|
27
27
|
"VERSION",
|
28
28
|
"lib/redis-mutex.rb",
|
29
29
|
"lib/redis/mutex.rb",
|
30
|
+
"lib/redis/mutex/macro.rb",
|
30
31
|
"redis-mutex.gemspec",
|
31
32
|
"spec/redis_mutex_spec.rb",
|
32
33
|
"spec/spec_helper.rb"
|
33
34
|
]
|
34
|
-
s.homepage =
|
35
|
+
s.homepage = "http://github.com/kenn/redis-mutex"
|
35
36
|
s.licenses = ["MIT"]
|
36
37
|
s.require_paths = ["lib"]
|
37
|
-
s.rubygems_version =
|
38
|
-
s.summary =
|
38
|
+
s.rubygems_version = "1.8.11"
|
39
|
+
s.summary = "Distrubuted mutex using Redis"
|
39
40
|
|
40
41
|
if s.respond_to? :specification_version then
|
41
42
|
s.specification_version = 3
|
42
43
|
|
43
44
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
44
45
|
s.add_runtime_dependency(%q<redis-classy>, ["~> 1.0.0"])
|
45
|
-
s.add_development_dependency(%q<rspec>, ["
|
46
|
-
s.add_development_dependency(%q<bundler>, ["
|
47
|
-
s.add_development_dependency(%q<jeweler>, ["
|
46
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
47
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
48
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
48
49
|
else
|
49
50
|
s.add_dependency(%q<redis-classy>, ["~> 1.0.0"])
|
50
|
-
s.add_dependency(%q<rspec>, ["
|
51
|
-
s.add_dependency(%q<bundler>, ["
|
52
|
-
s.add_dependency(%q<jeweler>, ["
|
51
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
52
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
53
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
53
54
|
end
|
54
55
|
else
|
55
56
|
s.add_dependency(%q<redis-classy>, ["~> 1.0.0"])
|
56
|
-
s.add_dependency(%q<rspec>, ["
|
57
|
-
s.add_dependency(%q<bundler>, ["
|
58
|
-
s.add_dependency(%q<jeweler>, ["
|
57
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
58
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
59
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
data/spec/redis_mutex_spec.rb
CHANGED
@@ -3,6 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
3
3
|
describe Redis::Mutex do
|
4
4
|
before do
|
5
5
|
Redis::Classy.flushdb
|
6
|
+
@short_mutex_options = { :block => 0.1, :sleep => 0.02 }
|
6
7
|
end
|
7
8
|
|
8
9
|
after do
|
@@ -32,10 +33,10 @@ describe Redis::Mutex do
|
|
32
33
|
end
|
33
34
|
|
34
35
|
it "should not get a lock when existing lock is still effective" do
|
35
|
-
mutex = Redis::Mutex.new(:test_lock,
|
36
|
+
mutex = Redis::Mutex.new(:test_lock, @short_mutex_options)
|
36
37
|
|
37
38
|
# someone beats us to it
|
38
|
-
mutex2 = Redis::Mutex.new(:test_lock,
|
39
|
+
mutex2 = Redis::Mutex.new(:test_lock, @short_mutex_options)
|
39
40
|
mutex2.lock
|
40
41
|
|
41
42
|
mutex.lock.should be_false # should not have the lock
|
@@ -54,4 +55,47 @@ describe Redis::Mutex do
|
|
54
55
|
mutex.unlock
|
55
56
|
mutex.get.should_not be_nil # lock should still be there
|
56
57
|
end
|
58
|
+
|
59
|
+
it "should ensure unlock when something goes wrong in the block" do
|
60
|
+
mutex = Redis::Mutex.new(:test_lock)
|
61
|
+
begin
|
62
|
+
mutex.lock do
|
63
|
+
raise "Something went wrong!"
|
64
|
+
end
|
65
|
+
rescue
|
66
|
+
mutex.locking.should be_false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should reset locking state on reuse" do
|
71
|
+
mutex = Redis::Mutex.new(:test_lock, @short_mutex_options)
|
72
|
+
mutex.lock.should be_true
|
73
|
+
mutex.lock.should be_false
|
74
|
+
end
|
75
|
+
|
76
|
+
describe Redis::Mutex::Macro do
|
77
|
+
it "should add auto_mutex" do
|
78
|
+
|
79
|
+
class C
|
80
|
+
include Redis::Mutex::Macro
|
81
|
+
auto_mutex :run_singularly, :block => 0 # Give up immediately if lock is taken
|
82
|
+
@@result = 0
|
83
|
+
|
84
|
+
def run_singularly
|
85
|
+
sleep 0.1
|
86
|
+
Thread.exclusive { @@result += 1 }
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.result
|
90
|
+
@@result
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
t1 = Thread.new { C.new.run_singularly }
|
95
|
+
t2 = Thread.new { C.new.run_singularly }
|
96
|
+
t1.join
|
97
|
+
t2.join
|
98
|
+
C.result.should == 1
|
99
|
+
end
|
100
|
+
end
|
57
101
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -11,7 +11,7 @@ RSpec.configure do |config|
|
|
11
11
|
# Use database 15 for testing so we don't accidentally step on you real data.
|
12
12
|
Redis::Classy.db = Redis.new(:db => 15)
|
13
13
|
unless Redis::Classy.keys.empty?
|
14
|
-
puts '[ERROR]: Redis database 15 not empty! run "rake flushdb" beforehand.'
|
14
|
+
puts '[ERROR]: Redis database 15 not empty! If you are sure, run "rake flushdb" beforehand.'
|
15
15
|
exit!
|
16
16
|
end
|
17
17
|
end
|
metadata
CHANGED
@@ -1,115 +1,108 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-mutex
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
4
5
|
prerelease:
|
5
|
-
version: 1.1.0
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Kenn Ejima
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
dependencies:
|
16
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2011-11-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
17
15
|
name: redis-classy
|
18
|
-
requirement: &
|
16
|
+
requirement: &2153901140 !ruby/object:Gem::Requirement
|
19
17
|
none: false
|
20
|
-
requirements:
|
18
|
+
requirements:
|
21
19
|
- - ~>
|
22
|
-
- !ruby/object:Gem::Version
|
20
|
+
- !ruby/object:Gem::Version
|
23
21
|
version: 1.0.0
|
24
22
|
type: :runtime
|
25
23
|
prerelease: false
|
26
|
-
version_requirements: *
|
27
|
-
- !ruby/object:Gem::Dependency
|
24
|
+
version_requirements: *2153901140
|
25
|
+
- !ruby/object:Gem::Dependency
|
28
26
|
name: rspec
|
29
|
-
requirement: &
|
27
|
+
requirement: &2153900520 !ruby/object:Gem::Requirement
|
30
28
|
none: false
|
31
|
-
requirements:
|
32
|
-
- -
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version:
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
35
33
|
type: :development
|
36
34
|
prerelease: false
|
37
|
-
version_requirements: *
|
38
|
-
- !ruby/object:Gem::Dependency
|
35
|
+
version_requirements: *2153900520
|
36
|
+
- !ruby/object:Gem::Dependency
|
39
37
|
name: bundler
|
40
|
-
requirement: &
|
38
|
+
requirement: &2153899800 !ruby/object:Gem::Requirement
|
41
39
|
none: false
|
42
|
-
requirements:
|
43
|
-
- -
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
version:
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
46
44
|
type: :development
|
47
45
|
prerelease: false
|
48
|
-
version_requirements: *
|
49
|
-
- !ruby/object:Gem::Dependency
|
46
|
+
version_requirements: *2153899800
|
47
|
+
- !ruby/object:Gem::Dependency
|
50
48
|
name: jeweler
|
51
|
-
requirement: &
|
49
|
+
requirement: &2153899020 !ruby/object:Gem::Requirement
|
52
50
|
none: false
|
53
|
-
requirements:
|
54
|
-
- -
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version:
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
57
55
|
type: :development
|
58
56
|
prerelease: false
|
59
|
-
version_requirements: *
|
60
|
-
description: Distrubuted
|
57
|
+
version_requirements: *2153899020
|
58
|
+
description: Distrubuted mutex using Redis
|
61
59
|
email: kenn.ejima@gmail.com
|
62
60
|
executables: []
|
63
|
-
|
64
61
|
extensions: []
|
65
|
-
|
66
|
-
extra_rdoc_files:
|
62
|
+
extra_rdoc_files:
|
67
63
|
- LICENSE.txt
|
68
|
-
- README.
|
69
|
-
files:
|
64
|
+
- README.md
|
65
|
+
files:
|
70
66
|
- .document
|
71
67
|
- .rspec
|
72
68
|
- Gemfile
|
73
69
|
- Gemfile.lock
|
74
70
|
- LICENSE.txt
|
75
|
-
- README.
|
71
|
+
- README.md
|
76
72
|
- Rakefile
|
77
73
|
- VERSION
|
78
74
|
- lib/redis-mutex.rb
|
79
75
|
- lib/redis/mutex.rb
|
76
|
+
- lib/redis/mutex/macro.rb
|
80
77
|
- redis-mutex.gemspec
|
81
78
|
- spec/redis_mutex_spec.rb
|
82
79
|
- spec/spec_helper.rb
|
83
|
-
has_rdoc: true
|
84
80
|
homepage: http://github.com/kenn/redis-mutex
|
85
|
-
licenses:
|
81
|
+
licenses:
|
86
82
|
- MIT
|
87
83
|
post_install_message:
|
88
84
|
rdoc_options: []
|
89
|
-
|
90
|
-
require_paths:
|
85
|
+
require_paths:
|
91
86
|
- lib
|
92
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
88
|
none: false
|
94
|
-
requirements:
|
95
|
-
- -
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
|
98
|
-
segments:
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
segments:
|
99
94
|
- 0
|
100
|
-
|
101
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
hash: 3163934937932082133
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
97
|
none: false
|
103
|
-
requirements:
|
104
|
-
- -
|
105
|
-
- !ruby/object:Gem::Version
|
106
|
-
version:
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
107
102
|
requirements: []
|
108
|
-
|
109
103
|
rubyforge_project:
|
110
|
-
rubygems_version: 1.
|
104
|
+
rubygems_version: 1.8.11
|
111
105
|
signing_key:
|
112
106
|
specification_version: 3
|
113
|
-
summary: Distrubuted
|
107
|
+
summary: Distrubuted mutex using Redis
|
114
108
|
test_files: []
|
115
|
-
|
data/README.rdoc
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
= Redis Mutex
|
2
|
-
|
3
|
-
Distrubuted mutex in Ruby using Redis. Supports both blocking and non-blocking semantics.
|
4
|
-
|
5
|
-
The idea was taken from http://redis.io/commands/setnx and http://gist.github.com/457269,
|
6
|
-
also block syntax was inspired by https://github.com/nateware/redis-objects.
|
7
|
-
|
8
|
-
Requires the redis-classy gem.
|
9
|
-
|
10
|
-
== Synopsis
|
11
|
-
|
12
|
-
Initialize a Redis::Mutex object with a key, then only one thread / process / server can
|
13
|
-
enter the locked block at a time.
|
14
|
-
|
15
|
-
mutex = Redis::Mutex.new(:lock_name)
|
16
|
-
mutex.lock do
|
17
|
-
do_something
|
18
|
-
end
|
19
|
-
|
20
|
-
By default, when one is running the locked block, others wait 1 second, polling interval is 100ms.
|
21
|
-
If 1 second has passed, the lock method returns false.
|
22
|
-
|
23
|
-
== Install
|
24
|
-
|
25
|
-
gem install redis-mutex
|
26
|
-
|
27
|
-
== Usage
|
28
|
-
|
29
|
-
In Gemfile:
|
30
|
-
|
31
|
-
gem "redis-mutex"
|
32
|
-
|
33
|
-
Create a reference to Redis. (e.g. in config/initializers/redis_mutex.rb for Rails)
|
34
|
-
|
35
|
-
Redis::Classy.db = Redis.new(:host => 'localhost')
|
36
|
-
|
37
|
-
There are four methods - new, lock, unlock and sweep:
|
38
|
-
|
39
|
-
mutex = Redis::Mutex.new(key, options)
|
40
|
-
mutex.lock
|
41
|
-
mutex.unlock
|
42
|
-
Redis::Mutex.sweep
|
43
|
-
|
44
|
-
For the key, it takes any Ruby objects that respond to :id, where the key is automatically set as "TheClass:id",
|
45
|
-
or pass any string or symbol.
|
46
|
-
|
47
|
-
Also the initialize method takes several options.
|
48
|
-
|
49
|
-
:block => Specify in seconds how long you want to wait for the lock to be released. Speficy 0
|
50
|
-
if you need non-blocking sematics and return false immediately. (default: 1)
|
51
|
-
:sleep => Specify in seconds how long the polling interval should be when :block is given.
|
52
|
-
It is recommended that you do NOT go below 0.01. (default: 0.1)
|
53
|
-
:expire => Specify in seconds when the lock should forcibly be removed when something went wrong
|
54
|
-
with the one who held the lock. (in seconds, default: 10)
|
55
|
-
|
56
|
-
The lock method returns true when the lock has been successfully obtained, or returns false when the attempts
|
57
|
-
failed after the seconds specified with :block. It immediately returns false when 0 is given to :block.
|
58
|
-
|
59
|
-
Here's a sample usage in a Rails app:
|
60
|
-
|
61
|
-
class RoomController < ApplicationController
|
62
|
-
def enter
|
63
|
-
@room = Room.find_by_id(params[:id])
|
64
|
-
|
65
|
-
mutex = Redis::Mutex.new(@room) # key => "Room:123"
|
66
|
-
mutex.lock do
|
67
|
-
do_something
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
Note that you need to explicitly call the unlock method unless you don't use the block syntax.
|
73
|
-
|
74
|
-
Also note that, if you take a closer look, you find that the actual key is structured in the following form:
|
75
|
-
|
76
|
-
Redis.new.keys
|
77
|
-
=> ["Redis::Mutex:Room:123"]
|
78
|
-
|
79
|
-
The automatic prefixing and binding is the feature of Redis::Classy.
|
80
|
-
For more internal details, refer to https://github.com/kenn/redis-classy
|