redis-mutex 1.2.2 → 1.2.3
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.lock +3 -3
- data/README.md +64 -34
- data/VERSION +1 -1
- data/lib/redis/mutex.rb +9 -9
- data/redis-mutex.gemspec +2 -2
- metadata +11 -11
data/Gemfile.lock
CHANGED
|
@@ -9,9 +9,9 @@ GEM
|
|
|
9
9
|
rake
|
|
10
10
|
rake (0.9.2.2)
|
|
11
11
|
redis (2.2.2)
|
|
12
|
-
redis-classy (1.0.
|
|
13
|
-
redis-namespace (~> 1.0
|
|
14
|
-
redis-namespace (1.0
|
|
12
|
+
redis-classy (1.0.1)
|
|
13
|
+
redis-namespace (~> 1.0)
|
|
14
|
+
redis-namespace (1.1.0)
|
|
15
15
|
redis (< 3.0.0)
|
|
16
16
|
rspec (2.7.0)
|
|
17
17
|
rspec-core (~> 2.7.0)
|
data/README.md
CHANGED
|
@@ -10,17 +10,30 @@ Synopsis
|
|
|
10
10
|
|
|
11
11
|
In the following example, only one thread / process / server can enter the locked block at one time.
|
|
12
12
|
|
|
13
|
+
```ruby
|
|
14
|
+
Redis::Mutex.lock(:your_lock_name)
|
|
15
|
+
# do something exclusively
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
or
|
|
20
|
+
|
|
13
21
|
```ruby
|
|
14
22
|
mutex = Redis::Mutex.new(:your_lock_name)
|
|
15
|
-
mutex.lock
|
|
23
|
+
if mutex.lock
|
|
16
24
|
# do something exclusively
|
|
25
|
+
mutex.unlock
|
|
26
|
+
else
|
|
27
|
+
puts "failed to obtain lock!"
|
|
17
28
|
end
|
|
18
29
|
```
|
|
19
30
|
|
|
20
|
-
By default,
|
|
21
|
-
When 1 second has passed, the lock method returns `false
|
|
31
|
+
By default, while one is holding a lock, others wait **1 second** in total, polling **every 100ms** to see if the lock was released.
|
|
32
|
+
When 1 second has passed, the lock method returns `false` and others give up. Note that if your job runs longer than **10 seconds**,
|
|
33
|
+
the lock will be automatically removed to avoid a deadlock situation in case your job is dead before releasing the lock. Also note
|
|
34
|
+
that you can configure any of these timing values, as explained later.
|
|
22
35
|
|
|
23
|
-
|
|
36
|
+
Or if you want to immediately receive `false` on an unsuccessful locking attempt, you can change the mutex mode to **non-blocking**.
|
|
24
37
|
|
|
25
38
|
Install
|
|
26
39
|
-------
|
|
@@ -42,21 +55,25 @@ Register the Redis server: (e.g. in `config/initializers/redis_mutex.rb` for Rai
|
|
|
42
55
|
Redis::Classy.db = Redis.new(:host => 'localhost')
|
|
43
56
|
```
|
|
44
57
|
|
|
45
|
-
Note that Redis Mutex uses the `redis-classy` gem internally.
|
|
58
|
+
Note that Redis Mutex uses the `redis-classy` gem internally to organize keys in an isolated namespace.
|
|
46
59
|
|
|
47
60
|
There are four methods - `new`, `lock`, `unlock` and `sweep`:
|
|
48
61
|
|
|
49
62
|
```ruby
|
|
50
|
-
mutex = Redis::Mutex.new(key, options)
|
|
51
|
-
mutex.lock
|
|
52
|
-
mutex.unlock
|
|
53
|
-
Redis::Mutex.sweep
|
|
63
|
+
mutex = Redis::Mutex.new(key, options) # Configure a mutex lock
|
|
64
|
+
mutex.lock # Try to obtain the lock
|
|
65
|
+
mutex.unlock # Release the lock if it's not expired
|
|
66
|
+
Redis::Mutex.sweep # Forcibly remove all locks
|
|
67
|
+
|
|
68
|
+
Redis::Mutex.lock(key, options) # Shortcut to new + lock
|
|
54
69
|
```
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
The key argument can be symbol, string, or any Ruby objects that respond to `id` method, where the key is automatically set as
|
|
72
|
+
`TheClass:id`. For any given key, `Redis::Mutex:` prefix will be automatically prepended. For instance, if you pass a `Room`
|
|
73
|
+
object with id of `123`, the actual key in Redis will be `Redis::Mutex:Room:123`. The automatic prefixing and instance binding
|
|
74
|
+
is the feature of `Redis::Classy` - for more internal details, refer to [Redis Classy](https://github.com/kenn/redis-classy).
|
|
58
75
|
|
|
59
|
-
|
|
76
|
+
The initialize method takes several options.
|
|
60
77
|
|
|
61
78
|
```ruby
|
|
62
79
|
:block => 1 # Specify in seconds how long you want to wait for the lock to be released.
|
|
@@ -67,46 +84,59 @@ Also the initialize method takes several options.
|
|
|
67
84
|
# with the one who held the lock. (default: 10)
|
|
68
85
|
```
|
|
69
86
|
|
|
70
|
-
The lock method returns `true` when the lock has been successfully obtained, or returns `false` when the attempts
|
|
71
|
-
|
|
87
|
+
The lock method returns `true` when the lock has been successfully obtained, or returns `false` when the attempts failed after
|
|
88
|
+
the seconds specified with **:block**. When 0 is given to **:block**, it is set to **non-blocking** mode and immediately returns `false`.
|
|
72
89
|
|
|
73
|
-
|
|
90
|
+
In the following Rails example, only one request can enter to a given room.
|
|
74
91
|
|
|
75
92
|
```ruby
|
|
76
93
|
class RoomController < ApplicationController
|
|
94
|
+
before_filter { @room = Room.find(params[:id]) }
|
|
95
|
+
|
|
77
96
|
def enter
|
|
78
|
-
@room
|
|
79
|
-
|
|
80
|
-
mutex = Redis::Mutex.new(@room) # key => "Room:123"
|
|
81
|
-
mutex.lock do
|
|
97
|
+
success = Redis::Mutex.lock(@room) do # key => "Room:123"
|
|
82
98
|
# do something exclusively
|
|
83
99
|
end
|
|
100
|
+
render :text => success ? 'success!' : 'failed to obtain lock!'
|
|
84
101
|
end
|
|
85
102
|
end
|
|
86
103
|
```
|
|
87
104
|
|
|
88
|
-
Note that you need to explicitly call the unlock method
|
|
89
|
-
|
|
90
|
-
In the following example,
|
|
105
|
+
Note that you need to explicitly call the unlock method when you don't use the block syntax, and it is recommended to
|
|
106
|
+
put the `unlock` method in the `ensure` clause unless you're sure your code won't raise any exception.
|
|
91
107
|
|
|
92
108
|
```ruby
|
|
93
109
|
def enter
|
|
94
110
|
mutex = Redis::Mutex.new('non-blocking', :block => 0, :expire => 10.minutes)
|
|
95
|
-
mutex.lock
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
if mutex.lock
|
|
112
|
+
begin
|
|
113
|
+
# do something exclusively
|
|
114
|
+
ensure
|
|
115
|
+
mutex.unlock
|
|
116
|
+
end
|
|
117
|
+
render :text => 'success!'
|
|
118
|
+
else
|
|
119
|
+
render :text => 'failed to obtain lock!'
|
|
120
|
+
end
|
|
101
121
|
end
|
|
102
122
|
```
|
|
103
123
|
|
|
104
|
-
|
|
124
|
+
Macro-style definition
|
|
125
|
+
----------------------
|
|
126
|
+
|
|
127
|
+
If you want to wrap an entire method into a critical section, you can use the macro-style definition. The locking scope
|
|
128
|
+
will be `TheClass#method` and only one method can run at any given time.
|
|
129
|
+
|
|
130
|
+
If you give a proc object to the `after_failure` option, it will get called after locking attempt failed.
|
|
105
131
|
|
|
106
132
|
```ruby
|
|
107
|
-
|
|
108
|
-
|
|
133
|
+
class JobController < ApplicationController
|
|
134
|
+
include Redis::Mutex::Macro
|
|
135
|
+
auto_mutex :run, :block => 0, :after_failure => lambda { render :text => "failed to obtain lock!" }
|
|
136
|
+
|
|
137
|
+
def run
|
|
138
|
+
# do something exclusively
|
|
139
|
+
render :text => "success!"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
109
142
|
```
|
|
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/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.2.
|
|
1
|
+
1.2.3
|
data/lib/redis/mutex.rb
CHANGED
|
@@ -11,7 +11,7 @@ class Redis
|
|
|
11
11
|
#
|
|
12
12
|
class Mutex < Redis::Classy
|
|
13
13
|
autoload :Macro, 'redis/mutex/macro'
|
|
14
|
-
attr_reader :
|
|
14
|
+
attr_reader :locking
|
|
15
15
|
DEFAULT_EXPIRE = 10
|
|
16
16
|
|
|
17
17
|
def initialize(object, options={})
|
|
@@ -53,26 +53,26 @@ class Redis
|
|
|
53
53
|
|
|
54
54
|
def try_lock
|
|
55
55
|
now = Time.now.to_f
|
|
56
|
-
@expires_at = now + @expire
|
|
57
|
-
return true if setnx(@expires_at)
|
|
58
|
-
return false if get.to_f > now
|
|
56
|
+
@expires_at = now + @expire # Extend in each blocking loop
|
|
57
|
+
return true if self.setnx(@expires_at) # Success, the lock has been acquired
|
|
58
|
+
return false if self.get.to_f > now # Check if the lock is still effective
|
|
59
59
|
|
|
60
60
|
# The lock has expired but wasn't released... BAD!
|
|
61
|
-
return true if getset(@expires_at).to_f <= now
|
|
61
|
+
return true if self.getset(@expires_at).to_f <= now # Success, we acquired the previously expired lock
|
|
62
62
|
return false # Dammit, it seems that someone else was even faster than us to remove the expired lock!
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def unlock(force=false)
|
|
66
66
|
@locking = false
|
|
67
|
-
del if get.to_f == @expires_at or force
|
|
67
|
+
self.del if self.get.to_f == @expires_at or force # Release the lock if it seems to be yours
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
class << self
|
|
71
71
|
def sweep
|
|
72
|
-
return 0 if (all_keys = keys).empty?
|
|
72
|
+
return 0 if (all_keys = self.keys).empty?
|
|
73
73
|
|
|
74
74
|
now = Time.now.to_f
|
|
75
|
-
values = mget(*all_keys)
|
|
75
|
+
values = self.mget(*all_keys)
|
|
76
76
|
|
|
77
77
|
expired_keys = [].tap do |array|
|
|
78
78
|
all_keys.each_with_index do |key, i|
|
|
@@ -81,7 +81,7 @@ class Redis
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
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
|
|
84
|
+
self.del(key) if self.getset(key, now + DEFAULT_EXPIRE).to_f <= now # Make extra sure that anyone haven't extended the lock
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
expired_keys.size
|
data/redis-mutex.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = "redis-mutex"
|
|
8
|
-
s.version = "1.2.
|
|
8
|
+
s.version = "1.2.3"
|
|
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 = "2011-11-
|
|
12
|
+
s.date = "2011-11-11"
|
|
13
13
|
s.description = "Distrubuted mutex using Redis"
|
|
14
14
|
s.email = "kenn.ejima@gmail.com"
|
|
15
15
|
s.extra_rdoc_files = [
|
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: 1.2.
|
|
4
|
+
version: 1.2.3
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,11 +9,11 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2011-11-
|
|
12
|
+
date: 2011-11-11 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: redis-classy
|
|
16
|
-
requirement: &
|
|
16
|
+
requirement: &2154746560 !ruby/object:Gem::Requirement
|
|
17
17
|
none: false
|
|
18
18
|
requirements:
|
|
19
19
|
- - ~>
|
|
@@ -21,10 +21,10 @@ dependencies:
|
|
|
21
21
|
version: 1.0.0
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
|
-
version_requirements: *
|
|
24
|
+
version_requirements: *2154746560
|
|
25
25
|
- !ruby/object:Gem::Dependency
|
|
26
26
|
name: rspec
|
|
27
|
-
requirement: &
|
|
27
|
+
requirement: &2154745900 !ruby/object:Gem::Requirement
|
|
28
28
|
none: false
|
|
29
29
|
requirements:
|
|
30
30
|
- - ! '>='
|
|
@@ -32,10 +32,10 @@ dependencies:
|
|
|
32
32
|
version: '0'
|
|
33
33
|
type: :development
|
|
34
34
|
prerelease: false
|
|
35
|
-
version_requirements: *
|
|
35
|
+
version_requirements: *2154745900
|
|
36
36
|
- !ruby/object:Gem::Dependency
|
|
37
37
|
name: bundler
|
|
38
|
-
requirement: &
|
|
38
|
+
requirement: &2154744920 !ruby/object:Gem::Requirement
|
|
39
39
|
none: false
|
|
40
40
|
requirements:
|
|
41
41
|
- - ! '>='
|
|
@@ -43,10 +43,10 @@ dependencies:
|
|
|
43
43
|
version: '0'
|
|
44
44
|
type: :development
|
|
45
45
|
prerelease: false
|
|
46
|
-
version_requirements: *
|
|
46
|
+
version_requirements: *2154744920
|
|
47
47
|
- !ruby/object:Gem::Dependency
|
|
48
48
|
name: jeweler
|
|
49
|
-
requirement: &
|
|
49
|
+
requirement: &2154744400 !ruby/object:Gem::Requirement
|
|
50
50
|
none: false
|
|
51
51
|
requirements:
|
|
52
52
|
- - ! '>='
|
|
@@ -54,7 +54,7 @@ dependencies:
|
|
|
54
54
|
version: '0'
|
|
55
55
|
type: :development
|
|
56
56
|
prerelease: false
|
|
57
|
-
version_requirements: *
|
|
57
|
+
version_requirements: *2154744400
|
|
58
58
|
description: Distrubuted mutex using Redis
|
|
59
59
|
email: kenn.ejima@gmail.com
|
|
60
60
|
executables: []
|
|
@@ -92,7 +92,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
92
92
|
version: '0'
|
|
93
93
|
segments:
|
|
94
94
|
- 0
|
|
95
|
-
hash:
|
|
95
|
+
hash: 2435582858684301188
|
|
96
96
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
97
|
none: false
|
|
98
98
|
requirements:
|