redis-semaphore 0.2.3.1 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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
  '