redis_locks 0.0.3 → 0.1.0
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/Gemfile.lock +3 -1
- data/README.md +10 -0
- data/lib/redis_locks.rb +2 -1
- data/lib/redis_locks/connections.rb +15 -0
- data/lib/redis_locks/evalsha_or_eval.rb +3 -3
- data/lib/redis_locks/mutex.rb +14 -12
- data/lib/redis_locks/semaphore.rb +33 -21
- data/lib/redis_locks/token_bucket.rb +18 -10
- data/lib/redis_locks/version.rb +1 -1
- data/redis_locks.gemspec +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d50bb61032babb4eb127f5f4cf8a4b8804d84c0
|
4
|
+
data.tar.gz: 658610bbd101c4bce5004415c08517a44b23dd35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b512b7584dc46896361d1c622a1c183ba6efc3ec5c22144e1162e614f47bc0bbe0c7e9e93bcb8323a1103982ceb26cd97df538d0f375b92a1b66eef13f675a60
|
7
|
+
data.tar.gz: 461ba90c3336dc56296053475a24141a1f0454e6cbb96cfe2d16c590eee7b7080147559a369a5713020f2af334495b7c7bb522737358930bffcc90242cca7619
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
redis_locks (0.0
|
4
|
+
redis_locks (0.1.0)
|
5
|
+
connection_pool
|
5
6
|
redis
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
9
10
|
specs:
|
10
11
|
coderay (1.1.1)
|
12
|
+
connection_pool (2.2.0)
|
11
13
|
diff-lcs (1.2.5)
|
12
14
|
method_source (0.8.2)
|
13
15
|
pry (0.10.3)
|
data/README.md
CHANGED
@@ -92,3 +92,13 @@ A [token-bucket](https://en.wikipedia.org/wiki/Token_bucket) rate limiter implem
|
|
92
92
|
|
93
93
|
limiter.take # true
|
94
94
|
```
|
95
|
+
|
96
|
+
# Configuration
|
97
|
+
|
98
|
+
We use a connection pool, because a connection shared among multiple threads
|
99
|
+
will result in contention when using blocking Redis operations.
|
100
|
+
|
101
|
+
`RedisLocks.redis=` and the `redis:` option in the initializer for each utility
|
102
|
+
can each be passed either a plain Redis object or an object responding to `with`
|
103
|
+
(which will be expected to accept a block and yield a Redis connection, e.g.
|
104
|
+
an instance of [ConnectionPool](https://github.com/mperham/connection_pool)).
|
data/lib/redis_locks.rb
CHANGED
@@ -4,11 +4,12 @@ require 'redis_locks/evalsha_or_eval'
|
|
4
4
|
require 'redis_locks/mutex'
|
5
5
|
require 'redis_locks/semaphore'
|
6
6
|
require 'redis_locks/token_bucket'
|
7
|
+
require 'redis_locks/connections'
|
7
8
|
|
8
9
|
module RedisLocks
|
9
10
|
|
10
11
|
def self.redis=(redis)
|
11
|
-
@redis = redis
|
12
|
+
@redis = Connections.ensure_pool(redis)
|
12
13
|
end
|
13
14
|
|
14
15
|
def self.redis
|
@@ -2,11 +2,11 @@ module RedisLocks
|
|
2
2
|
|
3
3
|
# This ensures that each Lua script is evaluated at most once; after it has
|
4
4
|
# been evaluated, we will be able to call it just by passing its digest.
|
5
|
-
def self.evalsha_or_eval(
|
6
|
-
|
5
|
+
def self.evalsha_or_eval(conn:, script:, digest:, keys: [], args: [])
|
6
|
+
conn.evalsha digest, keys, args
|
7
7
|
rescue Redis::CommandError => e
|
8
8
|
if e.message.start_with?('NOSCRIPT')
|
9
|
-
|
9
|
+
conn.eval script, keys, args
|
10
10
|
else
|
11
11
|
raise
|
12
12
|
end
|
data/lib/redis_locks/mutex.rb
CHANGED
@@ -11,7 +11,7 @@ module RedisLocks
|
|
11
11
|
|
12
12
|
def initialize(key, expires_in: 86400, redis: RedisLocks.redis)
|
13
13
|
@key = "#{NAMESPACE}:#{key}"
|
14
|
-
@redis = redis
|
14
|
+
@redis = Connections.ensure_pool(redis)
|
15
15
|
@expires_in = expires_in.to_i
|
16
16
|
|
17
17
|
raise ArgumentError.new("Invalid expires_in: #{expires_in}") unless expires_in > 0
|
@@ -27,16 +27,18 @@ module RedisLocks
|
|
27
27
|
expires_at = now + @expires_in
|
28
28
|
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
@expires_at
|
39
|
-
|
30
|
+
@redis.with do |conn|
|
31
|
+
if conn.setnx(@key, expires_at)
|
32
|
+
conn.expire(@key, expires_at - now)
|
33
|
+
@expires_at = expires_at
|
34
|
+
locked = true
|
35
|
+
else # it was locked
|
36
|
+
if (old_value = conn.get(@key)).to_i <= now
|
37
|
+
# lock has expired
|
38
|
+
if conn.getset(@key, expires_at) == old_value
|
39
|
+
@expires_at = expires_at
|
40
|
+
locked = true
|
41
|
+
end
|
40
42
|
end
|
41
43
|
end
|
42
44
|
end
|
@@ -58,7 +60,7 @@ module RedisLocks
|
|
58
60
|
# To prevent deleting a lock acquired from another process, only delete
|
59
61
|
# the key if it's still valid, and will be for another 2 seconds
|
60
62
|
if Time.now.utc.to_i - 2 < @expires_at
|
61
|
-
@redis.del(@key)
|
63
|
+
@redis.with { |conn| conn.del(@key) }
|
62
64
|
end
|
63
65
|
|
64
66
|
@expires_at = nil
|
@@ -66,7 +66,7 @@ module RedisLocks
|
|
66
66
|
@key = key
|
67
67
|
@resource_count = resources.to_i
|
68
68
|
@stale_client_timeout = stale_client_timeout.to_f
|
69
|
-
@redis = redis
|
69
|
+
@redis = Connections.ensure_pool(redis)
|
70
70
|
@tokens = []
|
71
71
|
|
72
72
|
raise ArgumentError.new("Lock key is required") if @key.nil? || @key.empty?
|
@@ -76,8 +76,11 @@ module RedisLocks
|
|
76
76
|
|
77
77
|
# Forcefully clear the lock. Be careful!
|
78
78
|
def delete!
|
79
|
-
@redis.
|
80
|
-
|
79
|
+
@redis.with do |conn|
|
80
|
+
conn.del(available_key)
|
81
|
+
conn.del(grabbed_key)
|
82
|
+
end
|
83
|
+
|
81
84
|
@tokens = []
|
82
85
|
end
|
83
86
|
|
@@ -101,18 +104,21 @@ module RedisLocks
|
|
101
104
|
def lock(timeout: nil, &block)
|
102
105
|
ensure_exists_and_release_stale_locks!
|
103
106
|
|
104
|
-
success =
|
107
|
+
success = @redis.with do |conn|
|
105
108
|
if timeout
|
106
|
-
|
109
|
+
!conn.blpop(available_key, timeout.to_i).nil?
|
107
110
|
else
|
108
|
-
|
111
|
+
!conn.lpop(available_key).nil?
|
109
112
|
end
|
113
|
+
end
|
110
114
|
|
111
115
|
return false unless success
|
112
116
|
|
113
117
|
token = SecureRandom.hex(16)
|
114
118
|
@tokens.push(token)
|
115
|
-
@redis.
|
119
|
+
@redis.with do |conn|
|
120
|
+
conn.zadd(grabbed_key, epoch_f(conn), token)
|
121
|
+
end
|
116
122
|
|
117
123
|
return_or_yield(token, &block)
|
118
124
|
end
|
@@ -141,9 +147,13 @@ module RedisLocks
|
|
141
147
|
def unlock(token = @tokens.pop)
|
142
148
|
return unless token
|
143
149
|
|
144
|
-
removed =
|
145
|
-
|
146
|
-
|
150
|
+
removed = false
|
151
|
+
|
152
|
+
@redis.with do |conn|
|
153
|
+
removed = conn.zrem grabbed_key, token
|
154
|
+
if removed
|
155
|
+
conn.lpush available_key, 1
|
156
|
+
end
|
147
157
|
end
|
148
158
|
|
149
159
|
removed
|
@@ -165,13 +175,15 @@ module RedisLocks
|
|
165
175
|
end
|
166
176
|
|
167
177
|
def ensure_exists_and_release_stale_locks!
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
178
|
+
@redis.with do |conn|
|
179
|
+
RedisLocks.evalsha_or_eval(
|
180
|
+
conn: conn,
|
181
|
+
script: SETUP_SCRIPT,
|
182
|
+
digest: SETUP_DIGEST,
|
183
|
+
keys: [available_key, grabbed_key],
|
184
|
+
args: [@resource_count, stale_before(conn)]
|
185
|
+
)
|
186
|
+
end
|
175
187
|
end
|
176
188
|
|
177
189
|
def namespaced_key(variable)
|
@@ -186,12 +198,12 @@ module RedisLocks
|
|
186
198
|
@grabbed_key ||= namespaced_key('GRABBED')
|
187
199
|
end
|
188
200
|
|
189
|
-
def stale_before
|
190
|
-
epoch_f - @stale_client_timeout
|
201
|
+
def stale_before(conn)
|
202
|
+
epoch_f(conn) - @stale_client_timeout
|
191
203
|
end
|
192
204
|
|
193
|
-
def epoch_f
|
194
|
-
epoch_i, microseconds =
|
205
|
+
def epoch_f(conn)
|
206
|
+
epoch_i, microseconds = conn.time
|
195
207
|
epoch_i + microseconds.to_f / 1_000_000
|
196
208
|
end
|
197
209
|
|
@@ -45,19 +45,20 @@ module RedisLocks
|
|
45
45
|
@key = "#{NAMESPACE}:#{key}".freeze
|
46
46
|
@rps = number.to_f / period.to_i
|
47
47
|
@burst = number.to_i
|
48
|
-
@redis = redis
|
48
|
+
@redis = Connections.ensure_pool(redis)
|
49
49
|
end
|
50
50
|
|
51
51
|
def take
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
52
|
+
took = @redis.with do |conn|
|
53
|
+
RedisLocks.evalsha_or_eval(
|
54
|
+
conn: conn,
|
55
|
+
script: SCRIPT,
|
56
|
+
digest: DIGEST,
|
57
|
+
keys: [@key],
|
58
|
+
args: [epoch_f(conn), @rps, @burst]
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
61
62
|
took == 1
|
62
63
|
end
|
63
64
|
|
@@ -65,5 +66,12 @@ module RedisLocks
|
|
65
66
|
raise RateLimitExceeded.new(@key, @rps) unless take
|
66
67
|
end
|
67
68
|
|
69
|
+
private
|
70
|
+
|
71
|
+
def epoch_f(conn)
|
72
|
+
epoch_i, microseconds = conn.time
|
73
|
+
epoch_i + (microseconds.to_f/1_000_000)
|
74
|
+
end
|
75
|
+
|
68
76
|
end
|
69
77
|
end
|
data/lib/redis_locks/version.rb
CHANGED
data/redis_locks.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis_locks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Academia.edu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: connection_pool
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rspec
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,6 +81,7 @@ files:
|
|
67
81
|
- README.md
|
68
82
|
- Rakefile
|
69
83
|
- lib/redis_locks.rb
|
84
|
+
- lib/redis_locks/connections.rb
|
70
85
|
- lib/redis_locks/evalsha_or_eval.rb
|
71
86
|
- lib/redis_locks/mutex.rb
|
72
87
|
- lib/redis_locks/resource_unavailable.rb
|