redis-mutex 1.3.5 → 2.0.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/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:
|