redis-semaphore 0.3.0 → 0.3.1

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: 309187f46b275cd806075cad1bbe6957dac9b81d
4
- data.tar.gz: 290e95937ffc62ca1026be42ad33d2b9017be559
3
+ metadata.gz: 8b9f39ef8423ccca320813836891182abf1d93ec
4
+ data.tar.gz: f631ca62601208fe26125808c049abe755de3ad9
5
5
  SHA512:
6
- metadata.gz: bc1eefa40196e5e95f8c4f74258b8136eb8179262a784bff743c5729a17631de3fbbd7b13b888eaffd43b7b63de8fa3209ea226d3009d66de9a18467742be594
7
- data.tar.gz: 8c69c42bfb18035f63fb15d94cbd92593344a2e08dee9883ddde9598e534dcef0227a6d51542496cea6582ac9c1751d7fe830965e5134e62502c5a7c886c0ca7
6
+ metadata.gz: 3c38139829552918fbc64bef11059fd3ac8a8117d6f25bbda7bfac2d355c791232d9a2b9c5458cc173d53b8d210b0aa01efd61e8c7aff6e1a5dc22253a6c1070
7
+ data.tar.gz: 0be29fe2f67e984c6e2757c0da6df45eba3ec8a97702cae4a623e185407e347f0642f624338ec66213c012e9f26220c71f4155e26cc1b9bd8a65ba65381689f9
data/README.md CHANGED
@@ -10,8 +10,8 @@ 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
- Changes for 0.3.0
14
- =================
13
+ Important change in v0.3.0
14
+ ===========================
15
15
 
16
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
17
 
@@ -98,6 +98,8 @@ s = Redis::Semaphore.new(:another_name, :redis => r)
98
98
  #...
99
99
  ```
100
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
+
101
103
  If an exception happens during a lock, the lock will automatically be released:
102
104
 
103
105
  ```ruby
@@ -217,6 +219,10 @@ Testing
217
219
  Changelog
218
220
  ---------
219
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
+
220
226
  ###0.3.0 January 24, 2016
221
227
  - Change API to include non-blocking option for `#lock` (thanks tomclose!).
222
228
  - Fix unwanted persisting of `available_key` (thanks dany1468!).
@@ -227,50 +233,7 @@ Changelog
227
233
  - Add expiration option (thanks jcalvert!).
228
234
  - Update API version logic.
229
235
 
