pmckee11-redis-lock 0.1.0 → 0.1.1
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +29 -3
- data/lib/redis-lock.rb +6 -1
- data/lib/redis-lock/version.rb +1 -1
- data/redis-lock.gemspec +1 -1
- data/spec/redis-lock_spec.rb +53 -29
- metadata +11 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 290df5759f6a3511b52c04a4399e72c6f8fa63b3
|
4
|
+
data.tar.gz: 7d51790da0d3841fb80054146adae29e8c4a601c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16ea49364c09328c26190ec9b99dd7a0fe56342df44eec8d3e6c5dda99663577510f04ed12fbbe2199026d8b5d897a90562217bb16c46d90944a5a36ea8b57bb
|
7
|
+
data.tar.gz: 41f0d9710dcdb9acb844e8220bfae98a0751c46e6247f581e93e68bce5ea69e4c815658d45c6a05395af70103c9a28915fb75f3562439310e33e20619424187e
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -13,11 +13,37 @@ and then run bundler.
|
|
13
13
|
Or run
|
14
14
|
|
15
15
|
$ gem install pmckee11-redis-lock
|
16
|
-
|
16
|
+
|
17
17
|
## Background
|
18
18
|
|
19
19
|
This implements a distributed lock with a timeout almost exactly as described in the redis documentation.
|
20
|
-
There are a few other redis lock implementations in ruby, but none of them seemed to be using the newer features in redis that can yield a performance improvement (e.g. the expanded SET parameters and Lua scripting). Using this gem requires a redis 2.6.12 server, allowing it to leverage those newer features to make fewer round trips and provide better performance
|
20
|
+
There are a few other redis lock implementations in ruby, but none of them seemed to be using the newer features in redis that can yield a performance improvement (e.g. the expanded SET parameters and Lua scripting). Using this gem requires a redis 2.6.12 or newer server, allowing it to leverage those newer features to make fewer round trips and provide better performance
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Create an instance of `Redis::Lock` with the desired redis connection and parameters. `auto_release_time` is the lock TTL in seconds (defaults to 10) and `base_sleep` is the amount of time in milliseconds to sleep after the first failure to acquire a lock (defaults to 100ms). Successive failures to acquire a lock result in exponential back off to prevent wasted cycles:
|
25
|
+
|
26
|
+
redis = Redis.new
|
27
|
+
my_lock = Redis::Lock.new(redis,
|
28
|
+
"my-lock-key",
|
29
|
+
:auto_release_time => LOCK_TTL_IN_SECS,
|
30
|
+
:base_sleep => SLEEP_IN_MS)
|
31
|
+
|
32
|
+
Once you have a lock, you can manually lock and unlock or pass a block to lock only around your code:
|
33
|
+
|
34
|
+
my_lock.lock
|
35
|
+
# Do stuff
|
36
|
+
my_lock.unlock
|
37
|
+
|
38
|
+
my_lock.do |lock|
|
39
|
+
# Do stuff
|
40
|
+
end
|
41
|
+
|
42
|
+
You can also configure the maximum amount of time in seconds to block on acquiring a lock (defaults to 10):
|
43
|
+
|
44
|
+
my_lock(5).do |lock|
|
45
|
+
# Do stuff
|
46
|
+
end
|
21
47
|
|
22
48
|
## Contributing
|
23
49
|
|
@@ -25,4 +51,4 @@ There are a few other redis lock implementations in ruby, but none of them seeme
|
|
25
51
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
26
52
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
27
53
|
4. Push to the branch (`git push origin my-new-feature`)
|
28
|
-
5. Create new Pull Request
|
54
|
+
5. Create new Pull Request
|
data/lib/redis-lock.rb
CHANGED
@@ -55,12 +55,17 @@ class Redis
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
# @return Boolean that is true if the lock is currently held by any process
|
59
|
+
def locked?
|
60
|
+
return !@redis.get(@key).nil?
|
61
|
+
end
|
62
|
+
|
58
63
|
# Determines whether or not the lock is held by this instance. By default, this method relies on the expiration time
|
59
64
|
# of the key as a performance optimization when possible. If this is undesirable for some reason, set force_remote
|
60
65
|
# to true.
|
61
66
|
# @param force_remote Boolean for whether to verify with a call to the redis server instead of using the lock time
|
62
67
|
# @return Boolean that is true if this lock instance currently holds the lock
|
63
|
-
def
|
68
|
+
def locked_by_me?(force_remote = false)
|
64
69
|
if @time_locked
|
65
70
|
if force_remote
|
66
71
|
return @redis.get(@key) == @instance_name
|
data/lib/redis-lock/version.rb
CHANGED
data/redis-lock.gemspec
CHANGED
data/spec/redis-lock_spec.rb
CHANGED
@@ -23,16 +23,18 @@ describe Redis::Lock do
|
|
23
23
|
context "#lock" do
|
24
24
|
it "should set an appropriate key in redis" do
|
25
25
|
lock.lock
|
26
|
-
@redis.get("lock:#{key}").
|
26
|
+
expect(@redis.get("lock:#{key}")).not_to be_nil
|
27
27
|
end
|
28
28
|
|
29
29
|
context "when a block is provided" do
|
30
30
|
it "locks before yielding and releases after" do
|
31
|
+
expect(lock).to receive(:test_message)
|
31
32
|
lock.lock do |l|
|
32
|
-
l.
|
33
|
-
|
33
|
+
expect(l).to eq(lock)
|
34
|
+
l.test_message
|
35
|
+
expect(@redis.get("lock:#{key}")).not_to be_nil
|
34
36
|
end
|
35
|
-
@redis.get("lock:#{key}").
|
37
|
+
expect(@redis.get("lock:#{key}")).to be_nil
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
@@ -45,8 +47,8 @@ describe Redis::Lock do
|
|
45
47
|
other_lock.lock(2)
|
46
48
|
fail()
|
47
49
|
rescue => e
|
48
|
-
(Time.now - time).
|
49
|
-
e.
|
50
|
+
expect(Time.now - time).to be_within(0.1).of(2.0)
|
51
|
+
expect(e).to be_a(Redis::Lock::AcquireLockTimeOut)
|
50
52
|
end
|
51
53
|
end
|
52
54
|
end
|
@@ -54,7 +56,7 @@ describe Redis::Lock do
|
|
54
56
|
context "when initialized with auto_release_time" do
|
55
57
|
it "sets the redis key with an appropriate expiration" do
|
56
58
|
other_lock = Redis::Lock.new(@redis, key, :auto_release_time => 7)
|
57
|
-
@redis.
|
59
|
+
expect(@redis).to receive(:set).with("lock:#{key}", an_instance_of(String), :nx => true, :ex => 7).and_return(true)
|
58
60
|
other_lock.lock
|
59
61
|
end
|
60
62
|
end
|
@@ -62,12 +64,12 @@ describe Redis::Lock do
|
|
62
64
|
context "when initialized with base_sleep" do
|
63
65
|
it "retries with exponential back off starting at base_sleep millis" do
|
64
66
|
other_lock = Redis::Lock.new(@redis, key, :base_sleep => 25)
|
65
|
-
other_lock.
|
66
|
-
other_lock.
|
67
|
-
other_lock.
|
68
|
-
other_lock.
|
69
|
-
other_lock.
|
70
|
-
sleep_time.
|
67
|
+
expect(other_lock).to receive(:sleep).with(0.025).ordered
|
68
|
+
expect(other_lock).to receive(:sleep).with(0.05).ordered
|
69
|
+
expect(other_lock).to receive(:sleep).with(0.1).ordered
|
70
|
+
expect(other_lock).to receive(:sleep).with(0.2).ordered
|
71
|
+
expect(other_lock).to receive(:sleep) do |sleep_time|
|
72
|
+
expect(sleep_time).to eq(0.4)
|
71
73
|
lock.unlock
|
72
74
|
end.ordered
|
73
75
|
lock.lock
|
@@ -80,7 +82,7 @@ describe Redis::Lock do
|
|
80
82
|
it "should delete an appropriate key from redis" do
|
81
83
|
lock.lock
|
82
84
|
lock.unlock
|
83
|
-
@redis.get("lock:#{key}").
|
85
|
+
expect(@redis.get("lock:#{key}")).to be_nil
|
84
86
|
end
|
85
87
|
|
86
88
|
it "should not delete a lock held by another instance" do
|
@@ -88,13 +90,13 @@ describe Redis::Lock do
|
|
88
90
|
other_lock.lock
|
89
91
|
sleep(1.1)
|
90
92
|
lock.lock
|
91
|
-
other_lock.unlock
|
92
|
-
@redis.get("lock:#{key}").
|
93
|
+
other_lock.unlock(true)
|
94
|
+
expect(@redis.get("lock:#{key}")).not_to be_nil
|
93
95
|
end
|
94
96
|
|
95
97
|
context "when the instance has not been locked" do
|
96
98
|
it "is a no op" do
|
97
|
-
@redis.
|
99
|
+
expect(@redis).not_to receive(:eval)
|
98
100
|
lock.unlock
|
99
101
|
end
|
100
102
|
end
|
@@ -104,7 +106,7 @@ describe Redis::Lock do
|
|
104
106
|
other_lock = Redis::Lock.new(@redis, key, :auto_release_time => 1)
|
105
107
|
other_lock.lock
|
106
108
|
sleep(1)
|
107
|
-
@redis.
|
109
|
+
expect(@redis).not_to receive(:eval)
|
108
110
|
other_lock.unlock
|
109
111
|
end
|
110
112
|
|
@@ -113,7 +115,7 @@ describe Redis::Lock do
|
|
113
115
|
other_lock = Redis::Lock.new(@redis, key, :auto_release_time => 1)
|
114
116
|
other_lock.lock
|
115
117
|
sleep(1)
|
116
|
-
@redis.
|
118
|
+
expect(@redis).to receive(:eval).with(Redis::Lock::UNLOCK_LUA_SCRIPT, ["lock:#{key}"], instance_of(Array)).once
|
117
119
|
other_lock.unlock(true)
|
118
120
|
end
|
119
121
|
end
|
@@ -121,18 +123,40 @@ describe Redis::Lock do
|
|
121
123
|
end
|
122
124
|
|
123
125
|
context "#locked?" do
|
126
|
+
context "when the lock is held" do
|
127
|
+
before :each do
|
128
|
+
lock.lock
|
129
|
+
end
|
130
|
+
|
131
|
+
after :each do
|
132
|
+
lock.unlock
|
133
|
+
end
|
134
|
+
|
135
|
+
it "returns true" do
|
136
|
+
expect(lock.locked?).to be_truthy
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "when the lock is not held" do
|
141
|
+
it "returns false" do
|
142
|
+
expect(lock.locked?).to be_falsey
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "#locked_by_me?" do
|
124
148
|
it "correctly determines if the instance holds the lock" do
|
125
|
-
lock.
|
149
|
+
expect(lock.locked_by_me?).to be_falsey
|
126
150
|
lock.lock
|
127
|
-
lock.
|
151
|
+
expect(lock.locked_by_me?).to be_truthy
|
128
152
|
lock.unlock
|
129
|
-
lock.
|
153
|
+
expect(lock.locked_by_me?).to be_falsey
|
130
154
|
end
|
131
155
|
|
132
156
|
context "when the instance has not been locked" do
|
133
157
|
it "is a no op" do
|
134
|
-
@redis.
|
135
|
-
lock.
|
158
|
+
expect(@redis).not_to receive(:get)
|
159
|
+
lock.locked_by_me?
|
136
160
|
end
|
137
161
|
end
|
138
162
|
|
@@ -141,8 +165,8 @@ describe Redis::Lock do
|
|
141
165
|
other_lock = Redis::Lock.new(@redis, key, :auto_release_time => 1)
|
142
166
|
other_lock.lock
|
143
167
|
sleep(1)
|
144
|
-
@redis.
|
145
|
-
other_lock.
|
168
|
+
expect(@redis).not_to receive(:get)
|
169
|
+
expect(other_lock.locked_by_me?).to be_falsey
|
146
170
|
end
|
147
171
|
|
148
172
|
context "but force_remote is true" do
|
@@ -150,11 +174,11 @@ describe Redis::Lock do
|
|
150
174
|
other_lock = Redis::Lock.new(@redis, key, :auto_release_time => 1)
|
151
175
|
other_lock.lock
|
152
176
|
sleep(1)
|
153
|
-
@redis.
|
154
|
-
other_lock.
|
177
|
+
expect(@redis).to receive(:get).once.and_return(nil)
|
178
|
+
expect(other_lock.locked_by_me?(true)).to be_falsey
|
155
179
|
end
|
156
180
|
end
|
157
181
|
end
|
158
182
|
end
|
159
183
|
|
160
|
-
end
|
184
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pmckee11-redis-lock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter McKee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -34,16 +34,22 @@ dependencies:
|
|
34
34
|
name: rspec
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.0'
|
37
40
|
- - ">="
|
38
41
|
- !ruby/object:Gem::Version
|
39
|
-
version: '0'
|
42
|
+
version: '3.0'
|
40
43
|
type: :development
|
41
44
|
prerelease: false
|
42
45
|
version_requirements: !ruby/object:Gem::Requirement
|
43
46
|
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '3.0'
|
44
50
|
- - ">="
|
45
51
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0'
|
52
|
+
version: '3.0'
|
47
53
|
description: Distributed lock using ruby redis
|
48
54
|
email:
|
49
55
|
- pmckee11@gmail.com
|
@@ -82,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
88
|
version: '0'
|
83
89
|
requirements: []
|
84
90
|
rubyforge_project:
|
85
|
-
rubygems_version: 2.
|
91
|
+
rubygems_version: 2.4.5
|
86
92
|
signing_key:
|
87
93
|
specification_version: 4
|
88
94
|
summary: Distributed lock using ruby redis
|