redis-semaphore 0.2.4 → 0.3.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 +5 -13
- data/README.md +37 -44
- data/lib/redis/semaphore.rb +44 -14
- data/spec/semaphore_spec.rb +66 -2
- data/spec/spec_helper.rb +5 -0
- metadata +23 -25
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
MjI5YzlmMzBhOWE2ZmUwZTQyNjQ0Y2JjM2ZkMzZhOWEzMTc0YWZkOA==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8b9f39ef8423ccca320813836891182abf1d93ec
|
4
|
+
data.tar.gz: f631ca62601208fe26125808c049abe755de3ad9
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
YjZiZTZlZTBmYzQ0MWFlNjFlNzFiODVjOTc4YjAyOWRiODUwYWY4MjNiNmYy
|
11
|
-
ZDAyZDEwOTZjZjk3ODVmMWIyNmJkNWZiYmEyMzM4ZDNjYmU2MTI=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
MTdhN2RjYTRjMDIxMGJhNTM4YWM0YTViYzNjM2FjYmJmMjBmYjgwZGZjMDE0
|
14
|
-
MGNiZmM3YjI3OTJiNmU5OTAyZTZhNWVmOGFlNzQyZjMxNTYwZmIzMDY2NGU3
|
15
|
-
NTA4NTBkMGNiZjRhNmQzZjEyNjM4ZjMwNzFjYTI4YzgxZWFiYzg=
|
6
|
+
metadata.gz: 3c38139829552918fbc64bef11059fd3ac8a8117d6f25bbda7bfac2d355c791232d9a2b9c5458cc173d53b8d210b0aa01efd61e8c7aff6e1a5dc22253a6c1070
|
7
|
+
data.tar.gz: 0be29fe2f67e984c6e2757c0da6df45eba3ec8a97702cae4a623e185407e347f0642f624338ec66213c012e9f26220c71f4155e26cc1b9bd8a65ba65381689f9
|
data/README.md
CHANGED
@@ -10,6 +10,23 @@ The mutex and semaphore is blocking, not polling, and has a fair queue serving p
|
|
10
10
|
|
11
11
|
For more info see [Wikipedia](http://en.wikipedia.org/wiki/Semaphore_(programming)).
|
12
12
|
|
13
|
+
Important change in v0.3.0
|
14
|
+
===========================
|
15
|
+
|
16
|
+
If you've been using `redis-semaphore` before version `0.3.0` you should be aware that the interface for `lock` has changed slightly. Before `0.3` calling `semaphore.lock(0)` (with `0` as the timeout) would block the semaphore indefinitely, just like a redis `blpop` command would.
|
17
|
+
|
18
|
+
This has changed in `0.3` to mean *do not block at all*. You can still omit the argument entirely, or pass in `nil` to get the old functionality back. Examples:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
# These block indefinitely until a resource becomes available:
|
22
|
+
semaphore.lock
|
23
|
+
semaphore.lock(nil)
|
24
|
+
|
25
|
+
# This does not block at all and rather returns immediately if there's no
|
26
|
+
# resource available:
|
27
|
+
semaphore.lock(0)
|
28
|
+
```
|
29
|
+
|
13
30
|
Usage
|
14
31
|
-----
|
15
32
|
|
@@ -81,6 +98,8 @@ s = Redis::Semaphore.new(:another_name, :redis => r)
|
|
81
98
|
#...
|
82
99
|
```
|
83
100
|
|
101
|
+
Note that it's [a bad idea to reuse the same redis client across threads](https://github.com/dv/redis-semaphore/issues/18), due to the blocking nature of the `blpop` command. We might add support for this in a future version.
|
102
|
+
|
84
103
|
If an exception happens during a lock, the lock will automatically be released:
|
85
104
|
|
86
105
|
```ruby
|
@@ -200,54 +219,21 @@ Testing
|
|
200
219
|
Changelog
|
201
220
|
---------
|
202
221
|
|
222
|
+
###0.3.1 April 17, 2016 (Pending)
|
223
|
+
- Fix `sem.lock(0)` bug (thanks eugenk!).
|
224
|
+
- Fix `release_stale_locks!` deadlock bug (thanks mfischer-zd for the bug-report!).
|
225
|
+
|
226
|
+
###0.3.0 January 24, 2016
|
227
|
+
- Change API to include non-blocking option for `#lock` (thanks tomclose!).
|
228
|
+
- Fix unwanted persisting of `available_key` (thanks dany1468!).
|
229
|
+
- Fix `available_count` returning 0 for nonexisting semaphores (thanks mikeryz!).
|
230
|
+
|
203
231
|
###0.2.4 January 11, 2015
|
204
232
|
- Fix bug with TIME and redis-namespace (thanks sos4nt!).
|
205
233
|
- Add expiration option (thanks jcalvert!).
|
234
|
+
- Update API version logic.
|
206
235
|
|
207
|
-
|
208
|
-
- Block-based locking return the value of the block (thanks frobcode!).
|
209
|
-
|
210
|
-
###0.2.2 June 16, 2014
|
211
|
-
- Fixed bug in `all_tokens` (thanks presskey!).
|
212
|
-
- Fixed bug in error message (thanks Dmitriy!).
|
213
|
-
|
214
|
-
###0.2.1 August 6, 2013
|
215
|
-
- Remove dependency on Redis 2.6+ using fallback for TIME command (thanks dubdromic!).
|
216
|
-
- Add ```:use_local_time``` option
|
217
|
-
|
218
|
-
###0.2.0 June 2, 2013
|
219
|
-
- Use Redis TIME command for lock timeouts (thanks dubdromic!).
|
220
|
-
- Version increase because of new dependency on Redis 2.6+
|
221
|
-
|
222
|
-
###0.1.7 April 18, 2013
|
223
|
-
- Fix bug where ```release_stale_locks!``` was not public (thanks scomma!).
|
224
|
-
|
225
|
-
###0.1.6 March 31, 2013
|
226
|
-
- Add non-ownership of tokens
|
227
|
-
- Add stale client timeout (thanks timgaleckas!).
|
228
|
-
|
229
|
-
###0.1.5 October 1, 2012
|
230
|
-
- Add detection of Redis::Namespace definition to avoid potential bug (thanks ruud!).
|
231
|
-
|
232
|
-
###0.1.4 October 1, 2012
|
233
|
-
- Fixed empty namespaces (thanks ruurd!).
|
234
|
-
|
235
|
-
###0.1.3 July 9, 2012
|
236
|
-
- Tokens are now identifiable (thanks timgaleckas!).
|
237
|
-
|
238
|
-
###0.1.2 June 1, 2012
|
239
|
-
- Add redis-namespace support (thanks neovintage!).
|
240
|
-
|
241
|
-
### 0.1.1 September 17, 2011
|
242
|
-
- When an exception is raised during locked period, ensure it unlocks.
|
243
|
-
|
244
|
-
### 0.1.0 August 4, 2011
|
245
|
-
- Initial release.
|
246
|
-
|
247
|
-
Author
|
248
|
-
------
|
249
|
-
|
250
|
-
[David Verhasselt](http://davidverhasselt.com) - david@crowdway.com
|
236
|
+
More in [CHANGELOG](CHANGELOG.md).
|
251
237
|
|
252
238
|
Contributors
|
253
239
|
------------
|
@@ -266,4 +252,11 @@ Thanks to these awesome people for their contributions:
|
|
266
252
|
- [Petteri Räty](https://github.com/betelgeuse)
|
267
253
|
- [Stefan Schüßler](https://github.com/sos4nt)
|
268
254
|
- [Jonathan Calvert](https://github.com/jcalvert)
|
255
|
+
- [mikeryz](https://github.com/mikeryz)
|
256
|
+
- [tomclose](https://github.com/tomclose)
|
257
|
+
- [Eugen Kuksa](https://github.com/eugenk)
|
258
|
+
- [Eugene Kenny](https://github.com/eugeneius)
|
259
|
+
|
260
|
+
### "Merge"-button clicker
|
269
261
|
|
262
|
+
[David Verhasselt](http://davidverhasselt.com) - david@crowdway.com
|
data/lib/redis/semaphore.rb
CHANGED
@@ -37,13 +37,16 @@ class Redis
|
|
37
37
|
@redis.set(version_key, API_VERSION)
|
38
38
|
end
|
39
39
|
|
40
|
-
set_expiration_if_necessary
|
41
40
|
true
|
42
41
|
end
|
43
42
|
end
|
44
43
|
|
45
44
|
def available_count
|
46
|
-
|
45
|
+
if exists?
|
46
|
+
@redis.llen(available_key)
|
47
|
+
else
|
48
|
+
@resource_count
|
49
|
+
end
|
47
50
|
end
|
48
51
|
|
49
52
|
def delete!
|
@@ -53,14 +56,19 @@ class Redis
|
|
53
56
|
@redis.del(version_key)
|
54
57
|
end
|
55
58
|
|
56
|
-
def lock(timeout =
|
59
|
+
def lock(timeout = nil)
|
57
60
|
exists_or_create!
|
58
61
|
release_stale_locks! if check_staleness?
|
59
62
|
|
60
|
-
|
61
|
-
|
63
|
+
if timeout.nil? || timeout > 0
|
64
|
+
# passing timeout 0 to blpop causes it to block
|
65
|
+
_key, current_token = @redis.blpop(available_key, timeout || 0)
|
66
|
+
else
|
67
|
+
current_token = @redis.lpop(available_key)
|
68
|
+
end
|
69
|
+
|
70
|
+
return false if current_token.nil?
|
62
71
|
|
63
|
-
current_token = token_pair[1]
|
64
72
|
@tokens.push(current_token)
|
65
73
|
@redis.hset(grabbed_key, current_token, current_time.to_f)
|
66
74
|
return_value = current_token
|
@@ -100,6 +108,8 @@ class Redis
|
|
100
108
|
@redis.multi do
|
101
109
|
@redis.hdel grabbed_key, token
|
102
110
|
@redis.lpush available_key, token
|
111
|
+
|
112
|
+
set_expiration_if_necessary
|
103
113
|
end
|
104
114
|
end
|
105
115
|
|
@@ -124,7 +134,7 @@ class Redis
|
|
124
134
|
end
|
125
135
|
|
126
136
|
def release_stale_locks!
|
127
|
-
|
137
|
+
simple_expiring_mutex(:release_locks, 10) do
|
128
138
|
@redis.hgetall(grabbed_key).each do |token, locked_at|
|
129
139
|
timed_out_at = locked_at.to_f + @stale_client_timeout
|
130
140
|
|
@@ -137,17 +147,37 @@ class Redis
|
|
137
147
|
|
138
148
|
private
|
139
149
|
|
140
|
-
def
|
141
|
-
|
142
|
-
|
150
|
+
def simple_expiring_mutex(key_name, expires_in)
|
151
|
+
# Using the locking mechanism as described in
|
152
|
+
# http://redis.io/commands/setnx
|
153
|
+
|
154
|
+
key_name = namespaced_key(key_name)
|
155
|
+
cached_current_time = current_time.to_f
|
156
|
+
my_lock_expires_at = cached_current_time + expires_in + 1
|
157
|
+
|
158
|
+
got_lock = @redis.setnx(key_name, my_lock_expires_at)
|
159
|
+
|
160
|
+
if !got_lock
|
161
|
+
# Check if expired
|
162
|
+
other_lock_expires_at = @redis.get(key_name).to_f
|
163
|
+
|
164
|
+
if other_lock_expires_at < cached_current_time
|
165
|
+
old_expires_at = @redis.getset(key_name, my_lock_expires_at).to_f
|
166
|
+
|
167
|
+
# Check if another client started cleanup yet. If not,
|
168
|
+
# then we now have the lock.
|
169
|
+
got_lock = (old_expires_at == other_lock_expires_at)
|
170
|
+
end
|
171
|
+
end
|
143
172
|
|
144
|
-
return false
|
145
|
-
@redis.expire(key_name, expires) unless expires.nil?
|
173
|
+
return false if !got_lock
|
146
174
|
|
147
175
|
begin
|
148
|
-
yield
|
176
|
+
yield
|
149
177
|
ensure
|
150
|
-
|
178
|
+
# Make sure not to delete the lock in case someone else already expired
|
179
|
+
# our lock, with one second in between to account for some lag.
|
180
|
+
@redis.del(key_name) if my_lock_expires_at > (current_time.to_f - 1)
|
151
181
|
end
|
152
182
|
end
|
153
183
|
|
data/spec/semaphore_spec.rb
CHANGED
@@ -22,6 +22,10 @@ describe "redis" do
|
|
22
22
|
expect(semaphore.available_count).to eq(1)
|
23
23
|
end
|
24
24
|
|
25
|
+
it "has the correct amount of available resources before locking" do
|
26
|
+
expect(semaphore.available_count).to eq(1)
|
27
|
+
end
|
28
|
+
|
25
29
|
it "should not exist from the start" do
|
26
30
|
expect(semaphore.exists?).to eq(false)
|
27
31
|
semaphore.lock
|
@@ -89,9 +93,9 @@ describe "redis" do
|
|
89
93
|
it "should not leave the semaphore locked after raising an exception" do
|
90
94
|
expect {
|
91
95
|
semaphore.lock(1) do
|
92
|
-
raise Exception
|
96
|
+
raise Exception, "redis semaphore exception"
|
93
97
|
end
|
94
|
-
}.to raise_error
|
98
|
+
}.to raise_error(Exception, "redis semaphore exception")
|
95
99
|
|
96
100
|
expect(semaphore.locked?).to eq(false)
|
97
101
|
end
|
@@ -121,6 +125,24 @@ describe "redis" do
|
|
121
125
|
|
122
126
|
expect(@redis.keys.count).to eq(original_key_size)
|
123
127
|
end
|
128
|
+
|
129
|
+
it "should not block when the timeout is zero" do
|
130
|
+
did_we_get_in = false
|
131
|
+
|
132
|
+
semaphore.lock do
|
133
|
+
semaphore.lock(0) do
|
134
|
+
did_we_get_in = true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
expect(did_we_get_in).to be false
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should be locked when the timeout is zero" do
|
142
|
+
semaphore.lock(0) do
|
143
|
+
expect(semaphore.locked?).to be true
|
144
|
+
end
|
145
|
+
end
|
124
146
|
end
|
125
147
|
|
126
148
|
describe "semaphore with expiration" do
|
@@ -135,6 +157,15 @@ describe "redis" do
|
|
135
157
|
sleep 3.0
|
136
158
|
expect(@redis.keys.count).to eq(original_key_size)
|
137
159
|
end
|
160
|
+
|
161
|
+
it "expires keys after unlocking" do
|
162
|
+
original_key_size = @redis.keys.count
|
163
|
+
semaphore.lock do
|
164
|
+
# noop
|
165
|
+
end
|
166
|
+
sleep 3.0
|
167
|
+
expect(@redis.keys.count).to eq(original_key_size)
|
168
|
+
end
|
138
169
|
end
|
139
170
|
|
140
171
|
describe "semaphore without staleness checking" do
|
@@ -243,4 +274,37 @@ describe "redis" do
|
|
243
274
|
end
|
244
275
|
end
|
245
276
|
|
277
|
+
# Private method tests, do not use
|
278
|
+
describe "simple_expiring_mutex" do
|
279
|
+
let(:semaphore) { Redis::Semaphore.new(:my_semaphore, :redis => @redis) }
|
280
|
+
|
281
|
+
before do
|
282
|
+
semaphore.class.send(:public, :simple_expiring_mutex)
|
283
|
+
end
|
284
|
+
|
285
|
+
it "gracefully expires stale lock" do
|
286
|
+
expiration = 1
|
287
|
+
|
288
|
+
thread =
|
289
|
+
Thread.new do
|
290
|
+
semaphore.simple_expiring_mutex(:test, expiration) do
|
291
|
+
sleep 3
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
sleep 1.5
|
296
|
+
|
297
|
+
expect(semaphore.simple_expiring_mutex(:test, expiration)).to be_falsy
|
298
|
+
|
299
|
+
sleep expiration
|
300
|
+
|
301
|
+
it_worked = false
|
302
|
+
semaphore.simple_expiring_mutex(:test, expiration) do
|
303
|
+
it_worked = true
|
304
|
+
end
|
305
|
+
|
306
|
+
expect(it_worked).to be_truthy
|
307
|
+
thread.join
|
308
|
+
end
|
309
|
+
end
|
246
310
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,98 +1,97 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-semaphore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Verhasselt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-05-14 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
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - "<"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '11'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - "<"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '11'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '2.14'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '2.14'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: timecop
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: pry
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
description:
|
84
|
-
|
85
|
-
'
|
83
|
+
description: |
|
84
|
+
Implements a distributed semaphore or mutex using Redis.
|
86
85
|
email: david@crowdway.com
|
87
86
|
executables: []
|
88
87
|
extensions: []
|
89
88
|
extra_rdoc_files: []
|
90
89
|
files:
|
90
|
+
- LICENSE
|
91
91
|
- README.md
|
92
92
|
- Rakefile
|
93
|
-
- LICENSE
|
94
|
-
- lib/redis/semaphore.rb
|
95
93
|
- lib/redis-semaphore.rb
|
94
|
+
- lib/redis/semaphore.rb
|
96
95
|
- spec/semaphore_spec.rb
|
97
96
|
- spec/spec_helper.rb
|
98
97
|
homepage: http://github.com/dv/redis-semaphore
|
@@ -105,19 +104,18 @@ require_paths:
|
|
105
104
|
- lib
|
106
105
|
required_ruby_version: !ruby/object:Gem::Requirement
|
107
106
|
requirements:
|
108
|
-
- -
|
107
|
+
- - ">="
|
109
108
|
- !ruby/object:Gem::Version
|
110
109
|
version: '0'
|
111
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
111
|
requirements:
|
113
|
-
- -
|
112
|
+
- - ">="
|
114
113
|
- !ruby/object:Gem::Version
|
115
114
|
version: '0'
|
116
115
|
requirements: []
|
117
116
|
rubyforge_project:
|
118
|
-
rubygems_version: 2.1
|
117
|
+
rubygems_version: 2.4.5.1
|
119
118
|
signing_key:
|
120
119
|
specification_version: 4
|
121
120
|
summary: Implements a distributed semaphore or mutex using Redis.
|
122
121
|
test_files: []
|
123
|
-
has_rdoc:
|