bfg-redis-lock 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +80 -0
- data/Rakefile +8 -0
- data/lib/redis-lock.rb +192 -0
- data/lib/redis-lock/version.rb +5 -0
- data/redis-lock.gemspec +26 -0
- data/spec/redis-lock_spec.rb +180 -0
- data/spec/spec_helper.rb +16 -0
- metadata +109 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 BiddingForGood
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Redis::Lock
|
2
|
+
|
3
|
+
Yet another gem for pessimistic locking using Redis.
|
4
|
+
|
5
|
+
The gem uses unique identifiers for the lock values instead of timestamps as described in [the Redis SETNX documentation](http://redis.io/commands/setnx).
|
6
|
+
This avoids any issues with the clocks on the client and server not being exactly in sync. While this shouldn't occur, it does and the implementation described here could fail if the client is more than 1 second out of sync with the server.
|
7
|
+
|
8
|
+
The gem uses a combination of [SETNX](http://redis.io/commands/setnx) and [EXPIRES](http://redis.io/commands/expires) instead of [GETSET](http://redis.io/commands/getset) as described in the lock implementation on the redis site. If a client crashes between issuing the
|
9
|
+
SETNX and the EXPIRES commands, the next client that attempts to get a lock will set the EXPIRES on the lock and wait as normal
|
10
|
+
( just in case there is a legitimate lock in use and something else happened ).
|
11
|
+
|
12
|
+
When attempting to remove the lock, the client verifies that they are still the lock owner before removing it. The gem watches the lock key while
|
13
|
+
attempting to remove the lock to catch the case where the lock expires and is acquired by another client between checking ownership and deleting the lock.
|
14
|
+
If the lock is changed while attempting to remove the lock, the removal process will be tried again.
|
15
|
+
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
gem 'bfg-redis-lock'
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install bfg-redis-lock
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
require 'redis'
|
34
|
+
require 'redis-lock'
|
35
|
+
|
36
|
+
Once required, you can do things like:
|
37
|
+
|
38
|
+
redis = Redis.new
|
39
|
+
redis.lock "my_key" do |lock|
|
40
|
+
# do something while locked
|
41
|
+
end
|
42
|
+
|
43
|
+
The block form above ensures that the lock is released after the block has been executed. Alternatively, you can choose to not provide a block and
|
44
|
+
work directly with the lock:
|
45
|
+
|
46
|
+
redis = Redis.new
|
47
|
+
lock = redis.lock "my_key"
|
48
|
+
|
49
|
+
# do some stuff
|
50
|
+
|
51
|
+
lock.unlock
|
52
|
+
|
53
|
+
If you would like, you can specify a timeout for acquiring the lock as well as the lock duration in seconds. The defaults are 5 seconds for acquiring a lock and 10 seconds for the lock duration:
|
54
|
+
|
55
|
+
redis = Redis.new
|
56
|
+
redis.lock "my_key", :acquire_timeout => 2, :lock_duration => 5 do |lock|
|
57
|
+
# do something
|
58
|
+
end
|
59
|
+
|
60
|
+
If the lock can't be acquired before the timeout, a LockError will be raised:
|
61
|
+
|
62
|
+
redis = Redis.new
|
63
|
+
lock = redis.lock "my_key", :lock_duration => 30
|
64
|
+
redis.lock "my_key", :acquire_timeout => 1 # raises a LockError after one second of attempting to acquire the lock
|
65
|
+
|
66
|
+
You can extend a lock if you are the owner:
|
67
|
+
|
68
|
+
redis = Redis.new
|
69
|
+
lock = redis.lock "my_key"
|
70
|
+
lock.extend_lock 30 # extend lock_duration to be 30 seconds from now
|
71
|
+
|
72
|
+
If you are no longer the lock owner, a LockError will be raised.
|
73
|
+
|
74
|
+
## Contributing
|
75
|
+
|
76
|
+
1. Fork it
|
77
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
78
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
79
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
80
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/redis-lock.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
require "redis"
|
2
|
+
require "redis-lock/version"
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
|
7
|
+
class Lock
|
8
|
+
|
9
|
+
class LockError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :redis
|
13
|
+
attr_reader :id
|
14
|
+
attr_reader :lockname
|
15
|
+
attr_reader :acquire_timeout
|
16
|
+
attr_reader :lock_duration
|
17
|
+
attr_reader :logger
|
18
|
+
attr_accessor :before_delete_callback
|
19
|
+
attr_accessor :before_extend_callback
|
20
|
+
|
21
|
+
def initialize(redis, lock_name, options = {})
|
22
|
+
@redis = redis
|
23
|
+
@lockname = "lock:#{lock_name}"
|
24
|
+
@acquire_timeout = options[:acquire_timeout] || 5
|
25
|
+
@lock_duration = options[:lock_duration] || 10
|
26
|
+
@logger = options[:logger]
|
27
|
+
|
28
|
+
# generate a unique UUID for this lock
|
29
|
+
@id = SecureRandom.uuid
|
30
|
+
end
|
31
|
+
|
32
|
+
def lock(&block)
|
33
|
+
acquire_lock or raise LockError.new(lockname)
|
34
|
+
|
35
|
+
if block
|
36
|
+
begin
|
37
|
+
block.call(self)
|
38
|
+
ensure
|
39
|
+
release_lock
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def unlock
|
47
|
+
release_lock
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def acquire_lock
|
52
|
+
try_until = Time.now + acquire_timeout
|
53
|
+
|
54
|
+
# loop until now + timeout trying to get the lock
|
55
|
+
while Time.now < try_until
|
56
|
+
log :debug, "attempting to acquire lock #{lockname}"
|
57
|
+
|
58
|
+
# try and obtain the lock
|
59
|
+
if redis.setnx(lockname, id)
|
60
|
+
log :info, "lock #{lockname} acquired for #{id}"
|
61
|
+
# lock was obtained, so add an expiration
|
62
|
+
add_expiration
|
63
|
+
return true
|
64
|
+
elsif missing_expiration?
|
65
|
+
# if no expiration, client that obtained lock likely crashed - add an expiration
|
66
|
+
# and wait
|
67
|
+
log :debug, "expiration missing on lock #{lockname}"
|
68
|
+
add_expiration
|
69
|
+
end
|
70
|
+
|
71
|
+
# didn't get the lock, sleep briefly and try again
|
72
|
+
sleep(0.001)
|
73
|
+
end
|
74
|
+
|
75
|
+
# was never able to get the lock - give up
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
|
79
|
+
def extend_lock(extend_by = 10)
|
80
|
+
begin
|
81
|
+
with_watch do
|
82
|
+
if lock_owner?
|
83
|
+
log :debug, "we are the lock owner - extending lock by #{extend_by} seconds"
|
84
|
+
|
85
|
+
# check if we want to do a callback
|
86
|
+
if before_extend_callback
|
87
|
+
log :debug, "calling callback"
|
88
|
+
before_extend_callback.call(redis)
|
89
|
+
end
|
90
|
+
|
91
|
+
redis.multi do |multi|
|
92
|
+
multi.expire lockname, extend_by
|
93
|
+
end
|
94
|
+
|
95
|
+
# we extended the lock, return the lock
|
96
|
+
return self
|
97
|
+
end
|
98
|
+
|
99
|
+
log :debug, "we aren't the lock owner - raising LockError"
|
100
|
+
|
101
|
+
# we aren't the lock owner anymore - raise LockError
|
102
|
+
raise LockError.new("unable to extend #{lockname} - no longer the lock owner")
|
103
|
+
end
|
104
|
+
rescue LockError => e
|
105
|
+
raise e
|
106
|
+
rescue StandardError => e
|
107
|
+
log :warn, "#{lockname} changed while attempting to release key - retrying"
|
108
|
+
# try extending the lock again, just in case
|
109
|
+
extend_lock extend_by
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def release_lock
|
114
|
+
# we are going to watch the lock key while attempting to remove it, so we can
|
115
|
+
# retry removing the lock if the lock is changed while we are removing it.
|
116
|
+
release_with_watch do
|
117
|
+
|
118
|
+
log :debug, "releasing #{lockname}..."
|
119
|
+
|
120
|
+
# make sure we still own the lock
|
121
|
+
if lock_owner?
|
122
|
+
log :debug, "we are the lock owner"
|
123
|
+
|
124
|
+
# check if we want to do a callback
|
125
|
+
if before_delete_callback
|
126
|
+
log :debug, "calling callback"
|
127
|
+
before_delete_callback.call(redis)
|
128
|
+
end
|
129
|
+
|
130
|
+
redis.multi do |multi|
|
131
|
+
multi.del lockname
|
132
|
+
end
|
133
|
+
return true
|
134
|
+
end
|
135
|
+
|
136
|
+
# we weren't the owner of the lock anymore - just return
|
137
|
+
return false
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def locked?
|
143
|
+
lock_owner?
|
144
|
+
end
|
145
|
+
|
146
|
+
def missing_expiration?
|
147
|
+
redis.ttl(lockname) == -1
|
148
|
+
end
|
149
|
+
|
150
|
+
def add_expiration()
|
151
|
+
log :debug, "adding expiration of #{lock_duration} seconds to #{lockname}"
|
152
|
+
redis.expire(lockname, lock_duration)
|
153
|
+
end
|
154
|
+
|
155
|
+
def lock_owner?
|
156
|
+
log :debug, "our id: #{id} - lock owner: #{redis.get(lockname)}"
|
157
|
+
redis.get(lockname) == id
|
158
|
+
end
|
159
|
+
|
160
|
+
def release_with_watch(&block)
|
161
|
+
with_watch do
|
162
|
+
begin
|
163
|
+
block.call
|
164
|
+
rescue => e
|
165
|
+
log :warn, "#{lockname} changed while attempting to release key - retrying"
|
166
|
+
release_with_watch &block
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def with_watch(&block)
|
172
|
+
redis.watch lockname
|
173
|
+
begin
|
174
|
+
block.call
|
175
|
+
ensure
|
176
|
+
redis.unwatch
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def log(level, message)
|
181
|
+
if logger
|
182
|
+
logger.send(level) { message }
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end # Lock class
|
187
|
+
|
188
|
+
def lock(key, options = {}, &block)
|
189
|
+
Lock.new(self, key, options).lock(&block)
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
data/redis-lock.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'redis-lock/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "bfg-redis-lock"
|
8
|
+
gem.version = Redis::Lock::VERSION
|
9
|
+
gem.authors = ["Stuart Garner"]
|
10
|
+
gem.email = ["stuart@biddingforgood.com"]
|
11
|
+
gem.summary = %q{A pessimistic redis lock implementation.'}
|
12
|
+
gem.description = <<-DESC
|
13
|
+
A pessimistic redis lock implementation that doesn't use timestamps, works with the latest redis client, and properly handles removing locks.
|
14
|
+
DESC
|
15
|
+
gem.homepage = "https://github.com/BiddingForGood/redis-lock"
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($/)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
|
22
|
+
gem.add_dependency "redis", "~> 3.0.0"
|
23
|
+
|
24
|
+
gem.add_development_dependency "rake", "~> 0.9.2"
|
25
|
+
gem.add_development_dependency "rspec", "~> 2.12.0"
|
26
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'logger'
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
describe Redis::Lock do
|
6
|
+
|
7
|
+
let(:redis) { Redis.new }
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
redis.del "lock:test"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "responds to lock" do
|
14
|
+
redis.should respond_to(:lock)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can acquire and release a lock" do
|
18
|
+
lock = redis.lock "test"
|
19
|
+
|
20
|
+
redis.get("lock:test").should eq(lock.id)
|
21
|
+
lock.should be_locked
|
22
|
+
|
23
|
+
lock.unlock
|
24
|
+
|
25
|
+
redis.get("lock:test").should be_nil
|
26
|
+
lock.should_not be_locked
|
27
|
+
end
|
28
|
+
|
29
|
+
it "processes a provided block and ensures that the lock is release when completed" do
|
30
|
+
lock = redis.lock "test" do |lock|
|
31
|
+
redis.set "test", "hello"
|
32
|
+
lock.should be_locked
|
33
|
+
end
|
34
|
+
|
35
|
+
redis.get("test").should eq("hello")
|
36
|
+
lock.should_not be_locked
|
37
|
+
end
|
38
|
+
|
39
|
+
it "prevents other clients from obtaining a lock" do
|
40
|
+
lock = redis.lock "test", :lock_duration => 10
|
41
|
+
expect { redis.lock "test", :acquire_timeout => 1 }.to raise_exception
|
42
|
+
lock.unlock
|
43
|
+
end
|
44
|
+
|
45
|
+
it "expires the locks appropriately" do
|
46
|
+
lock = redis.lock "test", :lock_duration => 1
|
47
|
+
sleep(2)
|
48
|
+
lock.should_not be_locked
|
49
|
+
end
|
50
|
+
|
51
|
+
it "handles clients crashing between obtaining a lock and setting the expires" do
|
52
|
+
redis.set "lock:test", "xxx"
|
53
|
+
|
54
|
+
lock = redis.lock("test", :acquire_timeout => 5, :lock_duration => 1)
|
55
|
+
lock.should be_locked
|
56
|
+
|
57
|
+
redis.get("lock:test").should_not eq("xxx")
|
58
|
+
redis.get("lock:test").should eq(lock.id)
|
59
|
+
redis.ttl("lock:test").should_not eq(-1)
|
60
|
+
lock.unlock
|
61
|
+
end
|
62
|
+
|
63
|
+
it "doesn't remove the lock if the lock expires before complete and another client aquires the lock" do
|
64
|
+
lock1 = redis.lock "test", :lock_duration => 1
|
65
|
+
lock2 = redis.lock "test", :acquire_timeout => 3
|
66
|
+
lock1.unlock
|
67
|
+
|
68
|
+
lock1.should_not be_locked
|
69
|
+
redis.get("lock:test").should eq(lock2.id)
|
70
|
+
lock2.should be_locked
|
71
|
+
|
72
|
+
lock2.unlock
|
73
|
+
end
|
74
|
+
|
75
|
+
it "retries removing the lock when another client changes it during delete" do
|
76
|
+
callback = Proc.new do |redis|
|
77
|
+
redis.incr "retry_count"
|
78
|
+
|
79
|
+
# mess with the lock using another client
|
80
|
+
unless redis.get("retry_count").to_i > 1
|
81
|
+
Redis.new.expires "lock:test", 60
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
redis.set "retry_count", 0
|
86
|
+
lock = redis.lock "test"
|
87
|
+
lock.before_delete_callback = callback
|
88
|
+
lock.unlock
|
89
|
+
|
90
|
+
redis.get("retry_count").should eq(2.to_s)
|
91
|
+
lock.should_not be_locked
|
92
|
+
end
|
93
|
+
|
94
|
+
it "doesn't remove the lock when another client changes it" do
|
95
|
+
callback = Proc.new do |redis|
|
96
|
+
redis.incr "retry_count"
|
97
|
+
|
98
|
+
# mess with the lock using another client
|
99
|
+
unless redis.get("retry_count").to_i > 1
|
100
|
+
Redis.new.set "lock:test", "xxx"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
redis.set "retry_count", 0
|
105
|
+
lock = redis.lock "test"
|
106
|
+
lock.before_delete_callback = callback
|
107
|
+
lock.unlock
|
108
|
+
|
109
|
+
redis.get("retry_count").should eq(1.to_s)
|
110
|
+
lock.should_not be_locked
|
111
|
+
redis.get("lock:test").should eq("xxx")
|
112
|
+
|
113
|
+
redis.del("lock:test")
|
114
|
+
end
|
115
|
+
|
116
|
+
it "can extend a lock we own" do
|
117
|
+
lock = redis.lock "test", :lock_duration => 10
|
118
|
+
lock.extend_lock 30
|
119
|
+
|
120
|
+
redis.ttl("lock:test").should eq(30)
|
121
|
+
|
122
|
+
lock.unlock
|
123
|
+
end
|
124
|
+
|
125
|
+
it "can't extend a lock we don't own" do
|
126
|
+
lock1 = redis.lock "test", :lock_duration => 1
|
127
|
+
lock2 = redis.lock "test"
|
128
|
+
|
129
|
+
expect { lock1.extend_lock 30 }.to raise_exception
|
130
|
+
|
131
|
+
lock1.unlock
|
132
|
+
lock2.unlock
|
133
|
+
end
|
134
|
+
|
135
|
+
it "will retry the lock extension if the key changes while we are doing the extension" do
|
136
|
+
callback = Proc.new do |redis|
|
137
|
+
redis.incr "retry_count"
|
138
|
+
|
139
|
+
# mess with the lock using another client
|
140
|
+
unless redis.get("retry_count").to_i > 1
|
141
|
+
Redis.new.expires "lock:test", 60
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
redis.set "retry_count", 0
|
146
|
+
lock = redis.lock "test", :lock_duration => 10
|
147
|
+
lock.before_extend_callback = callback
|
148
|
+
lock.extend_lock 30
|
149
|
+
|
150
|
+
redis.get("retry_count").should eq(2.to_s)
|
151
|
+
lock.should be_locked
|
152
|
+
|
153
|
+
lock.unlock
|
154
|
+
end
|
155
|
+
|
156
|
+
it "can run a lot of times without any conflicts" do
|
157
|
+
redis.set "num_locks", 0
|
158
|
+
threads = []
|
159
|
+
logger = Logger.new(STDOUT)
|
160
|
+
# logger.level = Logger::INFO
|
161
|
+
logger.level = Logger::WARN
|
162
|
+
|
163
|
+
time = Benchmark.realtime do
|
164
|
+
10.times do
|
165
|
+
threads << Thread.new do
|
166
|
+
10.times do
|
167
|
+
Redis.new.lock("test", :lock_duration => 1, :logger => logger) do |lock|
|
168
|
+
lock.redis.incr "num_locks"
|
169
|
+
end
|
170
|
+
sleep(0.1)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
threads.each { |t| t.join }
|
175
|
+
end
|
176
|
+
|
177
|
+
redis.get("num_locks").should eq(100.to_s)
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'redis-lock'
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
8
|
+
config.run_all_when_everything_filtered = true
|
9
|
+
config.filter_run :focus
|
10
|
+
|
11
|
+
# Run specs in random order to surface order dependencies. If you find an
|
12
|
+
# order dependency and want to debug it, you can fix the order by providing
|
13
|
+
# the seed, which is printed after each run.
|
14
|
+
# --seed 1234
|
15
|
+
config.order = 'random'
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bfg-redis-lock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Stuart Garner
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: redis
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.9.2
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.9.2
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.12.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.12.0
|
62
|
+
description: ! ' A pessimistic redis lock implementation that doesn''t use timestamps,
|
63
|
+
works with the latest redis client, and properly handles removing locks.
|
64
|
+
|
65
|
+
'
|
66
|
+
email:
|
67
|
+
- stuart@biddingforgood.com
|
68
|
+
executables: []
|
69
|
+
extensions: []
|
70
|
+
extra_rdoc_files: []
|
71
|
+
files:
|
72
|
+
- .gitignore
|
73
|
+
- .rspec
|
74
|
+
- Gemfile
|
75
|
+
- LICENSE.txt
|
76
|
+
- README.md
|
77
|
+
- Rakefile
|
78
|
+
- lib/redis-lock.rb
|
79
|
+
- lib/redis-lock/version.rb
|
80
|
+
- redis-lock.gemspec
|
81
|
+
- spec/redis-lock_spec.rb
|
82
|
+
- spec/spec_helper.rb
|
83
|
+
homepage: https://github.com/BiddingForGood/redis-lock
|
84
|
+
licenses: []
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.8.23
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: A pessimistic redis lock implementation.'
|
107
|
+
test_files:
|
108
|
+
- spec/redis-lock_spec.rb
|
109
|
+
- spec/spec_helper.rb
|