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 CHANGED
@@ -4,10 +4,10 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :development do
7
+ gem "guard"
7
8
  gem "guard-rspec"
8
9
  gem "rb-fsevent" # for guard
9
10
  gem "rspec"
10
- gem "ruby-debug19", require: false
11
11
  end
12
12
 
13
13
  group :test do
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 a timeout; this defaults to 10 seconds.
37
- It uses exponential backoff with sleeps so it's fairly safe to use longer timeouts.
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 a timeout (otherwise defaulting to 10 second).
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
- ## Problems
57
+ ## Goals
55
58
 
56
- Why do other gems get this wrong?
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.
@@ -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] may be set, but defaults to 1 minute
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] optional, number of milliseconds to sleep when lock is held, defaults to 125
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
- def lock( timeout = 10, &block )
39
- do_lock_with_timeout(timeout) or raise LockNotAcquired.new(key)
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( timeout )
78
+ def do_lock_with_timeout( acquisition_timeout )
78
79
  locked = false
79
- with_timeout(timeout) { locked = do_lock }
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. TODO: Use exponential backoff.
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
- # @option timeout defaults to 10 seconds
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 )
@@ -1,5 +1,5 @@
1
1
  class Redis
2
2
  class Lock
3
- VERSION = "0.2.4"
3
+ VERSION = "0.2.5"
4
4
  end
5
5
  end
@@ -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
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-07-01 00:00:00.000000000 Z
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