mario-redis-lock 1.0.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2853868a970109fa52abc01236f6107c2d4dbded
4
- data.tar.gz: 6cdecec78e8585b1b24c142100d80900a75ea16e
3
+ metadata.gz: e1f96fb381f15d6699a71780455f6328569d67f3
4
+ data.tar.gz: 059591adc0ec6438f6d8c9990f16b8bf027aee7b
5
5
  SHA512:
6
- metadata.gz: 48204bca5b65a3073ccbfe5edbf54a71424388381daf698c8ecc11689496063fe5039d41acbe222d4e1d6d87cb13bdab692db1911aa47a939da061518fedd3b6
7
- data.tar.gz: ee01cdf3e836303897dd3c459ca0e6ec6b07979358c75115025c26c254fc1816d7472dda43873e33ef212c9589499d97f57788936430b6d8229ac741dc64db4b
6
+ metadata.gz: 6c907b7305b4c0470d50559cae339764b9d5d825ea115c251971604b734b27d11e30bdae9b0a79aabc5629bff40d4dadfbcd7f203bf1c4c14aa903515b722031
7
+ data.tar.gz: 4b9290554e5061faeb847a6137f891396f1e6e99964e2bffd6376fe61369626f02ca6b722d53eee1090c365f3bdd6b4fc848fee73028ec50c9d0247f30e7a95d
@@ -0,0 +1,46 @@
1
+ ## Example: Beer Waiter
2
+
3
+ In this game, everybody wants a beer but there is only one waiter to attend. Each thread is a thirsty customer, and the Redis lock is the waiter.
4
+
5
+ This example can be copy-pasted, just make sure you have redis in localhost (default `Redis.new` instance) and the mario-redis-lock gem installed.
6
+
7
+ ```ruby
8
+ require 'redis_lock'
9
+
10
+ N = 15 # how many people in the bar
11
+ puts "Starting with #{N} new thirsty customers ..."
12
+ puts
13
+
14
+ RedisLock.configure do |conf|
15
+ conf.retry_sleep = 1 # call the waiter every second
16
+ conf.retry_timeout = 10 # wait up to 10 seconds before giving up
17
+ conf.autorelease = 3 # the waiter will wait a maximun of 3 seconds to be "released" before giving the lock to someone else
18
+ end
19
+
20
+ # Code for a single Thread#i
21
+ def try_to_get_a_drink(i)
22
+ name = "Thread##{i}"
23
+ RedisLock.acquire do |lock|
24
+ if lock.acquired?
25
+ puts "<< #{name} gets barman's attention (lock acquired)"
26
+ sleep 0.2 # time do decide
27
+ beer = %w(lager pale_ale ipa stout sour)[rand 5]
28
+ puts ".. #{name} orders a #{beer}"
29
+ sleep 0.4 # time for the waiter to serve the beer
30
+ puts ">> #{name} takes the #{beer} and leaves happy :)"
31
+ puts
32
+ else
33
+ puts "!! #{name} is bored of waiting and leaves angry (timeout)"
34
+ end
35
+ end
36
+ end
37
+
38
+ # Start N threads that will be executed in parallel
39
+ threads = []
40
+ N.times(){|i| threads << Thread.new(){ try_to_get_a_drink(i) }}
41
+ threads.each{|thread| thread.join} # do not exit until all threads are done
42
+
43
+ puts "DONE"
44
+ ```
45
+
46
+ It uses threads for concurrency, but you can also execute this script from different places at the same time in parallel, they share the same lock as far as they use the same Redis instance.
@@ -0,0 +1,113 @@
1
+ ## Example: Avoid the Dog-Pile effec when invalidating some cached value
2
+
3
+ The Dog-Pile effect is a specific case of the [Thundering Herd problem](http://en.wikipedia.org/wiki/Thundering_herd_problem),
4
+ that happens when a cached value expires and suddenly too many threads try to calculate the new value at the same time.
5
+
6
+ Sometimes, the calculation takes expensive resources and it is just fine to do it from just one thread.
7
+
8
+ Assume you have a simple cache, a `fetch` function that uses a redis instance.
9
+
10
+ Without the lock:
11
+
12
+ ```ruby
13
+ # Retrieve the cached value from the redis key.
14
+ # If the key is not available, execute the block
15
+ # and store the new calculated value in the redis key with an expiration time.
16
+ def fetch(redis, key, expire, &block)
17
+ redis.get(key) or (
18
+ val = block.call
19
+ redis.setex(key, expire, val) if val
20
+ val
21
+ )
22
+ end
23
+ ```
24
+
25
+ Whith this method, it is easy to optimize slow operations by caching them in Redis.
26
+ For example, if you want to do a `heavy_database_query`:
27
+
28
+ ```ruby
29
+ require 'redis'
30
+ redis = Redis.new(url: "redis://:p4ssw0rd@host:6380")
31
+ expire = 60 # keep the result cached for 1 minute
32
+ key = 'heavy_query'
33
+
34
+ val = fetch redis, key, expire do
35
+ heavy_database_query # Recalculate if not cached (SLOW)
36
+ end
37
+
38
+ puts val
39
+ ```
40
+
41
+ But this fetch could block the database if executed from too many threads, because when the Redis key expires all of them will do the same "heavy_database_query" at the same time.
42
+
43
+ To avoid this problem, you can make a `fetch_with_lock` method using a `RedisLock`:
44
+
45
+ ```ruby
46
+ # Retrieve the cached value from the redis key.
47
+ # If the key is not available, execute the block
48
+ # and store the new calculated value in the redis key with an expiration time.
49
+ # The block is executed with a RedisLock to avoid the dog pile effect.
50
+ # Use the following options:
51
+ # * :retry_timeout => (default 10) Seconds to stop trying to get the value from redis or the lock.
52
+ # * :retry_sleep => (default 0.1) Seconds to sleep (block the process) between retries.
53
+ # * :lock_autorelease => (default same as :retry_timeout) Maximum time in seconds to execute the block. The lock is released after this, assuming that the process failed.
54
+ # * :lock_key => (default "#{key}_lock") The key used for the lock.
55
+ def fetch_with_lock(redis, key, expire, opts={}, &block)
56
+ # Options
57
+ opts[:retry_timeout] ||= 10
58
+ opts[:retry_sleep] ||= 0.1
59
+ opts[:first_try_time] ||= Time.now # used as memory for next retries
60
+ opts[:lock_key] ||= "#{key}_lock"
61
+ opts[:lock_autorelease] ||= opts[:retry_timeout]
62
+
63
+ # Try to get from redis.
64
+ val = redis.get(key)
65
+ return val if val
66
+
67
+ # If not in redis, calculate the new value (block.call), but with a RedisLock.
68
+ RedisLock.acquire({
69
+ redis: redis,
70
+ key: opts[:lock_key],
71
+ autorelease: opts[:lock_autorelease],
72
+ retry: false,
73
+ }) do |lock|
74
+ if lock.acquired?
75
+ val = block.call # execute block, load/calculate heavy stuff
76
+ redis.setex(key, expire, val) if val # store in the redis cache
77
+ end
78
+ end
79
+ return val if val
80
+
81
+ # If the lock was not available, then someone else was already re-calculating the value.
82
+ # Just wait a little bit and try again.
83
+ if (Time.now - opts[:first_try_time]) < opts[:retry_timeout] # unless timed out
84
+ sleep opts[:retry_sleep]
85
+ return fetch_with_lock(redis, key, expire, opts, &block)
86
+ end
87
+
88
+ # If the lock is still unavailable after the timeout, desist and return nil.
89
+ nil
90
+ end
91
+
92
+ ```
93
+
94
+ Now with this new method, is easy to do the "heavy_database_query", cached in redis and with a lock:
95
+
96
+
97
+ ```ruby
98
+ require 'redis'
99
+ require 'redis_lock'
100
+ redis = Redis.new(url: "redis://:p4ssw0rd@host:6380")
101
+ expire = 60 # keep the result cached for 1 minute
102
+ key = 'heavy_query'
103
+
104
+ val = fetch_with_lock redis, key, expire, retry_timeout: 10, retry_sleep: 1 do
105
+ heavy_database_query # Recalculate if not cached (SLOW)
106
+ end
107
+
108
+ puts val
109
+ ```
110
+
111
+ In this case, the script could be executed from as many threads as we want at the same time, because the "heavy_database_query" is done only once while the other threads wait until the value is cached again or the lock is released.
112
+
113
+
data/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # RedisLock
2
2
 
3
- Yet another distributed lock for Ruby using Redis, with emphasis in the documentation.
3
+ Yet another Ruby distributed lock using Redis, with emphasis in transparency.
4
4
 
5
+ Implements the locking algorithm described in the [Redis SET command documentation](http://redis.io/commands/set):
5
6
 
6
- ## Why another redis lock gem?
7
+ * Acquire lock with `SET {{key}} {{uuid_token}} NX PX {{ms_to_expire}}`
8
+ * Release lock with `EVAL "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return nil end" {{key}} {{uuid_token}}`
9
+ * Auto release lock if expires
7
10
 
8
- Other redis locks for ruby: [redis-mutex](https://rubygems.org/gems/redis-mutex), [mlanett-redis-lock](https://rubygems.org/gems/mlanett-redis-lock), [redis-lock](https://rubygems.org/gems/redis-lock), [jashmenn-redis-lock](https://rubygems.org/gems/jashmenn-redis-lock), [ruby_redis_lock](https://rubygems.org/gems/ruby_redis_lock), [robust-redis-lock](https://rubygems.org/gems/robust-redis-lock), [bfg-redis-lock](https://rubygems.org/gems/bfg-redis-lock), etc.
11
+ It has the properties:
9
12
 
10
- Looking at those other gems I realized that it was not easy to know what was exactly going on with the locks. Then I made this one to be simple but explicit, to be used with confidence in my high scale production applications.
13
+ * Mutual exclusion: At any given moment, only one client can hold a lock
14
+ * Deadlock free: Eventually it is always possible to acquire a lock, even if the client that locked a resource crashed or gets partitioned
15
+ * NOT fault tolerant: if the REDIS instance goes down, the lock doesn't work. For a lock wiht liveness guarantee, see [redlock-rb](https://github.com/antirez/redlock-rb), that can use multiple REDIS instances to handle the lock.
11
16
 
12
17
 
13
18
  ## Installation
@@ -17,7 +22,7 @@ Requirements:
17
22
  * [Redis](http://redis.io/) >= 2.6.12
18
23
  * [redis gem](https://rubygems.org/gems/redis) >= 3.0.5
19
24
 
20
- The required versions are needed for the new syntax of the SET command, to easily implement the robust locking algorithm described in the [SET command documentation](http://redis.io/commands/set).
25
+ The required versions are needed for the new syntax of the SET command (using NX and EX/PX).
21
26
 
22
27
  Install from RubyGems:
23
28
 
@@ -30,14 +35,14 @@ Or include it in your project's `Gemfile` with Bundler:
30
35
 
31
36
  ## Usage
32
37
 
33
- Acquire the lock to do "exclusive stuff":
38
+ Acquire the lock to `do_exclusive_stuff`:
34
39
 
35
40
  ```ruby
36
- RedisLock.adquire do |lock|
41
+ RedisLock.acquire do |lock|
37
42
  if lock.acquired?
38
- do_exclusive_stuff # you are the one with the lock, hooray!
43
+ do_exclusive_stuff # you are the only process with the lock, hooray!
39
44
  else
40
- oh_well # someone else has the lock
45
+ oh_well # timeout, some other process has the lock and didn't release it before the retry_timeout
41
46
  end
42
47
  end
43
48
  ```
@@ -49,42 +54,31 @@ Or (equivalent)
49
54
  lock = RedisLock.new
50
55
  if lock.acquire
51
56
  begin
52
- do_exclusive_stuff # you are the one with the lock, hooray!
57
+ do_exclusive_stuff # you are the only process with the lock, hooray!
53
58
  ensure
54
59
  lock.release
55
60
  end
56
61
  else
57
- oh_well # someone else has the lock
62
+ oh_well # timeout, some other process has the lock and didn't release it before the retry_timeout
58
63
  end
59
64
  ```
60
65
 
61
- The class method `RedisLock.adquire(options, &block)` is more concise and releases the lock at the end of the block, even if `do_exclusive_stuff` raises an exception.
62
- But the second alternative is a little more flexible.
66
+ The class method `RedisLock.acquire(options, &block)` is more concise and releases the lock at the end of the block, even if `do_exclusive_stuff` raises an exception.
67
+ The second alternative is a little more flexible.
68
+
69
+ #### Detailed Usage Examples
63
70
 
71
+ * [Beer Waiter](EXAMPLE_BEER_WAITER.md): Run many threads at the same time, all them try to get a beer in 3 seconds using the same lock. Some will get it, some will timeout.
72
+ * [Dog Pile Effect](EXAMPLE_DOG_PILE_EFFECT.md): See how to implement a `fetch_with_lock` method, that works like most `Cache.fetch(key, &block)` methods out there (if value is cached in that given key, return the cached value, otherwise run the block), but only executes the block from one of the processes that share that cache, avoiding the case when the cache is invalidated and all processes execute an expensive operation at the same time.
64
73
 
65
74
  ### Options
66
75
 
67
76
  * **redis**: (default `Redis.new`) an instance of Redis, or an options hash to initialize an instance of Redis (see [redis gem](https://rubygems.org/gems/redis)). You can also pass anything that "quaks" like redis, for example an instance of [mock_redis](https://rubygems.org/gems/mock_redis), for testing purposes.
68
77
  * **key**: (default `"RedisLock::default"`) Redis key used for the lock. If you need multiple locks, use a different (unique) key for each lock.
69
78
  * **autorelease**: (default `10.0`) seconds to automatically release (expire) the lock after being acquired. Make sure to give enough time for your "exclusive stuff" to be executed, otherwise other processes could get the lock and start messing with the "exclusive stuff" before this one is done. The autorelease time is important, even when manually doing `lock.realease`, because the process could crash before releasing the lock. Autorelease (expiration time) guarantees that the lock will always be released.
70
- * **retry**: (default `true`) boolean to enable/disable consecutive acquire retries in the same `acquire` call. If true, use `retry_timeout` and `retry_sleep` to specify how long and hot often should the `acquire` method be blocking the thread until is able to get the lock.
71
- * **retry_timeout**: (default `10.0`) time in seconds to specify how long should this thread be waiting for the lock to be released. Note that the execution thread is put to sleep while waiting. For a non-blocking approach, set `retry` to false.
72
- * **retry_sleep**: (default `0.1`) seconds to sleep between retries. For example: `RedisLock.adquire(retry_timeout: 10.0, retry_sleep: 0.1) do |lock|`, in the worst case scenario, will do 99 or 100 retries (one every 100 milliseconds, plus a little extra for the acquire attempt) during 10 seconds, and finally yield with `lock.acquired? == false`.
73
-
74
- Configure the default values with `RedisLock.configure`:
75
-
76
- ```ruby
77
- RedisLock.configure do |defaults|
78
- defaults.redis = Redis.new
79
- defaults.key = "RedisLock::default"
80
- defaults.autorelease = 10.0
81
- defaults.retry = true
82
- defaults.retry_timeout = 10.0
83
- defaults.retry_sleep = 0.1
84
- end
85
- ```
86
-
87
- A good place to set defaults in a Rails app would be in an initializer `conf/initializers/redis_lock.rb`.
79
+ * **retry**: (default `true`) boolean to enable/disable consecutive acquire retries in the same `acquire` call. If true, use `retry_timeout` and `retry_sleep` to specify how long and how often should the `acquire` method block the thread (sleep) until able to get the lock.
80
+ * **retry_timeout**: (default `10.0`) seconds before giving up before the lock is released. Note that the execution thread is put to sleep while waiting. For a non-blocking approach, set `retry` to false.
81
+ * **retry_sleep**: (default `0.1`) seconds to sleep between retries. For example: `RedisLock.acquire(retry_timeout: 10.0, retry_sleep: 0.1){|lock| ... }` if the lock was acquired by other process and never released, will do almost 100 retries (a rerty every 0.1 seconds, plus a little extra to run the the `SET` command) during 10 seconds, and finally yield with `lock.acquired? == false`.
88
82
 
89
83
  Options can be set to other than the defaults when calling `RedisLock.acquire`:
90
84
 
@@ -99,127 +93,34 @@ end
99
93
  Or when creating a new lock instance:
100
94
 
101
95
  ```ruby
102
- lock = RedisLock.new(key: 'exclusive_stuff', retry: false)
96
+ lock = RedisLock.new(key: 'exclusive_stuff', retry: false, autorelease: 0.1)
103
97
  if lock.acquire
104
- begin
105
- do_exclusive_stuff
106
- ensure
107
- lock.release
108
- end
98
+ do_exclusive_stuff_or_not
109
99
  end
110
100
  ```
111
101
 
112
- ### Example: Shared Photo Booth that can only take one photo at a time
113
-
114
- If we have a `PhotoBooth` shared resource, we can use a `RedisLock` to ensure it is used only by one thread at a time:
102
+ You can also configure default values with `RedisLock.configure`:
115
103
 
116
104
  ```ruby
117
- require 'redis_lock'
118
- require 'photo_booth' # made up shared resource
119
-
120
- RedisLock.configure do |c|
121
- c.redis = {url: "redis://:p4ssw0rd@10.0.1.1:6380/15"}
122
- c.key = 'photo_booth_lock'
123
-
124
- c.autorelease = 60 # assume it never takes more than one minute to make a picture
125
- c.retry_timeout = 300 # retry for 5 minutes
126
- c.retry_sleep = 1 # retry once every second
127
- end
128
-
129
- RedisLock.acquire do |lock|
130
- if lock.acquired?
131
- PhotoBooth.take_photo
132
- else
133
- raise "I'm bored of waiting and I'm getting out"
134
- end
135
- end
136
- ```
137
-
138
- This script can be executed from many different places at the same time, as far as they have access to the shared PhotoBooth and Redis instances. Only one photo will be taken at a time.
139
- Note that the options `autorelease`, `retry_timeout` and `retry_sleep` should be tuned differently depending on the frequency of the operation and the known speed of the `PhotoBooth.take_photo` operation.
140
-
141
-
142
- ### Example: Avoid the Dog-Pile effec when invalidating some cached value
143
-
144
- The Dog-Pile effect is a specific case of the [Thundering Herd problem](http://en.wikipedia.org/wiki/Thundering_herd_problem),
145
- that happens when a cached value expires and suddenly too many threads try to calculate the new value at the same time.
146
-
147
- Sometimes, the calculation takes expensive resources and it is just fine to do it from just one thread.
148
-
149
- Assume you have a simple cache, a `fetch` function that uses a redis instance.
150
-
151
- Without the lock:
152
-
153
- ```ruby
154
- # Retrieve the cached value from the redis key.
155
- # If the key is not available, execute the block
156
- # and store the new calculated value in the redis key with an expiration time.
157
- def fetch(redis, key, expire, &block)
158
- val = redis.get(key)
159
- if not val
160
- val = block.call
161
- redis.setex(key, expire, val) unless val.nil? # do not set anything if the value is nil
162
- end
163
- val
164
- end
165
- ```
166
-
167
- Whith this method, it is easy to optimize slow operations by caching them in Redis.
168
- For example, if you want to do a `heavy_database_query`:
169
-
170
- ```ruby
171
- require 'redis'
172
- redis = Redis.new(url: "redis://:p4ssw0rd@host:6380")
173
-
174
- val = fetch redis, 'heavy_query', 10 do
175
- heavy_database_query # Recalculate if not cached (SLOW)
105
+ RedisLock.configure do |defaults|
106
+ defaults.redis = Redis.new
107
+ defaults.key = "RedisLock::default"
108
+ defaults.autorelease = 10.0
109
+ defaults.retry = true
110
+ defaults.retry_timeout = 10.0
111
+ defaults.retry_sleep = 0.1
176
112
  end
177
-
178
- puts val
179
113
  ```
180
114
 
181
- But this fetch could block the database if executed from too many threads, because when the Redis key expires all of them will do the `heavy_database_query` at the same time.
182
-
183
- Avoid this problem with a `RedisLock`:
184
-
185
- ```ruby
186
- require 'redis'
187
- require 'redis_lock'
188
- redis = Redis.new(url: "redis://:p4ssw0rd@host:6380")
189
-
190
- RedisLock.configure do |c|
191
- c.redis = redis
192
- c.key = 'heavy_query_lock'
193
- c.autorelease = 20 # assume it never takes more than 20 seconds to do the slow query
194
- c.retry = false # try to acquire only once, if the lock is already taken then the new value should be cached again soon
195
- end
115
+ A good place to set defaults in a Rails app would be in an initializer like `conf/initializers/redis_lock.rb`.
196
116
 
197
- def fetch_with_lock(retries = 10)
198
- val = fetch redis, 'heavy_query', 10 do
199
- # If we need to recalculate val,
200
- # use a lock to make sure that heavy_database_query is only done by one process
201
- RedisLock.acquire do |lock|
202
- if lock.acquired?
203
- heavy_database_query
204
- else
205
- nil # do not store in cache and return val = nil
206
- end
207
- end
208
- end
209
117
 
210
- # Try again if cache miss, and the lock was acquired by other process.
211
- if val.nil? and retries > 0
212
- fetch_with_lock(retries - 1)
213
- else
214
- val
215
- end
216
- end
118
+ ## Why another Redis lock gem?
217
119
 
218
- val = fetch_with_lock()
219
- puts val
220
- ```
120
+ There are other Redis locks for Ruby: [redlock-rb](https://github.com/antirez/redlock-rb), [redis-mutex](https://rubygems.org/gems/redis-mutex), [mlanett-redis-lock](https://rubygems.org/gems/mlanett-redis-lock), [redis-lock](https://rubygems.org/gems/redis-lock), [jashmenn-redis-lock](https://rubygems.org/gems/jashmenn-redis-lock), [ruby_redis_lock](https://rubygems.org/gems/ruby_redis_lock), [robust-redis-lock](https://rubygems.org/gems/robust-redis-lock), [bfg-redis-lock](https://rubygems.org/gems/bfg-redis-lock), etc.
221
121
 
222
- In this case, the script could be executed from as many threads as we want at the same time, because the heavy_database_query is done only once while the other threads wait until the value is cached again or the lock is released.
122
+ I realized I was not sure how most of them exactly work. What is exactly going on with the lock? When does it expire? How many times needs to retry? Is the thread put to sleep meanwhile?.
123
+ By the time I learned how to tell if a lock is good or not, I learned enough to write my own, making it simple but explicit, to be used with confidence in my high scale production applications.
223
124
 
224
125
 
225
126
  ## Contributing
@@ -232,3 +133,4 @@ In this case, the script could be executed from as many threads as we want at th
232
133
 
233
134
  Make sure you have installed Redis in localhost:6379. The DB 15 will be used for tests (and flushed after every test).
234
135
  There is a rake task to play with an example: `rake smoke_and_pass`
136
+
@@ -4,7 +4,7 @@ require 'securerandom' # SecureRandom (from stdlib)
4
4
  class RedisLock
5
5
 
6
6
  # Gem version
7
- VERSION = "1.0.1"
7
+ VERSION = "1.2.0"
8
8
 
9
9
  # Original defaults
10
10
  DEFAULT_ATTRS = {
@@ -37,15 +37,18 @@ class RedisLock
37
37
  # Acquire a lock. Use options to override defaults.
38
38
  # This method makes sure to release the lock as soon as the block is finalized.
39
39
  def self.acquire(opts={}, &block)
40
+ if block.arity != 1
41
+ raise ArgumentError.new('Expected lock parameter in block. Example: RedisLock.acquire(opts){|lock| do_stuff if lock.acquired? }')
42
+ end
40
43
  lock = RedisLock.new(opts)
41
44
  if lock.acquire
42
45
  begin
43
- block.call(lock)
46
+ block.call(lock) # lock.acquired? => true
44
47
  ensure
45
- lock.release
48
+ lock.release # Exception => release early
46
49
  end
47
50
  else
48
- block.call(lock)
51
+ block.call(lock) # lock.acquired? => false
49
52
  end
50
53
  end
51
54
 
@@ -58,11 +61,12 @@ class RedisLock
58
61
  # Set attributes from options or defaults
59
62
  self.redis = opts[:redis] || @@config.redis || Redis.new
60
63
  self.redis = Redis.new(redis) if redis.is_a? Hash # allow to use Redis options instead of a redis instance
61
- self.key = opts[:key] || @@config.key
62
- self.autorelease = opts[:autorelease] || @@config.autorelease
64
+
65
+ self.key = opts[:key] || @@config.key
66
+ self.autorelease = opts[:autorelease] || @@config.autorelease
63
67
  self.retry = opts.include?(:retry) ? opts[:retry] : @@config.retry
64
68
  self.retry_timeout = opts[:retry_timeout] || @@config.retry_timeout
65
- self.retry_sleep = opts[:retry_sleep] || @@config.retry_sleep
69
+ self.retry_sleep = opts[:retry_sleep] || @@config.retry_sleep
66
70
  end
67
71
 
68
72
  # Try to acquire the lock.
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.version = RedisLock::VERSION
10
10
  spec.authors = ["Mario Izquierdo"]
11
11
  spec.email = ["tomario@gmail.com"]
12
- spec.summary = %q{Yet another distributed lock for Ruby using Redis.}
13
- spec.description = %q{Yet another distributed lock for Ruby using Redis, with emphasis in the documentation. Requires Redis >= 2.6.12, because it uses the new syntax for SET to easily implement the robust algorithm described in the SET command documentation (http://redis.io/commands/set).}
12
+ spec.summary = %q{Yet another Ruby distributed lock using Redis, with emphasis in transparency.}
13
+ spec.description = %q{Yet another Ruby distributed lock using Redis, with emphasis in transparency. Requires Redis >= 2.6.12, because it uses the new syntax for SET to easily implement the robust algorithm described in the SET command documentation (http://redis.io/commands/set).}
14
14
  spec.homepage = "https://github.com/marioizquierdo/mario-redis-lock"
15
15
  spec.license = "MIT"
16
16
 
@@ -19,8 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ["lib"]
21
21
 
22
- spec.add_runtime_dependency 'redis', '>= 3.0.5' # Needed support for SET with EX, PX, NX, XX options: https://github.com/redis/redis-rb/pull/343
22
+ spec.add_runtime_dependency 'redis', '~> 3', '>= 3.0.5' # Needed support for SET with EX, PX, NX, XX options: https://github.com/redis/redis-rb/pull/343
23
23
 
24
- spec.add_development_dependency "bundler", "~> 1.5"
25
- spec.add_development_dependency "rake"
24
+ spec.add_development_dependency 'bundler', '~> 1'
25
+ spec.add_development_dependency 'rake', '~> 10'
26
26
  end
@@ -28,7 +28,7 @@ describe RedisLock do
28
28
  end
29
29
 
30
30
 
31
- describe "configure" do
31
+ describe ".configure" do
32
32
  it "changes the defaults" do
33
33
  RedisLock.configure do |conf|
34
34
  conf.key = "mykey"
@@ -53,10 +53,12 @@ describe RedisLock do
53
53
  end
54
54
  end
55
55
 
56
- describe "acquire" do
56
+ describe ".acquire" do
57
57
  describe "with bad redis connection" do
58
58
  it "raises a Redis::CannotConnectError" do
59
- proc { RedisLock.acquire(redis: {url: "redis://localhost:1111/15"}){|l| }}.must_raise Redis::CannotConnectError
59
+ proc {
60
+ RedisLock.acquire(redis: {url: "redis://localhost:1111/15"}){|lock| }
61
+ }.must_raise Redis::CannotConnectError
60
62
  end
61
63
  end
62
64
 
@@ -82,6 +84,11 @@ describe RedisLock do
82
84
  lock.autorelease.must_equal 5555
83
85
  end
84
86
  end
87
+ it "does not allow to pass a block with no |lock|" do
88
+ proc {
89
+ RedisLock.acquire(){ puts "I should not have been printed" }
90
+ }.must_raise ArgumentError, "You should use lock"
91
+ end
85
92
  end
86
93
 
87
94
  describe "initialize" do
metadata CHANGED
@@ -1,68 +1,75 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mario-redis-lock
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mario Izquierdo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-23 00:00:00.000000000 Z
11
+ date: 2016-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ - - ">="
18
21
  - !ruby/object:Gem::Version
19
22
  version: 3.0.5
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - '>='
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3'
30
+ - - ">="
25
31
  - !ruby/object:Gem::Version
26
32
  version: 3.0.5
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: bundler
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - ~>
37
+ - - "~>"
32
38
  - !ruby/object:Gem::Version
33
- version: '1.5'
39
+ version: '1'
34
40
  type: :development
35
41
  prerelease: false
36
42
  version_requirements: !ruby/object:Gem::Requirement
37
43
  requirements:
38
- - - ~>
44
+ - - "~>"
39
45
  - !ruby/object:Gem::Version
40
- version: '1.5'
46
+ version: '1'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: rake
43
49
  requirement: !ruby/object:Gem::Requirement
44
50
  requirements:
45
- - - '>='
51
+ - - "~>"
46
52
  - !ruby/object:Gem::Version
47
- version: '0'
53
+ version: '10'
48
54
  type: :development
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
51
57
  requirements:
52
- - - '>='
58
+ - - "~>"
53
59
  - !ruby/object:Gem::Version
54
- version: '0'
55
- description: Yet another distributed lock for Ruby using Redis, with emphasis in the
56
- documentation. Requires Redis >= 2.6.12, because it uses the new syntax for SET
57
- to easily implement the robust algorithm described in the SET command documentation
58
- (http://redis.io/commands/set).
60
+ version: '10'
61
+ description: Yet another Ruby distributed lock using Redis, with emphasis in transparency.
62
+ Requires Redis >= 2.6.12, because it uses the new syntax for SET to easily implement
63
+ the robust algorithm described in the SET command documentation (http://redis.io/commands/set).
59
64
  email:
60
65
  - tomario@gmail.com
61
66
  executables: []
62
67
  extensions: []
63
68
  extra_rdoc_files: []
64
69
  files:
65
- - .gitignore
70
+ - ".gitignore"
71
+ - EXAMPLE_BEER_WAITER.md
72
+ - EXAMPLE_DOG_PILE_EFFECT.md
66
73
  - Gemfile
67
74
  - LICENSE.txt
68
75
  - README.md
@@ -80,19 +87,19 @@ require_paths:
80
87
  - lib
81
88
  required_ruby_version: !ruby/object:Gem::Requirement
82
89
  requirements:
83
- - - '>='
90
+ - - ">="
84
91
  - !ruby/object:Gem::Version
85
92
  version: '0'
86
93
  required_rubygems_version: !ruby/object:Gem::Requirement
87
94
  requirements:
88
- - - '>='
95
+ - - ">="
89
96
  - !ruby/object:Gem::Version
90
97
  version: '0'
91
98
  requirements: []
92
99
  rubyforge_project:
93
- rubygems_version: 2.0.14
100
+ rubygems_version: 2.4.5
94
101
  signing_key:
95
102
  specification_version: 4
96
- summary: Yet another distributed lock for Ruby using Redis.
103
+ summary: Yet another Ruby distributed lock using Redis, with emphasis in transparency.
97
104
  test_files:
98
105
  - spec/redis_lock_spec.rb