230
- ###0.2.3 September 7, 2014
231
- - Block-based locking return the value of the block (thanks frobcode!).
232
-
233
- ###0.2.2 June 16, 2014
234
- - Fixed bug in `all_tokens` (thanks presskey!).
235
- - Fixed bug in error message (thanks Dmitriy!).
236
-
237
- ###0.2.1 August 6, 2013
238
- - Remove dependency on Redis 2.6+ using fallback for TIME command (thanks dubdromic!).
239
- - Add ```:use_local_time``` option
240
-
241
- ###0.2.0 June 2, 2013
242
- - Use Redis TIME command for lock timeouts (thanks dubdromic!).
243
- - Version increase because of new dependency on Redis 2.6+
244
-
245
- ###0.1.7 April 18, 2013
246
- - Fix bug where ```release_stale_locks!``` was not public (thanks scomma!).
247
-
248
- ###0.1.6 March 31, 2013
249
- - Add non-ownership of tokens
250
- - Add stale client timeout (thanks timgaleckas!).
251
-
252
- ###0.1.5 October 1, 2012
253
- - Add detection of Redis::Namespace definition to avoid potential bug (thanks ruud!).
254
-
255
- ###0.1.4 October 1, 2012
256
- - Fixed empty namespaces (thanks ruurd!).
257
-
258
- ###0.1.3 July 9, 2012
259
- - Tokens are now identifiable (thanks timgaleckas!).
260
-
261
- ###0.1.2 June 1, 2012
262
- - Add redis-namespace support (thanks neovintage!).
263
-
264
- ### 0.1.1 September 17, 2011
265
- - When an exception is raised during locked period, ensure it unlocks.
266
-
267
- ### 0.1.0 August 4, 2011
268
- - Initial release.
269
-
270
- Author
271
- ------
272
-
273
- [David Verhasselt](http://davidverhasselt.com) - david@crowdway.com
236
+ More in [CHANGELOG](CHANGELOG.md).
274
237
 
275
238
  Contributors
276
239
  ------------
@@ -291,3 +254,9 @@ Thanks to these awesome people for their contributions:
291
254
  - [Jonathan Calvert](https://github.com/jcalvert)
292
255
  - [mikeryz](https://github.com/mikeryz)
293
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
261
+
262
+ [David Verhasselt](http://davidverhasselt.com) - david@crowdway.com
@@ -62,14 +62,13 @@ class Redis
62
62
 
63
63
  if timeout.nil? || timeout > 0
64
64
  # passing timeout 0 to blpop causes it to block
65
- token_pair = @redis.blpop(available_key, timeout || 0)
65
+ _key, current_token = @redis.blpop(available_key, timeout || 0)
66
66
  else
67
- token_pair = @redis.lpop(available_key)
67
+ current_token = @redis.lpop(available_key)
68
68
  end
69
69
 
70
- return false if token_pair.nil?
70
+ return false if current_token.nil?
71
71
 
72
- current_token = token_pair[1]
73
72
  @tokens.push(current_token)
74
73
  @redis.hset(grabbed_key, current_token, current_time.to_f)
75
74
  return_value = current_token
@@ -135,7 +134,7 @@ class Redis
135
134
  end
136
135
 
137
136
  def release_stale_locks!
138
- simple_mutex(:release_locks, 10) do
137
+ simple_expiring_mutex(:release_locks, 10) do
139
138
  @redis.hgetall(grabbed_key).each do |token, locked_at|
140
139
  timed_out_at = locked_at.to_f + @stale_client_timeout
141
140
 
@@ -148,17 +147,37 @@ class Redis
148
147
 
149
148
  private
150
149
 
151
- def simple_mutex(key_name, expires = nil)
152
- key_name = namespaced_key(key_name) if key_name.kind_of? Symbol
153
- token = @redis.getset(key_name, API_VERSION)
150
+ def simple_expiring_mutex(key_name, expires_in)
151
+ # Using the locking mechanism as described in
152
+ # http://redis.io/commands/setnx
154
153
 
155
- return false unless token.nil?
156
- @redis.expire(key_name, expires) unless expires.nil?
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
172
+
173
+ return false if !got_lock
157
174
 
158
175
  begin
159
- yield token
176
+ yield
160
177
  ensure
161
- @redis.del(key_name)
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)
162
181
  end
163
182
  end
164
183
 
@@ -137,6 +137,12 @@ describe "redis" do
137
137
 
138
138
  expect(did_we_get_in).to be false
139
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
140
146
  end
141
147
 
142
148
  describe "semaphore with expiration" do
@@ -268,4 +274,37 @@ describe "redis" do
268
274
  end
269
275
  end
270
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
271
310
  end
@@ -5,3 +5,8 @@ Bundler.require(:development)
5
5
  $TESTING=true
6
6
  $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
7
7
  require 'redis/semaphore'
8
+
9
+ RSpec.configure do |c|
10
+ c.filter_run focus: true
11
+ c.run_all_when_everything_filtered = true
12
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-semaphore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.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: 2016-01-24 00:00:00.000000000 Z
11
+ date: 2016-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "<"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
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: '0'
40
+ version: '11'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.14'
55
55
  - !ruby/object:Gem::Dependency
56
- name: pry
56
+ name: timecop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: timecop
70
+ name: pry
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -114,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
114
  version: '0'
115
115
  requirements: []
116
116
  rubyforge_project:
117
- rubygems_version: 2.4.5
117
+ rubygems_version: 2.4.5.1
118
118
  signing_key:
119
119
  specification_version: 4
120
120
  summary: Implements a distributed semaphore or mutex using Redis.