redis-semaphore 0.2.3.1 → 0.2.4

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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NThjZDI5NWY4MjY4Zjc2OWU3ODgxMTUwYjM1NDMyNjEyNjU0ZmY3YQ==
4
+ MjdjZjZhZWZiNmZiYTY1YWJkMWQ0ZDUyYmJjZGY2NGViMzQzZDhmMA==
5
5
  data.tar.gz: !binary |-
6
- MTMzNTlhMjUxZjY3Mzg2ZDAzNWQwMjgxNGUxYzUzNDQ5NTFlMDA3YQ==
6
+ MjI5YzlmMzBhOWE2ZmUwZTQyNjQ0Y2JjM2ZkMzZhOWEzMTc0YWZkOA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MTRiMjNkYTdlYWQ3OTkxY2MwYTI0NjIyNzcyM2U0NjQ3MTRhNWRhM2VmNDNk
10
- MDJkZDUzODE5NWQ5ZjE4YWM0NmVhMzNjMDllOTZiMGFlNjA5YWJjNmMwNWVl
11
- Yjk2NTVjN2IzYjUxN2QzZTA3MjBmYmZjMzk2YWFlODViZmYyNjA=
9
+ YzZjOTA0ZWQ2OWUzMzk2YTYyODA1ZWIzYjBiZmU1MTc5Yjg4MTk0MDZjYWYz
10
+ YjZiZTZlZTBmYzQ0MWFlNjFlNzFiODVjOTc4YjAyOWRiODUwYWY4MjNiNmYy
11
+ ZDAyZDEwOTZjZjk3ODVmMWIyNmJkNWZiYmEyMzM4ZDNjYmU2MTI=
12
12
  data.tar.gz: !binary |-
