redis-mutex 1.1.0 → 1.2.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/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
|