lock_and_cache 1.1.0 → 2.0.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/.yardopts +1 -0
- data/CHANGELOG +16 -0
- data/README.md +26 -12
- data/lib/lock_and_cache.rb +71 -58
- data/lib/lock_and_cache/version.rb +1 -1
- data/lock_and_cache.gemspec +3 -2
- data/spec/lock_and_cache_spec.rb +71 -0
- metadata +17 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d82a253311d779887e8cc365b71c27004d894ee
|
4
|
+
data.tar.gz: 54764d5ef99783e9476406fd6c89c53ad9ae9e0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edc54e74ebf38fb009f8211999881b7bc6e8b0addf193c0db828caf6044e63a178c89dc6802a2c1ff459bd35ff3e44531520d9ce18e37b66fe31d572e8f08aed
|
7
|
+
data.tar.gz: 8dc9e372a03506867174c05b52ba9e15067937388549bacc7afc7f70fccfd26b883c1d36159329e605da7dcaea022a6b0521a4d154273772e5474dea170ba184
|
data/.yardopts
CHANGED
data/CHANGELOG
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
2.0.0 / 2015-09-11
|
2
|
+
|
3
|
+
* Breaking changes
|
4
|
+
|
5
|
+
* Stricter key digest - differentiates symbols and strings
|
6
|
+
* No more lock_expires or lock_spin options
|
7
|
+
|
8
|
+
* Bug fixes
|
9
|
+
|
10
|
+
* Allow method names with non-word chars like #foo?
|
11
|
+
|
12
|
+
* Enhancements
|
13
|
+
|
14
|
+
* heartbeats so that SIGKILL will effectively clear the lock
|
15
|
+
* #lock_and_cache_clear now clears lock too
|
16
|
+
|
1
17
|
1.1.0 / 2015-08-07
|
2
18
|
|
3
19
|
* Breaking changes
|
data/README.md
CHANGED
@@ -4,22 +4,45 @@
|
|
4
4
|
[](https://codeclimate.com/github/seamusabshere/lock_and_cache)
|
5
5
|
[](https://gemnasium.com/seamusabshere/lock_and_cache)
|
6
6
|
[](http://badge.fury.io/rb/lock_and_cache)
|
7
|
-
[](https://hakiri.io/github/seamusabshere/lock_and_cache/master)
|
8
8
|
[](http://inch-ci.org/github/seamusabshere/lock_and_cache)
|
9
9
|
|
10
10
|
Lock and cache using redis!
|
11
11
|
|
12
|
-
##
|
12
|
+
## Sponsor
|
13
|
+
|
14
|
+
<p><a href="http://faraday.io"><img src="https://s3.amazonaws.com/photos.angel.co/startups/i/175701-a63ebd1b56a401e905963c64958204d4-medium_jpg.jpg" alt="Faraday logo"/></a></p>
|
15
|
+
|
16
|
+
We use [`lock_and_cache`](https://rubygems.org/gems/lock_and_cache) for [big data-driven marketing at Faraday](http://faraday.io).
|
17
|
+
|
18
|
+
## Theory
|
19
|
+
|
20
|
+
`lock_and_cache`...
|
21
|
+
|
22
|
+
1. returns cached value if found
|
23
|
+
2. acquires a lock
|
24
|
+
3. returns cached value if found (just in case it was calculated while we were waiting for a lock)
|
25
|
+
4. calculates and caches the value
|
26
|
+
5. releases the lock
|
27
|
+
6. returns the value
|
28
|
+
|
29
|
+
As you can see, most caching libraries only take care of (1) and (4).
|
30
|
+
|
31
|
+
## Practice
|
32
|
+
|
33
|
+
### Locking (antirez's Redlock)
|
13
34
|
|
14
35
|
Based on [antirez's Redlock algorithm](http://redis.io/topics/distlock).
|
15
36
|
|
37
|
+
Above and beyond Redlock, a 2-second heartbeat is used that will clear the lock if a process is killed. This is implemented using lock extensions.
|
38
|
+
|
16
39
|
```ruby
|
17
40
|
LockAndCache.storage = Redis.new
|
18
41
|
```
|
19
42
|
|
20
43
|
It will use this redis for both locking and storing cached values.
|
21
44
|
|
22
|
-
|
45
|
+
### Caching (block inside of a method)
|
23
46
|
|
24
47
|
(be sure to set up storage as above)
|
25
48
|
|
@@ -52,8 +75,6 @@ end
|
|
52
75
|
## Tunables
|
53
76
|
|
54
77
|
* `LockAndCache.storage=[redis]`
|
55
|
-
* `LockAndCache.lock_expires=[seconds]` default is 3 days
|
56
|
-
* `LockAndCache.lock_spin=[seconds]` (how long to wait before retrying lock) default is 0.1 seconds
|
57
78
|
* `ENV['LOCK_AND_CACHE_DEBUG']='true'` if you want some debugging output on `$stderr`
|
58
79
|
|
59
80
|
## Few dependencies
|
@@ -61,13 +82,6 @@ end
|
|
61
82
|
* [activesupport](https://rubygems.org/gems/activesupport) (come on, it's the bomb)
|
62
83
|
* [redis](https://github.com/redis/redis-rb)
|
63
84
|
* [redlock](https://github.com/leandromoreira/redlock-rb)
|
64
|
-
* [hash_digest](https://github.com/seamusabshere/hash_digest) (which requires [murmurhash3](https://github.com/funny-falcon/murmurhash3-ruby))
|
65
|
-
|
66
|
-
## Real-world usage
|
67
|
-
|
68
|
-
<p><a href="http://faraday.io"><img src="https://s3.amazonaws.com/photos.angel.co/startups/i/175701-a63ebd1b56a401e905963c64958204d4-medium_jpg.jpg" alt="Faraday logo"/></a></p>
|
69
|
-
|
70
|
-
We use [`lock_and_cache`](https://rubygems.org/gems/lock_and_cache) for [big data-driven marketing at Faraday](http://angel.co/faraday).
|
71
85
|
|
72
86
|
## Contributing
|
73
87
|
|
data/lib/lock_and_cache.rb
CHANGED
@@ -1,23 +1,31 @@
|
|
1
1
|
require 'lock_and_cache/version'
|
2
2
|
require 'timeout'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'base64'
|
3
5
|
require 'redis'
|
4
6
|
require 'redlock'
|
5
|
-
require 'hash_digest'
|
6
7
|
require 'active_support'
|
7
8
|
require 'active_support/core_ext'
|
8
9
|
|
10
|
+
# Lock and cache methods using redis!
|
11
|
+
#
|
12
|
+
# I bet you're caching, but are you locking?
|
9
13
|
module LockAndCache
|
10
|
-
DEFAULT_LOCK_EXPIRES = 60 * 60 * 24 * 1 * 1000 # 1 day in milliseconds
|
11
|
-
DEFAULT_LOCK_SPIN = 0.1
|
12
14
|
DEFAULT_MAX_LOCK_WAIT = 60 * 60 * 24 # 1 day in seconds
|
13
15
|
|
16
|
+
# @private
|
17
|
+
LOCK_HEARTBEAT_EXPIRES = 2
|
18
|
+
|
19
|
+
# @private
|
20
|
+
LOCK_HEARTBEAT_PERIOD = 1
|
21
|
+
|
14
22
|
class TimeoutWaitingForLock < StandardError; end
|
15
23
|
|
16
24
|
# @param redis_connection [Redis] A redis connection to be used for lock and cached value storage
|
17
25
|
def LockAndCache.storage=(redis_connection)
|
18
26
|
raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
|
19
27
|
@storage = redis_connection
|
20
|
-
@lock_manager = Redlock::Client.new [redis_connection]
|
28
|
+
@lock_manager = Redlock::Client.new [redis_connection], retry_count: 1
|
21
29
|
end
|
22
30
|
|
23
31
|
# @return [Redis] The redis connection used for lock and cached value storage
|
@@ -32,31 +40,6 @@ module LockAndCache
|
|
32
40
|
storage.flushdb
|
33
41
|
end
|
34
42
|
|
35
|
-
# @param seconds [Numeric] Lock expiry in seconds.
|
36
|
-
#
|
37
|
-
# @note Can be overridden by putting `expires:` in your call to `#lock_and_cache`
|
38
|
-
def LockAndCache.lock_expires=(seconds)
|
39
|
-
@lock_expires = seconds.to_f * 1000
|
40
|
-
end
|
41
|
-
|
42
|
-
# @return [Numeric] Lock expiry in milliseconds.
|
43
|
-
# @private
|
44
|
-
def LockAndCache.lock_expires
|
45
|
-
@lock_expires || DEFAULT_LOCK_EXPIRES
|
46
|
-
end
|
47
|
-
|
48
|
-
# @param seconds [Numeric] How long to wait before trying a lock again, in seconds
|
49
|
-
#
|
50
|
-
# @note Can be overridden by putting `lock_spin:` in your call to `#lock_and_cache`
|
51
|
-
def LockAndCache.lock_spin=(seconds)
|
52
|
-
@lock_spin = seconds.to_f
|
53
|
-
end
|
54
|
-
|
55
|
-
# @private
|
56
|
-
def LockAndCache.lock_spin
|
57
|
-
@lock_spin || DEFAULT_LOCK_SPIN
|
58
|
-
end
|
59
|
-
|
60
43
|
# @param seconds [Numeric] Maximum wait time to get a lock
|
61
44
|
#
|
62
45
|
# @note Can be overridden by putting `max_lock_wait:` in your call to `#lock_and_cache`
|
@@ -81,18 +64,28 @@ module LockAndCache
|
|
81
64
|
|
82
65
|
def initialize(obj, method_id, parts)
|
83
66
|
@obj = obj
|
84
|
-
@method_id = method_id
|
67
|
+
@method_id = method_id.to_sym
|
85
68
|
@_parts = parts
|
86
69
|
end
|
87
70
|
|
71
|
+
# A (non-cryptographic) digest of the key parts for use as the cache key
|
88
72
|
def digest
|
89
|
-
@digest ||= ::
|
73
|
+
@digest ||= ::Digest::SHA1.hexdigest ::Marshal.dump(key)
|
90
74
|
end
|
91
75
|
|
92
|
-
|
93
|
-
|
76
|
+
# A (non-cryptographic) digest of the key parts for use as the lock key
|
77
|
+
def lock_digest
|
78
|
+
@lock_digest ||= 'lock/' + digest
|
94
79
|
end
|
95
80
|
|
81
|
+
# A human-readable representation of the key parts
|
82
|
+
def key
|
83
|
+
@key ||= [obj_class_name, method_id, parts]
|
84
|
+
end
|
85
|
+
|
86
|
+
alias debug key
|
87
|
+
|
88
|
+
# An array of the parts we use for the key
|
96
89
|
def parts
|
97
90
|
@parts ||= @_parts.map do |v|
|
98
91
|
case v
|
@@ -104,21 +97,26 @@ module LockAndCache
|
|
104
97
|
end
|
105
98
|
end
|
106
99
|
|
100
|
+
# An object (or its class's) name
|
107
101
|
def obj_class_name
|
108
102
|
@obj_class_name ||= (obj.class == ::Class) ? obj.name : obj.class.name
|
109
103
|
end
|
110
104
|
|
111
105
|
end
|
112
106
|
|
113
|
-
|
114
|
-
|
115
|
-
|
107
|
+
def lock_and_cache_locked?(method_id, *key_parts)
|
108
|
+
debug = (ENV['LOCK_AND_CACHE_DEBUG'] == 'true')
|
109
|
+
key = LockAndCache::Key.new self, method_id, key_parts
|
110
|
+
LockAndCache.storage.exists key.lock_digest
|
111
|
+
end
|
112
|
+
|
113
|
+
# Clear a lock and cache given exactly the method and exactly the same arguments
|
116
114
|
def lock_and_cache_clear(method_id, *key_parts)
|
117
115
|
debug = (ENV['LOCK_AND_CACHE_DEBUG'] == 'true')
|
118
116
|
key = LockAndCache::Key.new self, method_id, key_parts
|
119
|
-
Thread.exclusive { $stderr.puts "[lock_and_cache] clear #{key.debug}" } if debug
|
120
|
-
|
121
|
-
LockAndCache.storage.del
|
117
|
+
Thread.exclusive { $stderr.puts "[lock_and_cache] clear #{key.debug} #{Base64.encode64(key.digest).strip} #{Digest::SHA1.hexdigest key.digest}" } if debug
|
118
|
+
LockAndCache.storage.del key.digest
|
119
|
+
LockAndCache.storage.del key.lock_digest
|
122
120
|
end
|
123
121
|
|
124
122
|
# Lock and cache a method given key parts.
|
@@ -129,43 +127,58 @@ module LockAndCache
|
|
129
127
|
def lock_and_cache(*key_parts)
|
130
128
|
raise "need a block" unless block_given?
|
131
129
|
debug = (ENV['LOCK_AND_CACHE_DEBUG'] == 'true')
|
132
|
-
caller[0] =~ /in `(
|
133
|
-
method_id = $1 or raise "couldn't get method_id from #{
|
130
|
+
caller[0] =~ /in `([^']+)'/
|
131
|
+
method_id = $1 or raise "couldn't get method_id from #{caller[0]}"
|
134
132
|
options = key_parts.last.is_a?(Hash) ? key_parts.pop.stringify_keys : {}
|
135
133
|
expires = options['expires']
|
136
|
-
lock_expires = options.fetch 'lock_expires', LockAndCache.lock_expires
|
137
|
-
lock_spin = options.fetch 'lock_spin', LockAndCache.lock_spin
|
138
134
|
max_lock_wait = options.fetch 'max_lock_wait', LockAndCache.max_lock_wait
|
139
135
|
key = LockAndCache::Key.new self, method_id, key_parts
|
140
136
|
digest = key.digest
|
141
|
-
storage = LockAndCache.storage
|
142
|
-
Thread.exclusive { $stderr.puts "[lock_and_cache] A1 #{key.debug}" } if debug
|
137
|
+
storage = LockAndCache.storage or raise("must set LockAndCache.storage=[Redis]")
|
138
|
+
Thread.exclusive { $stderr.puts "[lock_and_cache] A1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
|
143
139
|
if storage.exists digest
|
144
140
|
return ::Marshal.load(storage.get(digest))
|
145
141
|
end
|
146
|
-
Thread.exclusive { $stderr.puts "[lock_and_cache] B1 #{key.debug}" } if debug
|
142
|
+
Thread.exclusive { $stderr.puts "[lock_and_cache] B1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
|
147
143
|
retval = nil
|
148
144
|
lock_manager = LockAndCache.lock_manager
|
149
|
-
lock_digest =
|
145
|
+
lock_digest = key.lock_digest
|
150
146
|
lock_info = nil
|
151
147
|
begin
|
152
148
|
Timeout.timeout(max_lock_wait, TimeoutWaitingForLock) do
|
153
|
-
until lock_info = lock_manager.lock(lock_digest,
|
154
|
-
Thread.exclusive { $stderr.puts "[lock_and_cache] C1 #{key.debug}" } if debug
|
155
|
-
sleep
|
149
|
+
until lock_info = lock_manager.lock(lock_digest, LockAndCache::LOCK_HEARTBEAT_EXPIRES*1000)
|
150
|
+
Thread.exclusive { $stderr.puts "[lock_and_cache] C1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
|
151
|
+
sleep rand
|
156
152
|
end
|
157
153
|
end
|
158
|
-
Thread.exclusive { $stderr.puts "[lock_and_cache] D1 #{key.debug}" } if debug
|
154
|
+
Thread.exclusive { $stderr.puts "[lock_and_cache] D1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
|
159
155
|
if storage.exists digest
|
160
|
-
Thread.exclusive { $stderr.puts "[lock_and_cache] E1 #{key.debug}" } if debug
|
156
|
+
Thread.exclusive { $stderr.puts "[lock_and_cache] E1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
|
161
157
|
retval = ::Marshal.load storage.get(digest)
|
162
158
|
else
|
163
|
-
Thread.exclusive { $stderr.puts "[lock_and_cache] F1 #{key.debug}" } if debug
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
159
|
+
Thread.exclusive { $stderr.puts "[lock_and_cache] F1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
|
160
|
+
done = false
|
161
|
+
begin
|
162
|
+
lock_extender = Thread.new do
|
163
|
+
loop do
|
164
|
+
Thread.exclusive { $stderr.puts "[lock_and_cache] heartbeat1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
|
165
|
+
break if done
|
166
|
+
sleep LockAndCache::LOCK_HEARTBEAT_PERIOD
|
167
|
+
break if done
|
168
|
+
Thread.exclusive { $stderr.puts "[lock_and_cache] heartbeat2 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" } if debug
|
169
|
+
lock_manager.lock lock_digest, LockAndCache::LOCK_HEARTBEAT_EXPIRES*1000, extend: lock_info
|
170
|
+
end
|
171
|
+
end
|
172
|
+
retval = yield
|
173
|
+
if expires
|
174
|
+
storage.setex digest, expires, ::Marshal.dump(retval)
|
175
|
+
else
|
176
|
+
storage.set digest, ::Marshal.dump(retval)
|
177
|
+
end
|
178
|
+
ensure
|
179
|
+
done = true
|
180
|
+
lock_extender.exit if lock_extender.alive?
|
181
|
+
lock_extender.join if lock_extender.status.nil?
|
169
182
|
end
|
170
183
|
end
|
171
184
|
ensure
|
data/lock_and_cache.gemspec
CHANGED
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'activesupport'
|
22
|
-
spec.add_runtime_dependency 'hash_digest'
|
23
22
|
spec.add_runtime_dependency 'redis'
|
24
|
-
|
23
|
+
# temporary until https://github.com/leandromoreira/redlock-rb/pull/20 is merged
|
24
|
+
spec.add_runtime_dependency 'seamusabshere-redlock'
|
25
25
|
|
26
26
|
spec.add_development_dependency 'pry'
|
27
27
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
@@ -29,4 +29,5 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_development_dependency 'rspec'
|
30
30
|
spec.add_development_dependency 'thread'
|
31
31
|
spec.add_development_dependency 'yard'
|
32
|
+
spec.add_development_dependency 'redcarpet'
|
32
33
|
end
|
data/spec/lock_and_cache_spec.rb
CHANGED
@@ -65,6 +65,24 @@ class Bar
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
class Sleeper
|
69
|
+
include LockAndCache
|
70
|
+
|
71
|
+
def initialize
|
72
|
+
@id = SecureRandom.hex
|
73
|
+
end
|
74
|
+
|
75
|
+
def poke
|
76
|
+
lock_and_cache do
|
77
|
+
sleep
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def lock_and_cache_key
|
82
|
+
@id
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
68
86
|
describe LockAndCache do
|
69
87
|
before do
|
70
88
|
LockAndCache.flush
|
@@ -168,6 +186,59 @@ describe LockAndCache do
|
|
168
186
|
end
|
169
187
|
end
|
170
188
|
|
189
|
+
it 'unlocks if a process dies' do
|
190
|
+
child = nil
|
191
|
+
begin
|
192
|
+
sleeper = Sleeper.new
|
193
|
+
child = fork do
|
194
|
+
sleeper.poke
|
195
|
+
end
|
196
|
+
sleep 0.1
|
197
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other process has it
|
198
|
+
Process.kill 'KILL', child
|
199
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other (dead) process still has it
|
200
|
+
sleep 2
|
201
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(false) # but now it should be cleared because no heartbeat
|
202
|
+
ensure
|
203
|
+
Process.kill('KILL', child) rescue Errno::ESRCH
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
it "pays attention to heartbeats" do
|
208
|
+
child = nil
|
209
|
+
begin
|
210
|
+
sleeper = Sleeper.new
|
211
|
+
child = fork do
|
212
|
+
sleeper.poke
|
213
|
+
end
|
214
|
+
sleep 0.1
|
215
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other process has it
|
216
|
+
sleep 2
|
217
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other process still has it
|
218
|
+
sleep 2
|
219
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other process still has it
|
220
|
+
sleep 2
|
221
|
+
expect(sleeper.lock_and_cache_locked?(:poke)).to eq(true) # the other process still has it
|
222
|
+
ensure
|
223
|
+
Process.kill('TERM', child) rescue Errno::ESRCH
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
171
227
|
end
|
172
228
|
|
229
|
+
describe 'keying' do
|
230
|
+
it "doesn't conflate symbol and string args" do
|
231
|
+
symbol = LockAndCache::Key.new(Foo.new(:me), :click, a: 1)
|
232
|
+
string = LockAndCache::Key.new(Foo.new(:me), :click, 'a' => 1)
|
233
|
+
expect(symbol.digest).not_to eq(string.digest)
|
234
|
+
end
|
235
|
+
|
236
|
+
it "cares about order" do
|
237
|
+
symbol = LockAndCache::Key.new(Foo.new(:me), :click, {a: 1, b: 2})
|
238
|
+
string = LockAndCache::Key.new(Foo.new(:me), :click, {b: 2, a: 1})
|
239
|
+
expect(symbol.digest).not_to eq(string.digest)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
|
173
244
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lock_and_cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Seamus Abshere
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: hash_digest
|
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'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: redis
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,7 +39,7 @@ dependencies:
|
|
53
39
|
- !ruby/object:Gem::Version
|
54
40
|
version: '0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
|
-
name: redlock
|
42
|
+
name: seamusabshere-redlock
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
58
44
|
requirements:
|
59
45
|
- - ">="
|
@@ -150,6 +136,20 @@ dependencies:
|
|
150
136
|
- - ">="
|
151
137
|
- !ruby/object:Gem::Version
|
152
138
|
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: redcarpet
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
153
|
description: Lock and cache methods, in case things should only be calculated once
|
154
154
|
across processes.
|
155
155
|
email:
|