13
- YzdmYjMzMzlhMDZjZGZjNTVjZGUwNWRlMTk4N2EzYTlkZWFkMjU5ZWIzY2I1
14
- ZGVlYjM1N2QwYzg3YTFiMDFkYzE3YzM0YTY2OGJmYTgzYTI3Njg0YjEzYmY2
15
- YTQ5ZDQ2YjY3MWRjNjIzZmIyYmNlMDNkMDQ5OWM0MmNjNzhmNjQ=
13
+ MTdhN2RjYTRjMDIxMGJhNTM4YWM0YTViYzNjM2FjYmJmMjBmYjgwZGZjMDE0
14
+ MGNiZmM3YjI3OTJiNmU5OTAyZTZhNWVmOGFlNzQyZjMxNTYwZmIzMDY2NGU3
15
+ NTA4NTBkMGNiZjRhNmQzZjEyNjM4ZjMwNzFjYTI4YzgxZWFiYzg=
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- [![Code Climate](https://codeclimate.com/github/dv/redis-semaphore.png)](https://codeclimate.com/github/dv/redis-semaphore)
1
+ [![Code Climate](https://codeclimate.com/github/dv/redis-semaphore.svg?branch=master)](https://codeclimate.com/github/dv/redis-semaphore)
2
+ [![Build Status](https://travis-ci.org/dv/redis-semaphore.svg?branch=master)](https://travis-ci.org/dv/redis-semaphore)
2
3
 
3
4
  redis-semaphore
4
5
  ===============
@@ -7,7 +8,7 @@ Implements a mutex and semaphore using Redis and the neat BLPOP command.
7
8
 
8
9
  The mutex and semaphore is blocking, not polling, and has a fair queue serving processes on a first-come, first-serve basis. It can also have an optional timeout after which a lock is unlocked automatically, to protect against dead clients.
9
10
 
10
- For more info see [Wikipedia](http://en.wikipedia.org/wiki/Semaphore_(programming\)).
11
+ For more info see [Wikipedia](http://en.wikipedia.org/wiki/Semaphore_(programming)).
11
12
 
12
13
  Usage
13
14
  -----
@@ -15,7 +16,7 @@ Usage
15
16
  Create a mutex:
16
17
 
17
18
  ```ruby
18
- s = Redis::Semaphore.new(:semaphore_name, :connection => "localhost")
19
+ s = Redis::Semaphore.new(:semaphore_name, :host => "localhost")
19
20
  s.lock do
20
21
  # We're now in a mutex protected area
21
22
  # No matter how many processes are running this program,
@@ -29,7 +30,7 @@ While our application is inside the code block given to ```s.lock```, other call
29
30
  You can also allow a set number of processes inside the semaphore-protected block, in case you have a well-defined number of resources available:
30
31
 
31
32
  ```ruby
32
- s = Redis::Semaphore.new(:semaphore_name, :resources => 5, :connection => "localhost")
33
+ s = Redis::Semaphore.new(:semaphore_name, :resources => 5, :host => "localhost")
33
34
  s.lock do
34
35
  # Up to five processes at a time will be able to get inside this code
35
36
  # block simultaneously.
@@ -40,7 +41,7 @@ end
40
41
  You're not obligated to use code blocks, linear calls work just fine:
41
42
 
42
43
  ```ruby
43
- s = Redis::Semaphore.new(:semaphore_name, :connection => "localhost")
44
+ s = Redis::Semaphore.new(:semaphore_name, :host => "localhost")
44
45
  s.lock
45
46
  work
46
47
  s.unlock # Don't forget this, or the mutex will stay locked!
@@ -75,7 +76,7 @@ sem.available_count # also returns 1
75
76
  In the constructor you can pass in any arguments that you would pass to a regular Redis constructor. You can even pass in your custom Redis client:
76
77
 
77
78
  ```ruby
78
- r = Redis.new(:connection => "localhost", :db => 222)
79
+ r = Redis.new(:host => "localhost", :db => 222)
79
80
  s = Redis::Semaphore.new(:another_name, :redis => r)
80
81
  #...
81
82
  ```
@@ -107,10 +108,10 @@ s = Redis::Semaphore.new(:stale_semaphore, :redis = r, :stale_client_timeout =>
107
108
  Or you could start a different thread or program that frequently checks for stale locks. This has the advantage of unblocking blocking calls to Semaphore#lock as well:
108
109
 
109
110
  ```ruby
110
- normal_sem = Redis::Semaphore.new(:semaphore, :connection => "localhost")
111
+ normal_sem = Redis::Semaphore.new(:semaphore, :host => "localhost")
111
112
 
112
113
  Thread.new do
113
- watchdog = Redis::Semaphore.new(:semaphore, :connection => "localhost", :stale_client_timeout => 5)
114
+ watchdog = Redis::Semaphore.new(:semaphore, :host => "localhost", :stale_client_timeout => 5)
114
115
 
115
116
  while(true) do
116
117
  watchdog.release_stale_locks!
@@ -175,6 +176,16 @@ s = Redis::Semaphore.new(:local_semaphore, :redis = r, :stale_client_timeout =>
175
176
  Redis servers earlier than version 2.6 don't support the TIME command. In that case we fall back to using the local time automatically.
176
177
 
177
178
 
179
+ ### Expiration
180
+
181
+ ```redis-semaphore``` supports an expiration option, which will call the **EXPIRE** Redis command on all related keys (except for `grabbed_keys`), to make sure that after a while all evidence of the semaphore will disappear and your Redis server will not be cluttered with unused keys. Pass in the expiration timeout in seconds:
182
+
183
+ ```ruby
184
+ s = Redis::Semaphore.new(:local_semaphore, :redis = r, :expiration => 100)
185
+ ```
186
+
187
+ This option should only be used if you know what you're doing. If you chose a wrong expiration timeout then the semaphore might disappear in the middle of a critical section. For most situations just using the `delete!` command should suffice to remove all semaphore keys from the server after you're done using the semaphore.
188
+
178
189
  Installation
179
190
  ------------
180
191
 
@@ -189,6 +200,10 @@ Testing
189
200
  Changelog
190
201
  ---------
191
202
 
203
+ ###0.2.4 January 11, 2015
204
+ - Fix bug with TIME and redis-namespace (thanks sos4nt!).
205
+ - Add expiration option (thanks jcalvert!).
206
+
192
207
  ###0.2.3 September 7, 2014
193
208
  - Block-based locking return the value of the block (thanks frobcode!).
194
209
 
@@ -237,7 +252,7 @@ Author
237
252
  Contributors
238
253
  ------------
239
254
 
240
- Thanks to these awesome peeps for their contributions:
255
+ Thanks to these awesome people for their contributions:
241
256
 
242
257
  - [Rimas Silkaitis](https://github.com/neovintage)
243
258
  - [Tim Galeckas](https://github.com/timgaleckas)
@@ -248,3 +263,7 @@ Thanks to these awesome peeps for their contributions:
248
263
  - [presskey](https://github.com/presskey)
249
264
  - [Stephen Bussey](https://github.com/sb8244)
250
265
  - [frobcode](https://github.com/frobcode)
266
+ - [Petteri Räty](https://github.com/betelgeuse)
267
+ - [Stefan Schüßler](https://github.com/sos4nt)
268
+ - [Jonathan Calvert](https://github.com/jcalvert)
269
+
@@ -2,19 +2,21 @@ require 'redis'
2
2
 
3
3
  class Redis
4
4
  class Semaphore
5
+ EXISTS_TOKEN = "1"
5
6
  API_VERSION = "1"
6
7
 
7
- #stale_client_timeout is the threshold of time before we assume
8
- #that something has gone terribly wrong with a client and we
9
- #invalidate it's lock.
8
+ # stale_client_timeout is the threshold of time before we assume
9
+ # that something has gone terribly wrong with a client and we
10
+ # invalidate it's lock.
10
11
  # Default is nil for which we don't check for stale clients
11
12
  # Redis::Semaphore.new(:my_semaphore, :stale_client_timeout => 30, :redis => myRedis)
12
13
  # Redis::Semaphore.new(:my_semaphore, :redis => myRedis)
13
14
  # Redis::Semaphore.new(:my_semaphore, :resources => 1, :redis => myRedis)
14
- # Redis::Semaphore.new(:my_semaphore, :connection => "", :port => "")
15
+ # Redis::Semaphore.new(:my_semaphore, :host => "", :port => "")
15
16
  # Redis::Semaphore.new(:my_semaphore, :path => "bla")
16
17
  def initialize(name, opts = {})
17
18
  @name = name
19
+ @expiration = opts.delete(:expiration)
18
20
  @resource_count = opts.delete(:resources) || 1
19
21
  @stale_client_timeout = opts.delete(:stale_client_timeout)
20
22
  @redis = opts.delete(:redis) || Redis.new(opts)
@@ -23,13 +25,19 @@ class Redis
23
25
  end
24
26
 
25
27
  def exists_or_create!
26
- token = @redis.getset(exists_key, API_VERSION)
28
+ token = @redis.getset(exists_key, EXISTS_TOKEN)
27
29
 
28
30
  if token.nil?
29
31
  create!
30
- elsif token != API_VERSION
31
- raise "Semaphore exists but running as wrong version (version #{token} vs #{API_VERSION})."
32
32
  else
33
+ # Previous versions of redis-semaphore did not set `version_key`.
34
+ # Make sure it's set now, so we can use it in future versions.
35
+
36
+ if token == API_VERSION && @redis.get(version_key).nil?
37
+ @redis.set(version_key, API_VERSION)
38
+ end
39
+
40
+ set_expiration_if_necessary
33
41
  true
34
42
  end
35
43
  end
@@ -42,6 +50,7 @@ class Redis
42
50
  @redis.del(available_key)
43
51
  @redis.del(grabbed_key)
44
52
  @redis.del(exists_key)
53
+ @redis.del(version_key)
45
54
  end
46
55
 
47
56
  def lock(timeout = 0)
@@ -80,7 +89,7 @@ class Redis
80
89
  @tokens.each do |token|
81
90
  return true if locked?(token)
82
91
  end
83
-
92
+
84
93
  false
85
94
  end
86
95
  end
@@ -127,6 +136,7 @@ class Redis
127
136
  end
128
137
 
129
138
  private
139
+
130
140
  def simple_mutex(key_name, expires = nil)
131
141
  key_name = namespaced_key(key_name) if key_name.kind_of? Symbol
132
142
  token = @redis.getset(key_name, API_VERSION)
@@ -150,10 +160,18 @@ class Redis
150
160
  @resource_count.times do |index|
151
161
  @redis.rpush(available_key, index)
152
162
  end
163
+ @redis.set(version_key, API_VERSION)
164
+ @redis.persist(exists_key)
153
165
 
154
- # Persist key
155
- @redis.del(exists_key)
156
- @redis.set(exists_key, API_VERSION)
166
+ set_expiration_if_necessary
167
+ end
168
+ end
169
+
170
+ def set_expiration_if_necessary
171
+ if @expiration
172
+ [available_key, exists_key, version_key].each do |key|
173
+ @redis.expire(key, @expiration)
174
+ end
157
175
  end
158
176
  end
159
177
 
@@ -185,12 +203,16 @@ class Redis
185
203
  @grabbed_key ||= namespaced_key('GRABBED')
186
204
  end
187
205
 
206
+ def version_key
207
+ @version_key ||= namespaced_key('VERSION')
208
+ end
209
+
188
210
  def current_time
189
211
  if @use_local_time
190
212
  Time.now
191
213
  else
192
214
  begin
193
- instant = @redis.time
215
+ instant = redis_namespace? ? @redis.redis.time : @redis.time
194
216
  Time.at(instant[0], instant[1])
195
217
  rescue
196
218
  @use_local_time = true
@@ -112,6 +112,29 @@ describe "redis" do
112
112
  end
113
113
  expect(block_value).to eq(lock_token)
114
114
  end
115
+
116
+ it "should disappear without a trace when calling `delete!`" do
117
+ original_key_size = @redis.keys.count
118
+
119
+ semaphore.exists_or_create!
120
+ semaphore.delete!
121
+
122
+ expect(@redis.keys.count).to eq(original_key_size)
123
+ end
124
+ end
125
+
126
+ describe "semaphore with expiration" do
127
+ let(:semaphore) { Redis::Semaphore.new(:my_semaphore, :redis => @redis, :expiration => 2) }
128
+ let(:multisem) { Redis::Semaphore.new(:my_semaphore_2, :resources => 2, :redis => @redis, :expiration => 2) }
129
+
130
+ it_behaves_like "a semaphore"
131
+
132
+ it "expires keys" do
133
+ original_key_size = @redis.keys.count
134
+ semaphore.exists_or_create!
135
+ sleep 3.0
136
+ expect(@redis.keys.count).to eq(original_key_size)
137
+ end
115
138
  end
116
139
 
117
140
  describe "semaphore without staleness checking" do
@@ -201,4 +224,23 @@ describe "redis" do
201
224
  expect(available_keys).to eq(grabbed_keys)
202
225
  end
203
226
  end
227
+
228
+ describe "version" do
229
+ context "with an existing versionless semaphore" do
230
+ let(:old_sem) { Redis::Semaphore.new(:my_semaphore, :redis => @redis) }
231
+ let(:semaphore) { Redis::Semaphore.new(:my_semaphore, :redis => @redis) }
232
+ let(:version_key) { old_sem.send(:version_key) }
233
+
234
+ before do
235
+ old_sem.exists_or_create!
236
+ @redis.del(version_key)
237
+ end
238
+
239
+ it "sets the version key" do
240
+ semaphore.exists_or_create!
241
+ expect(@redis.get(version_key)).not_to be_nil
242
+ end
243
+ end
244
+ end
245
+
204
246
  end
@@ -1,11 +1,6 @@
1
1
  require 'rubygems'
2
- require 'bundler'
3
- Bundler.setup(:default, :test)
4
- Bundler.require(:default, :test)
5
-
6
- require 'rspec'
7
- require 'redis'
8
- require 'logger'
2
+ require 'bundler/setup'
3
+ Bundler.require(:development)
9
4
 
10
5
  $TESTING=true
11
6
  $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
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.2.3.1
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Verhasselt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-07 00:00:00.000000000 Z
11
+ date: 2015-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -24,6 +24,62 @@ dependencies:
24
24
  - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '2.14'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '2.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: timecop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
27
83
  description: ! 'Implements a distributed semaphore or mutex using Redis.
28
84
 
29
85
  '