redis-mutex 1.2.2 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
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.0)
13
- redis-namespace (~> 1.0.0)
14
- redis-namespace (1.0.3)
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 do
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, 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`.
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
- 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.
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
- 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.
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
- Also the initialize method takes several options.
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
- failed after the seconds specified with **:block**. It immediately returns `false` when 0 is given to **:block**.
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
- Here's a sample usage in a Rails app:
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 = Room.find(params[:id])
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 unless you don't use the block syntax.
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
- # do something exclusively
97
- mutex.unlock
98
- rescue
99
- mutex.unlock
100
- raise
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
- Also note that, internally, the actual key is structured in the following form:
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
- Redis.new.keys
108
- => ["Redis::Mutex:Room:111", "Redis::Mutex:Room:112", ... ]
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.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 :block, :sleep, :expire, :locking
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 # Extend in each blocking loop
57
- return true if setnx(@expires_at) # Success, the lock has been acquired
58
- return false if get.to_f > now # Check if the lock is still effective
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 # Success, we acquired the previously expired lock
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 # Release the lock if it seems to be yours
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.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-05"
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.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-05 00:00:00.000000000 Z
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: &2162029080 !ruby/object:Gem::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: *2162029080
24
+ version_requirements: *2154746560
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &2161974220 !ruby/object:Gem::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: *2161974220
35
+ version_requirements: *2154745900
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: bundler
38
- requirement: &2161971780 !ruby/object:Gem::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: *2161971780
46
+ version_requirements: *2154744920
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: jeweler
49
- requirement: &2161970380 !ruby/object:Gem::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: *2161970380
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: -2314477826109442509
95
+ hash: 2435582858684301188
96
96
  required_rubygems_version: !ruby/object:Gem::Requirement
97
97
  none: false
98
98
  requirements: