redis_locks 0.0.1 → 0.0.2
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 +4 -4
- data/README.md +11 -6
- data/lib/redis_locks/mutex.rb +20 -11
- data/lib/redis_locks/semaphore.rb +1 -1
- data/lib/redis_locks/token_bucket.rb +1 -1
- data/lib/redis_locks/version.rb +1 -1
- data/lib/redis_locks.rb +10 -0
- data/spec/redis_locks/mutex_spec.rb +13 -5
- data/spec/redis_locks/semaphore_spec.rb +0 -2
- data/spec/redis_locks/token_bucket_spec.rb +0 -1
- data/spec/spec_helper.rb +5 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e975e1077350b297e9a2e29dd2c1874ee36015cd
|
|
4
|
+
data.tar.gz: 43c25d4b9302e7530cbbfcaa12c9f75f81ad51b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 836660edb41f89794435460298714126fb583cfe95655c6d915061a2d42660412309804724fcf2c2749be0b59daa20bb446e6014d499ef8b85f058394b6c3363
|
|
7
|
+
data.tar.gz: f3ca402e401fbfc4a2cdac4167dc714ba327a7d58eb36d7b101fa875d2e77946d15c2876c757fb1523164e06a57c791e5df7e23b6d6da483e692d4b114569aff
|
data/README.md
CHANGED
|
@@ -16,7 +16,9 @@ A simple mutex using `setnx`.
|
|
|
16
16
|
require 'redis'
|
|
17
17
|
require 'redis_locks'
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
RedisLocks.redis = Redis.new
|
|
20
|
+
|
|
21
|
+
lock = RedisLocks::Mutex.new('my_key')
|
|
20
22
|
|
|
21
23
|
# high-level use
|
|
22
24
|
lock.lock! do
|
|
@@ -29,9 +31,8 @@ A simple mutex using `setnx`.
|
|
|
29
31
|
lock.unlock # now lock can be acquired again
|
|
30
32
|
```
|
|
31
33
|
|
|
32
|
-
Supports lock expiry via `expires_in
|
|
33
|
-
By default, locks expire after 24 hours.
|
|
34
|
-
|
|
34
|
+
Supports lock expiry via an `expires_in` argument to the constructor or
|
|
35
|
+
`expires_at` argument to `lock`/`lock!`. By default, locks expire after 24 hours.
|
|
35
36
|
|
|
36
37
|
## RedisLocks::Semaphore
|
|
37
38
|
|
|
@@ -43,7 +44,9 @@ Supports multiple resources, waits to acquire a resource, and timeouts.
|
|
|
43
44
|
require 'redis'
|
|
44
45
|
require 'redis_locks'
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
RedisLocks.redis = Redis.new
|
|
48
|
+
|
|
49
|
+
semaphore = RedisLocks::Semaphore.new('my_key', resources: 2)
|
|
47
50
|
|
|
48
51
|
# high-level use
|
|
49
52
|
semaphore.lock! do
|
|
@@ -75,8 +78,10 @@ A [token-bucket](https://en.wikipedia.org/wiki/Token_bucket) rate limiter implem
|
|
|
75
78
|
require 'redis'
|
|
76
79
|
require 'redis_locks'
|
|
77
80
|
|
|
81
|
+
RedisLocks.redis = Redis.new
|
|
82
|
+
|
|
78
83
|
# allows up to two calls to `take`/`take!` every five seconds
|
|
79
|
-
limiter = RedisLocks::TokenBucket.new('my_key',
|
|
84
|
+
limiter = RedisLocks::TokenBucket.new('my_key', period: 5, number: 2)
|
|
80
85
|
|
|
81
86
|
2.times { limiter.take! }
|
|
82
87
|
|
data/lib/redis_locks/mutex.rb
CHANGED
|
@@ -9,23 +9,26 @@ module RedisLocks
|
|
|
9
9
|
|
|
10
10
|
NAMESPACE = "mutex"
|
|
11
11
|
|
|
12
|
-
def initialize(key,
|
|
12
|
+
def initialize(key, expires_in: 86400, redis: RedisLocks.redis)
|
|
13
13
|
@key = "#{NAMESPACE}:#{key}"
|
|
14
14
|
@redis = redis
|
|
15
|
-
@
|
|
15
|
+
@expires_in = expires_in.to_i
|
|
16
|
+
|
|
17
|
+
raise ArgumentError.new("Invalid expires_in: #{expires_in}") unless expires_in > 0
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
def lock(&block)
|
|
20
|
+
def lock(expires_at: nil, &block)
|
|
19
21
|
now = Time.now.utc.to_i
|
|
20
22
|
locked = false
|
|
23
|
+
expires_at ||= now + @expires_in
|
|
21
24
|
|
|
22
|
-
if @redis.setnx(@key,
|
|
23
|
-
@redis.expire(@key,
|
|
25
|
+
if @redis.setnx(@key, expires_at)
|
|
26
|
+
@redis.expire(@key, expires_at - now)
|
|
24
27
|
locked = true
|
|
25
28
|
else # it was locked
|
|
26
29
|
if (old_value = @redis.get(@key)).to_i <= now
|
|
27
30
|
# lock has expired
|
|
28
|
-
if @redis.
|
|
31
|
+
if @redis.getset(@key, expires_at) == old_value
|
|
29
32
|
locked = true
|
|
30
33
|
end
|
|
31
34
|
end
|
|
@@ -36,16 +39,22 @@ module RedisLocks
|
|
|
36
39
|
return_or_yield(&block)
|
|
37
40
|
end
|
|
38
41
|
|
|
39
|
-
def lock!(&block)
|
|
40
|
-
locked = lock
|
|
42
|
+
def lock!(expires_at: nil, &block)
|
|
43
|
+
locked = lock(expires_at: expires_at)
|
|
41
44
|
raise AlreadyLocked.new(@key) unless locked
|
|
42
45
|
return_or_yield(&block)
|
|
43
46
|
end
|
|
44
47
|
|
|
45
|
-
# only delete the key if it's still valid, and will be for another 2 seconds
|
|
46
48
|
def unlock
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
@redis.watch(@key) do
|
|
50
|
+
# only delete the key if it's still valid, and will be for another 2 seconds
|
|
51
|
+
if @redis.get(@key).to_i > Time.now.utc.to_i + 2
|
|
52
|
+
@redis.multi do |multi|
|
|
53
|
+
multi.del(@key)
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
@redis.unwatch
|
|
57
|
+
end
|
|
49
58
|
end
|
|
50
59
|
end
|
|
51
60
|
|
|
@@ -62,7 +62,7 @@ module RedisLocks
|
|
|
62
62
|
#
|
|
63
63
|
# `stale_client_timeout` is the threshold of time before we assume that
|
|
64
64
|
# something has gone terribly wrong with a client and we invalidate its lock.
|
|
65
|
-
def initialize(key,
|
|
65
|
+
def initialize(key, resources: 1, stale_client_timeout: 86400, redis: RedisLocks.redis)
|
|
66
66
|
@key = key
|
|
67
67
|
@resource_count = resources.to_i
|
|
68
68
|
@stale_client_timeout = stale_client_timeout.to_f
|
|
@@ -41,7 +41,7 @@ module RedisLocks
|
|
|
41
41
|
# max of `number` tokens being available). Each time a resource is used, a
|
|
42
42
|
# token is removed from the bucket; if no tokens are available, no resource
|
|
43
43
|
# may be used.
|
|
44
|
-
def initialize(key,
|
|
44
|
+
def initialize(key, period: 1, number: 1, redis: RedisLocks.redis)
|
|
45
45
|
@key = "#{NAMESPACE}:#{key}".freeze
|
|
46
46
|
@rps = number.to_f / period.to_i
|
|
47
47
|
@burst = number.to_i
|
data/lib/redis_locks/version.rb
CHANGED
data/lib/redis_locks.rb
CHANGED
|
@@ -2,10 +2,7 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
describe RedisLocks::Mutex do
|
|
4
4
|
let(:mutex) {
|
|
5
|
-
RedisLocks::Mutex.new(
|
|
6
|
-
'testmutex',
|
|
7
|
-
redis: $redis
|
|
8
|
-
)
|
|
5
|
+
RedisLocks::Mutex.new('testmutex')
|
|
9
6
|
}
|
|
10
7
|
|
|
11
8
|
it 'allows locking' do
|
|
@@ -31,7 +28,7 @@ describe RedisLocks::Mutex do
|
|
|
31
28
|
|
|
32
29
|
context 'when locked' do
|
|
33
30
|
before do
|
|
34
|
-
|
|
31
|
+
mutex.lock!
|
|
35
32
|
end
|
|
36
33
|
|
|
37
34
|
it 'does not allow locking again' do
|
|
@@ -58,4 +55,15 @@ describe RedisLocks::Mutex do
|
|
|
58
55
|
end
|
|
59
56
|
end
|
|
60
57
|
end
|
|
58
|
+
|
|
59
|
+
context 'when locked but expired' do
|
|
60
|
+
before do
|
|
61
|
+
mutex.lock!(expires_at: Time.now.utc.to_i+1)
|
|
62
|
+
sleep(2)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'allows lock' do
|
|
66
|
+
expect(mutex.lock).to be_truthy
|
|
67
|
+
end
|
|
68
|
+
end
|
|
61
69
|
end
|
|
@@ -5,7 +5,6 @@ describe RedisLocks::Semaphore do
|
|
|
5
5
|
let(:semaphore) {
|
|
6
6
|
RedisLocks::Semaphore.new(
|
|
7
7
|
'testsemaphore',
|
|
8
|
-
redis: $redis,
|
|
9
8
|
resources: 1
|
|
10
9
|
)
|
|
11
10
|
}
|
|
@@ -88,7 +87,6 @@ describe RedisLocks::Semaphore do
|
|
|
88
87
|
let(:semaphore) {
|
|
89
88
|
RedisLocks::Semaphore.new(
|
|
90
89
|
'testsem',
|
|
91
|
-
redis: $redis,
|
|
92
90
|
resources: 2
|
|
93
91
|
)
|
|
94
92
|
}
|
data/spec/spec_helper.rb
CHANGED
|
@@ -5,12 +5,14 @@ require 'redis'
|
|
|
5
5
|
require 'redis_locks'
|
|
6
6
|
require 'thread'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
redis = Redis.new(db: ENV['REDIS_LOCKS_SPEC_DB'] || 15)
|
|
9
9
|
|
|
10
|
-
raise "#{
|
|
10
|
+
raise "#{redis.inspect} is non-empty!" if redis.keys.any?
|
|
11
|
+
|
|
12
|
+
RedisLocks.redis = redis
|
|
11
13
|
|
|
12
14
|
RSpec.configure do |config|
|
|
13
15
|
config.after(:each) do
|
|
14
|
-
|
|
16
|
+
redis.flushdb
|
|
15
17
|
end
|
|
16
18
|
end
|