mlanett-redis-lock 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/README.md +9 -6
- data/lib/redis-lock.rb +15 -9
- data/lib/redis-lock/version.rb +1 -1
- data/spec/redis_lock_spec.rb +6 -0
- metadata +8 -2
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -33,27 +33,30 @@ rather than acquiring the lock with a very long lifetime which will result in lo
|
|
33
33
|
|
34
34
|
A lock needs an owner. Redis::Lock defaults to using an owner id of HOSTNAME:PID.
|
35
35
|
|
36
|
-
A lock may need more than one attempt to acquire it. Redis::Lock offers
|
37
|
-
|
36
|
+
A lock may need more than one attempt to acquire it. Redis::Lock offers an acquisition timeout; this defaults to 10 seconds.
|
37
|
+
|
38
|
+
There are two lock methods: Redis#lock, which is more convenient, and Redis::Lock#lock.
|
39
|
+
Notice there are two timeouts: the lock's lifetime (:life option) and the acquisition timeout, which is less important.
|
40
|
+
The acquisition timeout is set via the :acquire option to Redis#lock or passed directly to Redis::Lock#lock.
|
38
41
|
|
39
42
|
## Usage
|
40
43
|
|
41
44
|
This gem adds lock() and unlock() to Redis instances.
|
42
45
|
lock() takes a block and is safer than using lock() and unlock() separately.
|
43
|
-
lock() takes a key and lifetime and optionally
|
46
|
+
lock() takes a key and lifetime and optionally an acquisition timeout (defaulting to 10 seconds).
|
44
47
|
|
45
48
|
redis.lock("test") { |lock| do_something }
|
46
49
|
|
47
|
-
redis.lock("test") do |lock|
|
50
|
+
redis.lock("test", life: 2*60, acquire: 2) do |lock|
|
48
51
|
array.each do |entry|
|
49
52
|
do_something(entry)
|
50
53
|
lock.extend_life(60)
|
51
54
|
end
|
52
55
|
end
|
53
56
|
|
54
|
-
##
|
57
|
+
## Goals
|
55
58
|
|
56
|
-
|
59
|
+
I wrote this when noticing that other lock gems were using non-robust approaches.
|
57
60
|
|
58
61
|
You need to be able to handle race conditions while acquiring the lock.
|
59
62
|
You need to be able to handle the owner of the lock failing to release it.
|
data/lib/redis-lock.rb
CHANGED
@@ -21,9 +21,9 @@ class Redis
|
|
21
21
|
|
22
22
|
# @param redis is a Redis instance
|
23
23
|
# @param key is a unique string identifying the object to lock, e.g. "user-1"
|
24
|
-
# @param options[:life]
|
24
|
+
# @param options[:life] should be set, but defaults to 1 minute
|
25
25
|
# @param options[:owner] may be set, but defaults to HOSTNAME:PID
|
26
|
-
# @param options[:sleep]
|
26
|
+
# @param options[:sleep] is used when trying to acquire the lock; milliseconds; defaults to 125.
|
27
27
|
def initialize( redis, key, options = {} )
|
28
28
|
check_keys( options, :owner, :life, :sleep )
|
29
29
|
@redis = redis
|
@@ -35,8 +35,9 @@ class Redis
|
|
35
35
|
@sleep_in_ms = options[:sleep] || 125
|
36
36
|
end
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
# @param acquisition_timeout defaults to 10 seconds and can be used to determine how long to wait for a lock.
|
39
|
+
def lock( acquisition_timeout = 10, &block )
|
40
|
+
do_lock_with_timeout(acquisition_timeout) or raise LockNotAcquired.new(key)
|
40
41
|
if block then
|
41
42
|
begin
|
42
43
|
result = (block.arity == 1) ? block.call(self) : block.call
|
@@ -74,9 +75,9 @@ class Redis
|
|
74
75
|
# internal api
|
75
76
|
#
|
76
77
|
|
77
|
-
def do_lock_with_timeout(
|
78
|
+
def do_lock_with_timeout( acquisition_timeout )
|
78
79
|
locked = false
|
79
|
-
with_timeout(
|
80
|
+
with_timeout(acquisition_timeout) { locked = do_lock }
|
80
81
|
locked
|
81
82
|
end
|
82
83
|
|
@@ -166,9 +167,10 @@ class Redis
|
|
166
167
|
return false
|
167
168
|
end
|
168
169
|
|
169
|
-
# Calls block until it returns true or times out.
|
170
|
+
# Calls block until it returns true or times out.
|
170
171
|
# @param block should return true if successful, false otherwise
|
171
172
|
# @returns true if successful, false otherwise
|
173
|
+
# Note: at one time I thought of using a backoff strategy, but don't think that's important now.
|
172
174
|
def with_timeout( timeout, &block )
|
173
175
|
expire = Time.now + timeout.to_f
|
174
176
|
sleepy = @sleep_in_ms / 1000.to_f()
|
@@ -178,7 +180,6 @@ class Redis
|
|
178
180
|
log :debug, "Timeout for #{@key}" and return false if Time.now + sleepy > expire
|
179
181
|
sleep(sleepy)
|
180
182
|
# might like a different strategy, but general goal is not use 100% cpu while contending for a lock.
|
181
|
-
# sleepy = [ sleepy * 2, ( expire - Time.now ) / 4 ].min
|
182
183
|
end
|
183
184
|
end
|
184
185
|
|
@@ -219,7 +220,12 @@ class Redis
|
|
219
220
|
|
220
221
|
# Convenience methods
|
221
222
|
|
222
|
-
# @
|
223
|
+
# @param key is a unique string identifying the object to lock, e.g. "user-1"
|
224
|
+
# @options are as specified for Redis::Lock#lock (including :life)
|
225
|
+
# @param options[:life] should be set, but defaults to 1 minute
|
226
|
+
# @param options[:owner] may be set, but defaults to HOSTNAME:PID
|
227
|
+
# @param options[:sleep] is used when trying to acquire the lock; milliseconds; defaults to 125.
|
228
|
+
# @param options[:acquire] defaults to 10 seconds and can be used to determine how long to wait for a lock.
|
223
229
|
def lock( key, options = {}, &block )
|
224
230
|
acquire = options.delete(:acquire) || 10
|
225
231
|
Lock.new( self, key, options ).lock( acquire, &block )
|
data/lib/redis-lock/version.rb
CHANGED
data/spec/redis_lock_spec.rb
CHANGED
@@ -136,4 +136,10 @@ describe Redis::Lock, redis: true do
|
|
136
136
|
# We leave [ present, present ] to be unspecified.
|
137
137
|
end
|
138
138
|
|
139
|
+
example "How to get a lock using the helper." do
|
140
|
+
redis.lock "mykey", life: 10, acquire: 1 do |lock|
|
141
|
+
lock.extend_life 10
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
139
145
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mlanett-redis-lock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date: 2013-
|
16
|
+
date: 2013-08-25 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: redis
|
@@ -70,12 +70,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
70
70
|
- - ! '>='
|
71
71
|
- !ruby/object:Gem::Version
|
72
72
|
version: '0'
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
hash: -132533030489951021
|
73
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
77
|
none: false
|
75
78
|
requirements:
|
76
79
|
- - ! '>='
|
77
80
|
- !ruby/object:Gem::Version
|
78
81
|
version: '0'
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
hash: -132533030489951021
|
79
85
|
requirements: []
|
80
86
|
rubyforge_project:
|
81
87
|
rubygems_version: 1.8.23
|