lock_key 0.1.1 → 0.2.0
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.
- data/README.md +1 -1
- data/lib/lock_key/lock_key.rb +56 -44
- data/lib/lock_key/version.rb +1 -1
- data/spec/lock_key_spec.rb +17 -5
- data/spec/spec_helper.rb +0 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -23,7 +23,7 @@ Or install it yourself as:
|
|
23
23
|
## Usage
|
24
24
|
|
25
25
|
Based on [redis-lock](https://github.com/PatrickTulskie/redis-lock) and the [setnx redis comments](http://redis.io/commands/setnx)
|
26
|
-
LockKey provides basic key
|
26
|
+
LockKey provides basic key level locking.
|
27
27
|
|
28
28
|
## Locking a key
|
29
29
|
|
data/lib/lock_key/lock_key.rb
CHANGED
@@ -27,25 +27,25 @@ class Redis
|
|
27
27
|
:sleep_for => 0.5
|
28
28
|
}
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
def self.value_delimeter; @@value_delimeter; end
|
33
|
-
def self.value_delimeter=(del); @@value_delimeter = del; end
|
30
|
+
VALUE_DELIMETER = "-:-:-"
|
34
31
|
|
35
32
|
def self.defaults=(defaults); @@defaults = @@defaults.merge(defaults); end
|
36
33
|
def self.defaults; @@defaults; end
|
37
34
|
|
38
35
|
# The lock key id for this thread. Uses uuid so that concurrency is not an issue
|
39
|
-
#
|
36
|
+
# with respect to keys
|
40
37
|
def self.lock_key_id; Thread.current[:lock_key_id] ||= UUID_GEN.call; end
|
41
38
|
|
42
39
|
# Locks a key in redis options are same as default.
|
43
40
|
# If a block is given the lock is automatically released
|
44
41
|
# If no block is given, be sure to unlock the key when you're done.
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
42
|
+
#
|
43
|
+
# Note: Concurrency is hard, and deadlock is always a danger. Don't do any
|
44
|
+
# more than is absolutely needed inside the lock so that the lock exists
|
45
|
+
# for the shortest time possible.
|
46
|
+
#
|
47
|
+
# @param key String The Redis key to lock
|
48
|
+
# @param opts Hash The options hash for the lock
|
49
49
|
# @option opts :wait_for Numeric The time to wait for to obtain a lock
|
50
50
|
# @option opts :expire Numeric The time before the lock expires
|
51
51
|
# @option opts :raise Causes a raise if a lock cannot be obtained
|
@@ -62,17 +62,23 @@ class Redis
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def locked_key?(key)
|
65
|
-
|
65
|
+
!!fetch(key)
|
66
66
|
end
|
67
67
|
|
68
68
|
def kill_lock!(key)
|
69
69
|
_redis_.del(lock_key_for(key))
|
70
70
|
end
|
71
71
|
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
72
|
+
# Manually unlocks a key.
|
73
|
+
#
|
74
|
+
# This method is mainly intended for applications where the lock is obtained
|
75
|
+
# in one thread and then passed to another thread to be released. If you
|
76
|
+
# are obtaining and releasing a lock in the same thread, you should prefer
|
77
|
+
# the block form of lock_key over this method.
|
78
|
+
#
|
79
|
+
# @param key String The Redis key to unlock
|
80
|
+
# @param opts Hash An options hash
|
81
|
+
# @option opts :key The value of the key to unlock.
|
76
82
|
#
|
77
83
|
# @example
|
78
84
|
# # Unlock the key if this thread owns it.
|
@@ -88,10 +94,10 @@ class Redis
|
|
88
94
|
# redis.unlock_key "foo", :key => key_value
|
89
95
|
# end
|
90
96
|
def unlock_key(key, opts={})
|
91
|
-
|
92
|
-
value =
|
97
|
+
held_value = opts[:key]
|
98
|
+
value = fetch(key)
|
93
99
|
return true unless value
|
94
|
-
if value ==
|
100
|
+
if value == held_value || i_have_the_lock?(value)
|
95
101
|
kill_lock!(key)
|
96
102
|
true
|
97
103
|
else
|
@@ -104,54 +110,60 @@ class Redis
|
|
104
110
|
self
|
105
111
|
end
|
106
112
|
|
107
|
-
def
|
108
|
-
|
113
|
+
def fetch(key)
|
114
|
+
_redis_.get lock_key_for(key)
|
109
115
|
end
|
110
116
|
|
111
|
-
def
|
112
|
-
"
|
117
|
+
def lock_key_for(key)
|
118
|
+
"lock_key:#{key}"
|
113
119
|
end
|
114
120
|
|
115
|
-
def
|
116
|
-
LockKey.
|
121
|
+
def lock_key_value
|
122
|
+
LockKey.lock_key_id
|
117
123
|
end
|
118
124
|
|
119
125
|
def obtain_lock(key, opts={})
|
120
|
-
|
121
|
-
|
122
|
-
return _value_ if _redis_.setnx(_key_, _value_)
|
126
|
+
new_lock = renew_lock_if_owned(key,opts)
|
127
|
+
return new_lock if new_lock
|
123
128
|
|
124
|
-
got_lock = false
|
125
129
|
wait_until = Time.now + opts[:wait_for]
|
126
130
|
|
127
|
-
until
|
128
|
-
|
129
|
-
if
|
130
|
-
|
131
|
-
|
132
|
-
got_lock = new_lock if i_have_the_lock?(new_lock)
|
133
|
-
elsif i_have_the_lock?(current_lock)
|
134
|
-
got_lock = current_lock
|
131
|
+
until new_lock || Time.now > wait_until
|
132
|
+
_key_ = lock_key_for(key)
|
133
|
+
if _redis_.setnx(_key_, lock_key_value)
|
134
|
+
new_lock = lock_key_value
|
135
|
+
_redis_.expire(_key_, opts[:expire])
|
135
136
|
end
|
136
137
|
sleep opts[:sleep_for]
|
137
138
|
end
|
138
139
|
|
139
|
-
if !
|
140
|
+
if !new_lock && opts[:raise]
|
140
141
|
raise LockAttemptTimeout, "Could not lock #{key}"
|
141
142
|
end
|
142
143
|
|
143
|
-
|
144
|
-
end
|
145
|
-
|
146
|
-
def lock_expired?(lock_value)
|
147
|
-
return true if lock_value.nil?
|
148
|
-
exp = lock_value.split(value_delimeter).first
|
149
|
-
Time.now.to_i > exp.to_i
|
144
|
+
new_lock
|
150
145
|
end
|
151
146
|
|
152
147
|
def i_have_the_lock?(lock_value)
|
153
148
|
return false unless lock_value
|
154
|
-
|
149
|
+
# should just be a straight comparison, but need to roll out
|
150
|
+
# new code that changes things first
|
151
|
+
lock_value.split(VALUE_DELIMETER).last == LockKey.lock_key_id
|
152
|
+
end
|
153
|
+
|
154
|
+
def i_dont_have_the_lock?(lock_value)
|
155
|
+
!i_have_the_lock?(lock_value)
|
156
|
+
end
|
157
|
+
|
158
|
+
def renew_lock_if_owned(key,opts)
|
159
|
+
current_value = fetch(key)
|
160
|
+
return false if current_value && i_dont_have_the_lock?(current_value)
|
161
|
+
_key_ = lock_key_for(key)
|
162
|
+
result = _redis_.multi do
|
163
|
+
_redis_.set(_key_, lock_key_value)
|
164
|
+
_redis_.expire(_key_, opts[:expire])
|
165
|
+
end
|
166
|
+
lock_key_value if result.first == "OK"
|
155
167
|
end
|
156
168
|
end
|
157
169
|
end
|
data/lib/lock_key/version.rb
CHANGED
data/spec/lock_key_spec.rb
CHANGED
@@ -5,10 +5,6 @@ describe "LockKey" do
|
|
5
5
|
REDIS.flushdb
|
6
6
|
end
|
7
7
|
|
8
|
-
after do
|
9
|
-
REDIS.unlock_key "foo"
|
10
|
-
end
|
11
|
-
|
12
8
|
it "takes out a lock" do
|
13
9
|
REDIS.lock_key "foo"
|
14
10
|
REDIS.locked_key?("foo").should be_true
|
@@ -28,12 +24,28 @@ describe "LockKey" do
|
|
28
24
|
REDIS.locked_key?("foo").should be_false
|
29
25
|
end
|
30
26
|
|
27
|
+
it "lets you request the same lock many times" do
|
28
|
+
REDIS.lock_key "foo"
|
29
|
+
expect { REDIS.lock_key "foo" }.to_not raise_error
|
30
|
+
end
|
31
|
+
|
32
|
+
it "raises an error when it can't get a lock" do
|
33
|
+
REDIS.lock_key("foo", :expire => 10)
|
34
|
+
t = Thread.new do
|
35
|
+
expect do
|
36
|
+
REDIS.lock_key("foo", :wait_for => 1) { sleep 1 }
|
37
|
+
end.to raise_error
|
38
|
+
end
|
39
|
+
|
40
|
+
t.join
|
41
|
+
end
|
42
|
+
|
31
43
|
it "handles many threads" do
|
32
44
|
captures = []
|
33
45
|
one = lambda{ REDIS.lock_key("foo", :expire => 5) { sleep 2; captures << :one } }
|
34
46
|
two = lambda{ REDIS.lock_key("foo", :expire => 5) { sleep 1; captures << :two } }
|
35
47
|
three = lambda{ REDIS.lock_key("foo", :expire => 5) { sleep 2; captures << :three } }
|
36
|
-
four = lambda{ REDIS.lock_key("foo", :expire => 1, wait_for: 1) { sleep 2; captures << :four } }
|
48
|
+
four = lambda{ REDIS.lock_key("foo", :expire => 1, wait_for: 1, raise: false) { sleep 2; captures << :four } }
|
37
49
|
|
38
50
|
threads = []
|
39
51
|
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lock_key
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-09 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Uses redis to take out multi-threaded/processed safe locks
|
15
15
|
email:
|