redis-mutex 1.3.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +36 -23
- data/lib/redis/mutex.rb +61 -32
- data/lib/redis/mutex/macro.rb +6 -9
- data/redis-mutex.gemspec +2 -2
- data/spec/redis_mutex_spec.rb +101 -42
- metadata +11 -6
data/README.md
CHANGED
@@ -13,7 +13,7 @@ Synopsis
|
|
13
13
|
In the following example, only one thread / process / server can enter the locked block at one time.
|
14
14
|
|
15
15
|
```ruby
|
16
|
-
Redis::Mutex.
|
16
|
+
Redis::Mutex.with_lock(:your_lock_name) do
|
17
17
|
# do something exclusively
|
18
18
|
end
|
19
19
|
```
|
@@ -26,7 +26,7 @@ if mutex.lock
|
|
26
26
|
# do something exclusively
|
27
27
|
mutex.unlock
|
28
28
|
else
|
29
|
-
puts "failed to
|
29
|
+
puts "failed to acquire lock!"
|
30
30
|
end
|
31
31
|
```
|
32
32
|
|
@@ -37,6 +37,13 @@ that you can configure any of these timing values, as explained later.
|
|
37
37
|
|
38
38
|
Or if you want to immediately receive `false` on an unsuccessful locking attempt, you can change the mutex mode to **non-blocking**.
|
39
39
|
|
40
|
+
Changes in v2.0
|
41
|
+
---------------
|
42
|
+
|
43
|
+
* **Exception-based control flow**: Added `lock!` and `unlock!`, which raises an exception when fails to acquire a lock. Raises `Redis::Mutex::LockError` and `Redis::Mutex::UnlockError` respectively.
|
44
|
+
* **INCOMPATIBLE CHANGE**: `#lock` no longer accepts a block. Use `#with_lock` instead, which uses `lock!` internally and returns the value of block.
|
45
|
+
* `unlock` returns boolean values for success / failure, for consistency with `lock`.
|
46
|
+
|
40
47
|
Install
|
41
48
|
-------
|
42
49
|
|
@@ -48,7 +55,7 @@ Usage
|
|
48
55
|
In Gemfile:
|
49
56
|
|
50
57
|
```ruby
|
51
|
-
gem
|
58
|
+
gem 'redis-mutex'
|
52
59
|
```
|
53
60
|
|
54
61
|
Register the Redis server: (e.g. in `config/initializers/redis_mutex.rb` for Rails)
|
@@ -59,15 +66,19 @@ Redis::Classy.db = Redis.new(:host => 'localhost')
|
|
59
66
|
|
60
67
|
Note that Redis Mutex uses the `redis-classy` gem internally to organize keys in an isolated namespace.
|
61
68
|
|
62
|
-
There are
|
69
|
+
There are a number of methods:
|
63
70
|
|
64
71
|
```ruby
|
65
72
|
mutex = Redis::Mutex.new(key, options) # Configure a mutex lock
|
66
|
-
mutex.lock # Try to
|
67
|
-
mutex.unlock #
|
68
|
-
|
69
|
-
|
70
|
-
|
73
|
+
mutex.lock # Try to acquire the lock
|
74
|
+
mutex.unlock # Try to release the lock
|
75
|
+
mutex.lock! # Try to acquire the lock, raises exception when failed
|
76
|
+
mutex.unlock! # Try to release the lock, raises exception when failed
|
77
|
+
mutex.with_lock # Try to acquire the lock, execute the block, then return the value of the block.
|
78
|
+
# Raises exception when failed to acquire the lock.
|
79
|
+
|
80
|
+
Redis::Mutex.sweep # Remove all expired locks
|
81
|
+
Redis::Mutex.with_lock(key, options) # Shortcut to new + with_lock
|
71
82
|
```
|
72
83
|
|
73
84
|
The key argument can be symbol, string, or any Ruby objects that respond to `id` method, where the key is automatically set as
|
@@ -81,12 +92,12 @@ The initialize method takes several options.
|
|
81
92
|
:block => 1 # Specify in seconds how long you want to wait for the lock to be released.
|
82
93
|
# Speficy 0 if you need non-blocking sematics and return false immediately. (default: 1)
|
83
94
|
:sleep => 0.1 # Specify in seconds how long the polling interval should be when :block is given.
|
84
|
-
# It is recommended
|
85
|
-
:expire => 10 # Specify in seconds when the lock should
|
86
|
-
# with the one who held the lock. (default: 10)
|
95
|
+
# It is NOT recommended to go below 0.01. (default: 0.1)
|
96
|
+
:expire => 10 # Specify in seconds when the lock should be considered stale when something went wrong
|
97
|
+
# with the one who held the lock and failed to unlock. (default: 10)
|
87
98
|
```
|
88
99
|
|
89
|
-
The lock method returns `true` when the lock has been successfully
|
100
|
+
The lock method returns `true` when the lock has been successfully acquired, or returns `false` when the attempts failed after
|
90
101
|
the seconds specified with **:block**. When 0 is given to **:block**, it is set to **non-blocking** mode and immediately returns `false`.
|
91
102
|
|
92
103
|
In the following Rails example, only one request can enter to a given room.
|
@@ -96,29 +107,31 @@ class RoomController < ApplicationController
|
|
96
107
|
before_filter { @room = Room.find(params[:id]) }
|
97
108
|
|
98
109
|
def enter
|
99
|
-
|
110
|
+
Redis::Mutex.with_lock(@room) do # key => "Room:123"
|
100
111
|
# do something exclusively
|
101
112
|
end
|
102
|
-
render :
|
113
|
+
render text: 'success!'
|
114
|
+
rescue Redis::Mutex::LockError
|
115
|
+
render text: 'failed to acquire lock!'
|
103
116
|
end
|
104
117
|
end
|
105
118
|
```
|
106
119
|
|
107
|
-
Note that you need to explicitly call the unlock method when you don't use
|
108
|
-
put the `unlock` method in the `ensure` clause
|
120
|
+
Note that you need to explicitly call the `unlock` method when you don't use `with_lock` and its block syntax. Also it is recommended to
|
121
|
+
put the `unlock` method in the `ensure` clause.
|
109
122
|
|
110
123
|
```ruby
|
111
124
|
def enter
|
112
|
-
mutex = Redis::Mutex.new('non-blocking', :
|
125
|
+
mutex = Redis::Mutex.new('non-blocking', block: 0, expire: 10.minutes)
|
113
126
|
if mutex.lock
|
114
127
|
begin
|
115
128
|
# do something exclusively
|
116
129
|
ensure
|
117
130
|
mutex.unlock
|
118
131
|
end
|
119
|
-
render :
|
132
|
+
render text: 'success!'
|
120
133
|
else
|
121
|
-
render :
|
134
|
+
render text: 'failed to acquire lock!'
|
122
135
|
end
|
123
136
|
end
|
124
137
|
```
|
@@ -134,11 +147,11 @@ If you give a proc object to the `after_failure` option, it will get called afte
|
|
134
147
|
```ruby
|
135
148
|
class JobController < ApplicationController
|
136
149
|
include Redis::Mutex::Macro
|
137
|
-
auto_mutex :run, :
|
138
|
-
|
150
|
+
auto_mutex :run, block: 0, after_failure: lambda { render text: 'failed to acquire lock!' }
|
151
|
+
|
139
152
|
def run
|
140
153
|
# do something exclusively
|
141
|
-
render :
|
154
|
+
render text: 'success!'
|
142
155
|
end
|
143
156
|
end
|
144
157
|
```
|
data/lib/redis/mutex.rb
CHANGED
@@ -12,8 +12,10 @@ class Redis
|
|
12
12
|
class Mutex < Redis::Classy
|
13
13
|
autoload :Macro, 'redis/mutex/macro'
|
14
14
|
|
15
|
-
attr_reader :locking
|
16
15
|
DEFAULT_EXPIRE = 10
|
16
|
+
LockError = Class.new(StandardError)
|
17
|
+
UnlockError = Class.new(StandardError)
|
18
|
+
AssertionError = Class.new(StandardError)
|
17
19
|
|
18
20
|
def initialize(object, options={})
|
19
21
|
super(object.is_a?(String) || object.is_a?(Symbol) ? object : "#{object.class.name}:#{object.id}")
|
@@ -23,6 +25,7 @@ class Redis
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def lock
|
28
|
+
self.class.raise_assertion_error if block_given?
|
26
29
|
@locking = false
|
27
30
|
|
28
31
|
if @block > 0
|
@@ -30,66 +33,92 @@ class Redis
|
|
30
33
|
start_at = Time.now
|
31
34
|
while Time.now - start_at < @block
|
32
35
|
@locking = true and break if try_lock
|
33
|
-
|
36
|
+
sleep @sleep
|
34
37
|
end
|
35
38
|
else
|
36
39
|
# Non-blocking mode
|
37
40
|
@locking = try_lock
|
38
41
|
end
|
39
|
-
|
42
|
+
@locking
|
43
|
+
end
|
44
|
+
|
45
|
+
def try_lock
|
46
|
+
now = Time.now.to_f
|
47
|
+
@expires_at = now + @expire # Extend in each blocking loop
|
48
|
+
return true if setnx(@expires_at) # Success, the lock has been acquired
|
49
|
+
return false if get.to_f > now # Check if the lock is still effective
|
50
|
+
|
51
|
+
# The lock has expired but wasn't released... BAD!
|
52
|
+
return true if getset(@expires_at).to_f <= now # Success, we acquired the previously expired lock
|
53
|
+
return false # Dammit, it seems that someone else was even faster than us to remove the expired lock!
|
54
|
+
end
|
55
|
+
|
56
|
+
def unlock(force = false)
|
57
|
+
# Since it's possible that the operations in the critical section took a long time,
|
58
|
+
# we can't just simply release the lock. The unlock method checks if @expires_at
|
59
|
+
# remains the same, and do not release when the lock timestamp was overwritten.
|
60
|
+
|
61
|
+
if get == @expires_at.to_s or force
|
62
|
+
# Redis#del with a single key returns '1' or nil
|
63
|
+
!!del
|
64
|
+
else
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
40
68
|
|
41
|
-
|
69
|
+
def with_lock
|
70
|
+
if lock!
|
42
71
|
begin
|
43
|
-
yield
|
72
|
+
@result = yield
|
44
73
|
ensure
|
45
|
-
# Since it's possible that the yielded operation took a long time, we can't just simply
|
46
|
-
# Release the lock. The unlock method checks if the expires_at remains the same that you
|
47
|
-
# set, and do not release it when the lock timestamp was overwritten.
|
48
74
|
unlock
|
49
75
|
end
|
50
76
|
end
|
51
|
-
|
52
|
-
success
|
77
|
+
@result
|
53
78
|
end
|
54
79
|
|
55
|
-
def
|
56
|
-
|
57
|
-
@expires_at = now + @expire # Extend in each blocking loop
|
58
|
-
return true if self.setnx(@expires_at) # Success, the lock has been acquired
|
59
|
-
return false if self.get.to_f > now # Check if the lock is still effective
|
60
|
-
|
61
|
-
# The lock has expired but wasn't released... BAD!
|
62
|
-
return true if self.getset(@expires_at).to_f <= now # Success, we acquired the previously expired lock
|
63
|
-
return false # Dammit, it seems that someone else was even faster than us to remove the expired lock!
|
80
|
+
def lock!
|
81
|
+
lock or raise LockError, "failed to acquire lock #{key.inspect}"
|
64
82
|
end
|
65
83
|
|
66
|
-
def unlock(force=false)
|
67
|
-
|
68
|
-
self.del if self.get == @expires_at.to_s or force # Release the lock if it seems to be yours
|
84
|
+
def unlock!(force = false)
|
85
|
+
unlock(force) or raise UnlockError, "failed to release lock #{key.inspect}"
|
69
86
|
end
|
70
87
|
|
71
88
|
class << self
|
72
89
|
def sweep
|
73
|
-
return 0 if (all_keys =
|
90
|
+
return 0 if (all_keys = keys).empty?
|
74
91
|
|
75
92
|
now = Time.now.to_f
|
76
|
-
values =
|
93
|
+
values = mget(*all_keys)
|
77
94
|
|
78
|
-
expired_keys =
|
79
|
-
|
80
|
-
array << key if !values[i].nil? and values[i].to_f <= now
|
81
|
-
end
|
95
|
+
expired_keys = all_keys.zip(values).select do |key, time|
|
96
|
+
time && time.to_f <= now
|
82
97
|
end
|
83
98
|
|
84
|
-
expired_keys.each do |key|
|
85
|
-
|
99
|
+
expired_keys.each do |key, _|
|
100
|
+
# Make extra sure that anyone haven't extended the lock
|
101
|
+
del(key) if getset(key, now + DEFAULT_EXPIRE).to_f <= now
|
86
102
|
end
|
87
103
|
|
88
104
|
expired_keys.size
|
89
105
|
end
|
90
106
|
|
91
|
-
def lock(object, options={}
|
92
|
-
|
107
|
+
def lock(object, options = {})
|
108
|
+
raise_assertion_error if block_given?
|
109
|
+
new(object, options).lock
|
110
|
+
end
|
111
|
+
|
112
|
+
def lock!(object, options = {})
|
113
|
+
new(object, options).lock!
|
114
|
+
end
|
115
|
+
|
116
|
+
def with_lock(object, options = {}, &block)
|
117
|
+
new(object, options).with_lock(&block)
|
118
|
+
end
|
119
|
+
|
120
|
+
def raise_assertion_error
|
121
|
+
raise AssertionError, 'block syntax has been removed from #lock, use #with_lock instead'
|
93
122
|
end
|
94
123
|
end
|
95
124
|
end
|
data/lib/redis/mutex/macro.rb
CHANGED
@@ -31,17 +31,14 @@ class Redis
|
|
31
31
|
|
32
32
|
define_method(with_method) do |*args|
|
33
33
|
key = self.class.name << '#' << target.to_s
|
34
|
-
response = nil
|
35
34
|
|
36
|
-
|
37
|
-
|
35
|
+
begin
|
36
|
+
Redis::Mutex.with_lock(key, options) do
|
37
|
+
send(without_method, *args)
|
38
|
+
end
|
39
|
+
rescue Redis::Mutex::LockError
|
40
|
+
send(after_method, *args) if respond_to?(after_method)
|
38
41
|
end
|
39
|
-
|
40
|
-
if !success and respond_to?(after_method)
|
41
|
-
response = send(after_method, *args)
|
42
|
-
end
|
43
|
-
|
44
|
-
response
|
45
42
|
end
|
46
43
|
|
47
44
|
alias_method without_method, target
|
data/redis-mutex.gemspec
CHANGED
@@ -12,9 +12,9 @@ Gem::Specification.new do |gem|
|
|
12
12
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
13
13
|
gem.name = "redis-mutex"
|
14
14
|
gem.require_paths = ["lib"]
|
15
|
-
gem.version = '
|
15
|
+
gem.version = '2.0.0' # retrieve this value by: Gem.loaded_specs['redis-mutex'].version.to_s
|
16
16
|
|
17
|
-
gem.add_runtime_dependency "redis-classy", "~> 1.
|
17
|
+
gem.add_runtime_dependency "redis-classy", "~> 1.2"
|
18
18
|
gem.add_development_dependency "rspec"
|
19
19
|
gem.add_development_dependency "bundler"
|
20
20
|
|
data/spec/redis_mutex_spec.rb
CHANGED
@@ -1,49 +1,79 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
SHORT_MUTEX_OPTIONS = { :block => 0.1, :sleep => 0.02 }
|
4
|
+
|
5
|
+
class C
|
6
|
+
include Redis::Mutex::Macro
|
7
|
+
auto_mutex :run_singularly, :block => 0, :after_failure => lambda {|id| return "failure: #{id}" }
|
8
|
+
|
9
|
+
def run_singularly(id)
|
10
|
+
sleep 0.1
|
11
|
+
return "success: #{id}"
|
12
|
+
end
|
13
|
+
end
|
2
14
|
|
3
15
|
describe Redis::Mutex do
|
4
16
|
before do
|
5
17
|
Redis::Classy.flushdb
|
6
|
-
@short_mutex_options = { :block => 0.1, :sleep => 0.02 }
|
7
18
|
end
|
8
19
|
|
9
|
-
after do
|
20
|
+
after :all do
|
10
21
|
Redis::Classy.flushdb
|
22
|
+
Redis::Classy.quit
|
11
23
|
end
|
12
24
|
|
13
|
-
it
|
25
|
+
it 'locks the universe' do
|
26
|
+
mutex1 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
|
27
|
+
mutex1.lock.should be_true
|
28
|
+
|
29
|
+
mutex2 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
|
30
|
+
mutex2.lock.should be_false
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'fails to lock when the lock is taken' do
|
34
|
+
mutex1 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
|
35
|
+
|
36
|
+
mutex2 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
|
37
|
+
mutex2.lock.should be_true # mutex2 beats us to it
|
38
|
+
|
39
|
+
mutex1.lock.should be_false # fail
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'unlocks only once' do
|
43
|
+
mutex = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
|
44
|
+
mutex.lock.should be_true
|
45
|
+
|
46
|
+
mutex.unlock.should be_true # successfully released the lock
|
47
|
+
mutex.unlock.should be_false # the lock no longer exists
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'prevents accidental unlock from outside' do
|
51
|
+
mutex1 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
|
52
|
+
mutex1.lock.should be_true
|
53
|
+
|
54
|
+
mutex2 = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
|
55
|
+
mutex2.unlock.should be_false
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'sets expiration' do
|
14
59
|
start = Time.now
|
15
60
|
expires_in = 10
|
16
61
|
mutex = Redis::Mutex.new(:test_lock, :expire => expires_in)
|
17
|
-
mutex.
|
62
|
+
mutex.with_lock do
|
18
63
|
mutex.get.to_f.should be_within(1.0).of((start + expires_in).to_f)
|
19
64
|
end
|
20
|
-
# key should have been cleaned up
|
21
|
-
mutex.get.should be_nil
|
65
|
+
mutex.get.should be_nil # key should have been cleaned up
|
22
66
|
end
|
23
67
|
|
24
|
-
it
|
25
|
-
|
26
|
-
# locked in the far past
|
68
|
+
it 'overwrites a lock when existing lock is expired' do
|
69
|
+
# stale lock from the far past
|
27
70
|
Redis::Mutex.set(:test_lock, Time.now - 60)
|
28
71
|
|
72
|
+
mutex = Redis::Mutex.new(:test_lock)
|
29
73
|
mutex.lock.should be_true
|
30
|
-
mutex.get.should_not be_nil
|
31
|
-
mutex.unlock
|
32
|
-
mutex.get.should be_nil
|
33
|
-
end
|
34
|
-
|
35
|
-
it "should not get a lock when existing lock is still effective" do
|
36
|
-
mutex = Redis::Mutex.new(:test_lock, @short_mutex_options)
|
37
|
-
|
38
|
-
# someone beats us to it
|
39
|
-
mutex2 = Redis::Mutex.new(:test_lock, @short_mutex_options)
|
40
|
-
mutex2.lock
|
41
|
-
|
42
|
-
mutex.lock.should be_false # should not have the lock
|
43
|
-
mutex.get.should_not be_nil # lock value should still be set
|
44
74
|
end
|
45
75
|
|
46
|
-
it
|
76
|
+
it 'fails to unlock the key if it took too long past expiration' do
|
47
77
|
mutex = Redis::Mutex.new(:test_lock, :expire => 0.1, :block => 0)
|
48
78
|
mutex.lock.should be_true
|
49
79
|
sleep 0.2 # lock expired
|
@@ -56,38 +86,67 @@ describe Redis::Mutex do
|
|
56
86
|
mutex.get.should_not be_nil # lock should still be there
|
57
87
|
end
|
58
88
|
|
59
|
-
it
|
89
|
+
it 'ensures unlocking when something goes wrong in the block' do
|
60
90
|
mutex = Redis::Mutex.new(:test_lock)
|
61
91
|
begin
|
62
|
-
mutex.
|
92
|
+
mutex.with_lock do
|
63
93
|
raise "Something went wrong!"
|
64
94
|
end
|
65
|
-
rescue
|
66
|
-
mutex.
|
95
|
+
rescue RuntimeError
|
96
|
+
mutex.get.should be_nil
|
67
97
|
end
|
68
98
|
end
|
69
99
|
|
70
|
-
it
|
71
|
-
mutex = Redis::Mutex.new(:test_lock,
|
100
|
+
it 'resets locking state on reuse' do
|
101
|
+
mutex = Redis::Mutex.new(:test_lock, SHORT_MUTEX_OPTIONS)
|
72
102
|
mutex.lock.should be_true
|
73
103
|
mutex.lock.should be_false
|
74
104
|
end
|
75
105
|
|
76
|
-
|
77
|
-
|
106
|
+
it 'returns value of block' do
|
107
|
+
Redis::Mutex.with_lock(:test_lock) { :test_result }.should == :test_result
|
108
|
+
end
|
78
109
|
|
79
|
-
|
80
|
-
|
81
|
-
|
110
|
+
it 'requires block for #with_lock' do
|
111
|
+
expect { Redis::Mutex.with_lock(:test_lock) }.to raise_error(LocalJumpError) #=> no block given (yield)
|
112
|
+
end
|
82
113
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
114
|
+
it 'raises LockError if lock not obtained' do
|
115
|
+
expect { Redis::Mutex.lock!(:test_lock, SHORT_MUTEX_OPTIONS) }.to_not raise_error
|
116
|
+
expect { Redis::Mutex.lock!(:test_lock, SHORT_MUTEX_OPTIONS) }.to raise_error(Redis::Mutex::LockError)
|
117
|
+
end
|
88
118
|
|
119
|
+
it 'raises UnlockError if lock not obtained' do
|
120
|
+
mutex = Redis::Mutex.new(:test_lock)
|
121
|
+
mutex.lock.should be_true
|
122
|
+
mutex.unlock.should be_true
|
123
|
+
expect { mutex.unlock! }.to raise_error(Redis::Mutex::UnlockError)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'raises AssertionError when block is given to #lock' do
|
127
|
+
# instance method
|
128
|
+
mutex = Redis::Mutex.new(:test_lock)
|
129
|
+
expect { mutex.lock {} }.to raise_error(Redis::Mutex::AssertionError)
|
130
|
+
|
131
|
+
# class method
|
132
|
+
expect { Redis::Mutex.lock(:test_lock) {} }.to raise_error(Redis::Mutex::AssertionError)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'sweeps expired locks' do
|
136
|
+
Redis::Mutex.set(:past, Time.now.to_f - 60)
|
137
|
+
Redis::Mutex.set(:present, Time.now.to_f)
|
138
|
+
Redis::Mutex.set(:future, Time.now.to_f + 60)
|
139
|
+
Redis::Mutex.keys.size.should == 3
|
140
|
+
Redis::Mutex.sweep.should == 2
|
141
|
+
Redis::Mutex.keys.size.should == 1
|
142
|
+
end
|
143
|
+
|
144
|
+
describe Redis::Mutex::Macro do
|
145
|
+
it 'adds auto_mutex' do
|
89
146
|
t1 = Thread.new { C.new.run_singularly(1).should == "success: 1" }
|
90
|
-
|
147
|
+
# In most cases t1 wins, but make sure to give it a head start,
|
148
|
+
# not exceeding the sleep inside the method.
|
149
|
+
sleep 0.01
|
91
150
|
t2 = Thread.new { C.new.run_singularly(2).should == "failure: 2" }
|
92
151
|
t1.join
|
93
152
|
t2.join
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-mutex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-10-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis-classy
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '1.
|
21
|
+
version: '1.2'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '1.
|
29
|
+
version: '1.2'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: rspec
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -107,19 +107,24 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
107
107
|
- - ! '>='
|
108
108
|
- !ruby/object:Gem::Version
|
109
109
|
version: '0'
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
hash: -331406254001747474
|
110
113
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
114
|
none: false
|
112
115
|
requirements:
|
113
116
|
- - ! '>='
|
114
117
|
- !ruby/object:Gem::Version
|
115
118
|
version: '0'
|
119
|
+
segments:
|
120
|
+
- 0
|
121
|
+
hash: -331406254001747474
|
116
122
|
requirements: []
|
117
123
|
rubyforge_project:
|
118
|
-
rubygems_version: 1.8.
|
124
|
+
rubygems_version: 1.8.24
|
119
125
|
signing_key:
|
120
126
|
specification_version: 3
|
121
127
|
summary: Distrubuted mutex using Redis
|
122
128
|
test_files:
|
123
129
|
- spec/redis_mutex_spec.rb
|
124
130
|
- spec/spec_helper.rb
|
125
|
-
has_rdoc:
|