redis-semaphore 0.3.0 → 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 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.