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 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: