lock_and_cache 4.0.4 → 6.0.1
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 +5 -5
- data/.travis.yml +2 -1
- data/CHANGELOG +37 -0
- data/README.md +13 -10
- data/benchmarks/allowed_in_keys.rb +87 -0
- data/lib/lock_and_cache.rb +36 -15
- data/lib/lock_and_cache/action.rb +45 -18
- data/lib/lock_and_cache/key.rb +26 -6
- data/lib/lock_and_cache/version.rb +1 -1
- data/lock_and_cache.gemspec +0 -2
- data/spec/lock_and_cache/key_spec.rb +15 -5
- data/spec/lock_and_cache_spec.rb +30 -1
- data/spec/spec_helper.rb +2 -1
- metadata +4 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c89613cc8fdd7ed23861149ccfc49e664af988d6b917757f09c18b18b5415f0b
|
4
|
+
data.tar.gz: 286efd9473fedfbd8e7de5175d9a4ec374bde72757d103bd9c964faa6af1bc70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15f13a8204942588e436ddd5a0eec963148d179a10bf3268401316b8479f2857f50e5e83a47d324765c57f614c7e11b2656b7643c139ebbce6e6b67f1c2969ba
|
7
|
+
data.tar.gz: 28d901547a8b96a9065514151149176eef91c5ed9fb76274b7a7e655bba4f8f584b6246e20534342022ae1bddbfe4918cbe73cedcb7936fa886962b117c698bd
|
data/.travis.yml
CHANGED
data/CHANGELOG
CHANGED
@@ -1,3 +1,40 @@
|
|
1
|
+
6.0.1
|
2
|
+
|
3
|
+
* Enhancements
|
4
|
+
|
5
|
+
* Use Redis#exists? to avoid deprec warning
|
6
|
+
|
7
|
+
6.0.0
|
8
|
+
|
9
|
+
* Breaking changes
|
10
|
+
|
11
|
+
* Set lock_storage and cache_storage separately
|
12
|
+
|
13
|
+
5.0.0
|
14
|
+
|
15
|
+
* Enhancements / breaking changes
|
16
|
+
|
17
|
+
* Propagate errors to all waiters up to 1 second after the error
|
18
|
+
* Stop using redlock, just use plain single-node Redis locking
|
19
|
+
|
20
|
+
4.0.6
|
21
|
+
|
22
|
+
* ?
|
23
|
+
|
24
|
+
* Don't test on ruby 2.1
|
25
|
+
|
26
|
+
* Enhancements
|
27
|
+
|
28
|
+
* LockAndCache.cached?(*key_parts) to check if a value is cached
|
29
|
+
|
30
|
+
4.0.5 / 2017-04-01
|
31
|
+
|
32
|
+
* Enhancements
|
33
|
+
|
34
|
+
* allow dates and times in keys
|
35
|
+
* Test on ruby 2.3.0 and 2.4.1
|
36
|
+
* 2x faster key generation
|
37
|
+
|
1
38
|
4.0.4 / 2016-04-11
|
2
39
|
|
3
40
|
* Bug fixes
|
data/README.md
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
[](https://travis-ci.org/seamusabshere/lock_and_cache)
|
4
4
|
[](https://codeclimate.com/github/seamusabshere/lock_and_cache)
|
5
|
-
[](https://gemnasium.com/seamusabshere/lock_and_cache)
|
6
5
|
[](http://badge.fury.io/rb/lock_and_cache)
|
7
6
|
[](https://hakiri.io/github/seamusabshere/lock_and_cache/master)
|
8
7
|
[](http://inch-ci.org/github/seamusabshere/lock_and_cache)
|
@@ -14,7 +13,8 @@ Most caching libraries don't do locking, meaning that >1 process can be calculat
|
|
14
13
|
## Quickstart
|
15
14
|
|
16
15
|
```ruby
|
17
|
-
LockAndCache.
|
16
|
+
LockAndCache.lock_storage = Redis.new db: 3
|
17
|
+
LockAndCache.cache_storage = Redis.new db: 4
|
18
18
|
|
19
19
|
LockAndCache.lock_and_cache(:stock_price, {company: 'MSFT', date: '2015-05-05'}, expires: 10, nil_expires: 1) do
|
20
20
|
# get yer stock quote
|
@@ -27,9 +27,9 @@ end
|
|
27
27
|
|
28
28
|
## Sponsor
|
29
29
|
|
30
|
-
<p><a href="
|
30
|
+
<p><a href="https://www.faraday.io"><img src="https://s3.amazonaws.com/faraday-assets/files/img/logo.svg" alt="Faraday logo"/></a></p>
|
31
31
|
|
32
|
-
We use [`lock_and_cache`](https://github.com/seamusabshere/lock_and_cache) for [
|
32
|
+
We use [`lock_and_cache`](https://github.com/seamusabshere/lock_and_cache) for [B2C customer intelligence at Faraday](https://www.faraday.io).
|
33
33
|
|
34
34
|
## TOC
|
35
35
|
|
@@ -70,21 +70,24 @@ We use [`lock_and_cache`](https://github.com/seamusabshere/lock_and_cache) for [
|
|
70
70
|
|
71
71
|
As you can see, most caching libraries only take care of (1) and (4) (well, and (5) of course).
|
72
72
|
|
73
|
+
If an error is raised during calculation, that error is propagated to all waiters for 1 second.
|
74
|
+
|
73
75
|
## Practice
|
74
76
|
|
75
77
|
### Setup
|
76
78
|
|
77
79
|
```ruby
|
78
|
-
LockAndCache.
|
80
|
+
LockAndCache.lock_storage = Redis.new db: 3
|
81
|
+
LockAndCache.cache_storage = Redis.new db: 4
|
79
82
|
```
|
80
83
|
|
81
84
|
It will use this redis for both locking and storing cached values.
|
82
85
|
|
83
86
|
### Locking
|
84
87
|
|
85
|
-
|
88
|
+
Just uses Redis naive locking with NX.
|
86
89
|
|
87
|
-
|
90
|
+
A 32-second heartbeat is used that will clear the lock if a process is killed.
|
88
91
|
|
89
92
|
### Caching
|
90
93
|
|
@@ -189,7 +192,7 @@ Most caching libraries don't do locking, meaning that >1 process can be calculat
|
|
189
192
|
|
190
193
|
### Heartbeat
|
191
194
|
|
192
|
-
If the process holding the lock dies, we automatically remove the lock so somebody else can do it (using heartbeats
|
195
|
+
If the process holding the lock dies, we automatically remove the lock so somebody else can do it (using heartbeats).
|
193
196
|
|
194
197
|
### Context mode
|
195
198
|
|
@@ -203,14 +206,14 @@ You can expire nil values with a different timeout (`nil_expires`) than other va
|
|
203
206
|
|
204
207
|
## Tunables
|
205
208
|
|
206
|
-
* `LockAndCache.
|
209
|
+
* `LockAndCache.lock_storage=[redis]`
|
210
|
+
* `LockAndCache.cache_storage=[redis]`
|
207
211
|
* `ENV['LOCK_AND_CACHE_DEBUG']='true'` if you want some debugging output on `$stderr`
|
208
212
|
|
209
213
|
## Few dependencies
|
210
214
|
|
211
215
|
* [activesupport](https://rubygems.org/gems/activesupport) (come on, it's the bomb)
|
212
216
|
* [redis](https://github.com/redis/redis-rb)
|
213
|
-
* [redlock](https://github.com/leandromoreira/redlock-rb)
|
214
217
|
|
215
218
|
## Known issues
|
216
219
|
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'date'
|
3
|
+
require 'benchmark/ips'
|
4
|
+
|
5
|
+
ALLOWED_IN_KEYS = [
|
6
|
+
::String,
|
7
|
+
::Symbol,
|
8
|
+
::Numeric,
|
9
|
+
::TrueClass,
|
10
|
+
::FalseClass,
|
11
|
+
::NilClass,
|
12
|
+
::Integer,
|
13
|
+
::Float,
|
14
|
+
::Date,
|
15
|
+
::DateTime,
|
16
|
+
::Time,
|
17
|
+
].to_set
|
18
|
+
parts = RUBY_VERSION.split('.').map(&:to_i)
|
19
|
+
unless parts[0] >= 2 and parts[1] >= 4
|
20
|
+
ALLOWED_IN_KEYS << ::Fixnum
|
21
|
+
ALLOWED_IN_KEYS << ::Bignum
|
22
|
+
end
|
23
|
+
|
24
|
+
EXAMPLES = [
|
25
|
+
'hi',
|
26
|
+
:there,
|
27
|
+
123,
|
28
|
+
123.54,
|
29
|
+
1e99,
|
30
|
+
123456789 ** 2,
|
31
|
+
1e999,
|
32
|
+
true,
|
33
|
+
false,
|
34
|
+
nil,
|
35
|
+
Date.new(2015,1,1),
|
36
|
+
Time.now,
|
37
|
+
DateTime.now,
|
38
|
+
Mutex,
|
39
|
+
Mutex.new,
|
40
|
+
Benchmark,
|
41
|
+
{ hi: :world },
|
42
|
+
[[]],
|
43
|
+
Fixnum,
|
44
|
+
Struct,
|
45
|
+
Struct.new(:a),
|
46
|
+
Struct.new(:a).new(123)
|
47
|
+
]
|
48
|
+
EXAMPLES.each do |example|
|
49
|
+
puts "#{example} -> #{example.class}"
|
50
|
+
end
|
51
|
+
|
52
|
+
puts
|
53
|
+
|
54
|
+
[
|
55
|
+
Date.new(2015,1,1),
|
56
|
+
Time.now,
|
57
|
+
DateTime.now,
|
58
|
+
].each do |x|
|
59
|
+
puts x.to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
puts
|
63
|
+
|
64
|
+
EXAMPLES.each do |example|
|
65
|
+
a = ALLOWED_IN_KEYS.any? { |thing| example.is_a?(thing) }
|
66
|
+
b = ALLOWED_IN_KEYS.include? example.class
|
67
|
+
unless a == b
|
68
|
+
raise "#{example.inspect}: #{a.inspect} vs #{b.inspect}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Benchmark.ips do |x|
|
73
|
+
x.report("any") do
|
74
|
+
example = EXAMPLES.sample
|
75
|
+
y = ALLOWED_IN_KEYS.any? { |thing| example.is_a?(thing) }
|
76
|
+
a = 1
|
77
|
+
y
|
78
|
+
end
|
79
|
+
|
80
|
+
x.report("include") do
|
81
|
+
example = EXAMPLES.sample
|
82
|
+
y = ALLOWED_IN_KEYS.include? example.class
|
83
|
+
a = 1
|
84
|
+
y
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/lib/lock_and_cache.rb
CHANGED
@@ -3,7 +3,6 @@ require 'timeout'
|
|
3
3
|
require 'digest/sha1'
|
4
4
|
require 'base64'
|
5
5
|
require 'redis'
|
6
|
-
require 'redlock'
|
7
6
|
require 'active_support'
|
8
7
|
require 'active_support/core_ext'
|
9
8
|
|
@@ -21,16 +20,26 @@ module LockAndCache
|
|
21
20
|
|
22
21
|
class TimeoutWaitingForLock < StandardError; end
|
23
22
|
|
24
|
-
# @param redis_connection [Redis] A redis connection to be used for lock
|
25
|
-
def LockAndCache.
|
23
|
+
# @param redis_connection [Redis] A redis connection to be used for lock storage
|
24
|
+
def LockAndCache.lock_storage=(redis_connection)
|
26
25
|
raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
|
27
|
-
@
|
28
|
-
@lock_manager = Redlock::Client.new [redis_connection], retry_count: 1
|
26
|
+
@lock_storage = redis_connection
|
29
27
|
end
|
30
28
|
|
31
29
|
# @return [Redis] The redis connection used for lock and cached value storage
|
32
|
-
def LockAndCache.
|
33
|
-
@
|
30
|
+
def LockAndCache.lock_storage
|
31
|
+
@lock_storage
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param redis_connection [Redis] A redis connection to be used for cached value storage
|
35
|
+
def LockAndCache.cache_storage=(redis_connection)
|
36
|
+
raise "only redis for now" unless redis_connection.class.to_s == 'Redis'
|
37
|
+
@cache_storage = redis_connection
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Redis] The redis connection used for cached value storage
|
41
|
+
def LockAndCache.cache_storage
|
42
|
+
@cache_storage
|
34
43
|
end
|
35
44
|
|
36
45
|
# @param logger [Logger] A logger.
|
@@ -43,13 +52,22 @@ module LockAndCache
|
|
43
52
|
@logger
|
44
53
|
end
|
45
54
|
|
46
|
-
# Flush LockAndCache's storage.
|
55
|
+
# Flush LockAndCache's cached value storage.
|
47
56
|
#
|
48
57
|
# @note If you are sharing a redis database, it will clear it...
|
49
58
|
#
|
50
59
|
# @note If you want to clear a single key, try `LockAndCache.clear(key)` (standalone mode) or `#lock_and_cache_clear(method_id, *key_parts)` in context mode.
|
51
|
-
def LockAndCache.
|
52
|
-
|
60
|
+
def LockAndCache.flush_cache
|
61
|
+
cache_storage.flushdb
|
62
|
+
end
|
63
|
+
|
64
|
+
# Flush LockAndCache's lock storage.
|
65
|
+
#
|
66
|
+
# @note If you are sharing a redis database, it will clear it...
|
67
|
+
#
|
68
|
+
# @note If you want to clear a single key, try `LockAndCache.clear(key)` (standalone mode) or `#lock_and_cache_clear(method_id, *key_parts)` in context mode.
|
69
|
+
def LockAndCache.flush_locks
|
70
|
+
lock_storage.flushdb
|
53
71
|
end
|
54
72
|
|
55
73
|
# Lock and cache based on a key.
|
@@ -83,6 +101,14 @@ module LockAndCache
|
|
83
101
|
key.locked?
|
84
102
|
end
|
85
103
|
|
104
|
+
# Check if a key is cached already
|
105
|
+
#
|
106
|
+
# @note Standalone mode. See also "context mode," where you mix LockAndCache into a class and call it from within its methods.
|
107
|
+
def LockAndCache.cached?(*key_parts)
|
108
|
+
key = LockAndCache::Key.new key_parts
|
109
|
+
key.cached?
|
110
|
+
end
|
111
|
+
|
86
112
|
# @param seconds [Numeric] Maximum wait time to get a lock
|
87
113
|
#
|
88
114
|
# @note Can be overridden by putting `max_lock_wait:` in your call to `#lock_and_cache`
|
@@ -109,11 +135,6 @@ module LockAndCache
|
|
109
135
|
@heartbeat_expires || DEFAULT_HEARTBEAT_EXPIRES
|
110
136
|
end
|
111
137
|
|
112
|
-
# @private
|
113
|
-
def LockAndCache.lock_manager
|
114
|
-
@lock_manager
|
115
|
-
end
|
116
|
-
|
117
138
|
# Check if a method is locked on an object.
|
118
139
|
#
|
119
140
|
# @note Subject mode - this is expected to be called on an object whose class has LockAndCache mixed in. See also standalone mode.
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module LockAndCache
|
2
2
|
# @private
|
3
3
|
class Action
|
4
|
+
ERROR_MAGIC_KEY = :lock_and_cache_error
|
5
|
+
|
4
6
|
attr_reader :key
|
5
7
|
attr_reader :options
|
6
8
|
attr_reader :blk
|
@@ -30,8 +32,21 @@ module LockAndCache
|
|
30
32
|
@lock_digest ||= key.lock_digest
|
31
33
|
end
|
32
34
|
|
33
|
-
def
|
34
|
-
@
|
35
|
+
def lock_storage
|
36
|
+
@lock_storage ||= LockAndCache.lock_storage or raise("must set LockAndCache.lock_storage=[Redis]")
|
37
|
+
end
|
38
|
+
|
39
|
+
def cache_storage
|
40
|
+
@cache_storage ||= LockAndCache.cache_storage or raise("must set LockAndCache.cache_storage=[Redis]")
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_existing(existing)
|
44
|
+
v = ::Marshal.load(existing)
|
45
|
+
if v.is_a?(::Hash) and (founderr = v[ERROR_MAGIC_KEY])
|
46
|
+
raise "Another LockAndCache process raised #{founderr}"
|
47
|
+
else
|
48
|
+
v
|
49
|
+
end
|
35
50
|
end
|
36
51
|
|
37
52
|
def perform
|
@@ -40,24 +55,25 @@ module LockAndCache
|
|
40
55
|
raise "heartbeat_expires must be >= 2 seconds" unless heartbeat_expires >= 2
|
41
56
|
heartbeat_frequency = (heartbeat_expires / 2).ceil
|
42
57
|
LockAndCache.logger.debug { "[lock_and_cache] A1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
43
|
-
if
|
44
|
-
return
|
58
|
+
if cache_storage.exists?(digest) and (existing = cache_storage.get(digest)).is_a?(String)
|
59
|
+
return load_existing(existing)
|
45
60
|
end
|
46
61
|
LockAndCache.logger.debug { "[lock_and_cache] B1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
47
62
|
retval = nil
|
48
|
-
|
49
|
-
|
63
|
+
lock_secret = SecureRandom.hex 16
|
64
|
+
acquired = false
|
50
65
|
begin
|
51
66
|
Timeout.timeout(max_lock_wait, TimeoutWaitingForLock) do
|
52
|
-
until
|
67
|
+
until lock_storage.set(lock_digest, lock_secret, nx: true, ex: heartbeat_expires)
|
53
68
|
LockAndCache.logger.debug { "[lock_and_cache] C1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
54
69
|
sleep rand
|
55
70
|
end
|
71
|
+
acquired = true
|
56
72
|
end
|
57
73
|
LockAndCache.logger.debug { "[lock_and_cache] D1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
58
|
-
if
|
74
|
+
if cache_storage.exists?(digest) and (existing = cache_storage.get(digest)).is_a?(String)
|
59
75
|
LockAndCache.logger.debug { "[lock_and_cache] E1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
60
|
-
retval =
|
76
|
+
retval = load_existing existing
|
61
77
|
end
|
62
78
|
unless retval
|
63
79
|
LockAndCache.logger.debug { "[lock_and_cache] F1 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
@@ -70,39 +86,50 @@ module LockAndCache
|
|
70
86
|
sleep heartbeat_frequency
|
71
87
|
break if done
|
72
88
|
LockAndCache.logger.debug { "[lock_and_cache] heartbeat2 #{key.debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
73
|
-
|
89
|
+
# FIXME use lua to check the value
|
90
|
+
raise "unexpectedly lost lock for #{key.debug}" unless lock_storage.get(lock_digest) == lock_secret
|
91
|
+
lock_storage.set lock_digest, lock_secret, xx: true, ex: heartbeat_expires
|
74
92
|
end
|
75
93
|
end
|
76
|
-
|
77
|
-
|
94
|
+
begin
|
95
|
+
retval = blk.call
|
96
|
+
retval.nil? ? set_nil : set_non_nil(retval)
|
97
|
+
rescue
|
98
|
+
set_error $!
|
99
|
+
raise
|
100
|
+
end
|
78
101
|
ensure
|
79
102
|
done = true
|
80
103
|
lock_extender.join if lock_extender.status.nil?
|
81
104
|
end
|
82
105
|
end
|
83
106
|
ensure
|
84
|
-
|
107
|
+
lock_storage.del lock_digest if acquired
|
85
108
|
end
|
86
109
|
retval
|
87
110
|
end
|
88
111
|
|
112
|
+
def set_error(exception)
|
113
|
+
cache_storage.set digest, ::Marshal.dump(ERROR_MAGIC_KEY => exception.message), ex: 1
|
114
|
+
end
|
115
|
+
|
89
116
|
NIL = Marshal.dump nil
|
90
117
|
def set_nil
|
91
118
|
if nil_expires
|
92
|
-
|
119
|
+
cache_storage.set digest, NIL, ex: nil_expires
|
93
120
|
elsif expires
|
94
|
-
|
121
|
+
cache_storage.set digest, NIL, ex: expires
|
95
122
|
else
|
96
|
-
|
123
|
+
cache_storage.set digest, NIL
|
97
124
|
end
|
98
125
|
end
|
99
126
|
|
100
127
|
def set_non_nil(retval)
|
101
128
|
raise "expected not null #{retval.inspect}" if retval.nil?
|
102
129
|
if expires
|
103
|
-
|
130
|
+
cache_storage.set digest, ::Marshal.dump(retval), ex: expires
|
104
131
|
else
|
105
|
-
|
132
|
+
cache_storage.set digest, ::Marshal.dump(retval)
|
106
133
|
end
|
107
134
|
end
|
108
135
|
end
|
data/lib/lock_and_cache/key.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
1
3
|
module LockAndCache
|
2
4
|
# @private
|
3
5
|
class Key
|
@@ -22,8 +24,11 @@ module LockAndCache
|
|
22
24
|
#
|
23
25
|
# Recursively extract id from obj. Calls #lock_and_cache_key if available, otherwise #id
|
24
26
|
def extract_obj_id(obj)
|
25
|
-
|
27
|
+
klass = obj.class
|
28
|
+
if ALLOWED_IN_KEYS.include?(klass)
|
26
29
|
obj
|
30
|
+
elsif DATE.include?(klass)
|
31
|
+
obj.to_s
|
27
32
|
elsif obj.respond_to?(:lock_and_cache_key)
|
28
33
|
extract_obj_id obj.lock_and_cache_key
|
29
34
|
elsif obj.respond_to?(:id)
|
@@ -43,7 +48,19 @@ module LockAndCache
|
|
43
48
|
::TrueClass,
|
44
49
|
::FalseClass,
|
45
50
|
::NilClass,
|
46
|
-
|
51
|
+
::Integer,
|
52
|
+
::Float,
|
53
|
+
].to_set
|
54
|
+
parts = ::RUBY_VERSION.split('.').map(&:to_i)
|
55
|
+
unless parts[0] >= 2 and parts[1] >= 4
|
56
|
+
ALLOWED_IN_KEYS << ::Fixnum
|
57
|
+
ALLOWED_IN_KEYS << ::Bignum
|
58
|
+
end
|
59
|
+
DATE = [
|
60
|
+
::Date,
|
61
|
+
::DateTime,
|
62
|
+
::Time,
|
63
|
+
].to_set
|
47
64
|
METHOD_NAME_IN_CALLER = /in `([^']+)'/
|
48
65
|
|
49
66
|
attr_reader :context
|
@@ -81,14 +98,17 @@ module LockAndCache
|
|
81
98
|
end
|
82
99
|
|
83
100
|
def locked?
|
84
|
-
LockAndCache.
|
101
|
+
LockAndCache.lock_storage.exists? lock_digest
|
102
|
+
end
|
103
|
+
|
104
|
+
def cached?
|
105
|
+
LockAndCache.cache_storage.exists? digest
|
85
106
|
end
|
86
107
|
|
87
108
|
def clear
|
88
109
|
LockAndCache.logger.debug { "[lock_and_cache] clear #{debug} #{Base64.encode64(digest).strip} #{Digest::SHA1.hexdigest digest}" }
|
89
|
-
|
90
|
-
|
91
|
-
storage.del lock_digest
|
110
|
+
LockAndCache.cache_storage.del digest
|
111
|
+
LockAndCache.lock_storage.del lock_digest
|
92
112
|
end
|
93
113
|
|
94
114
|
alias debug key
|
data/lock_and_cache.gemspec
CHANGED
@@ -20,8 +20,6 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'activesupport'
|
22
22
|
spec.add_runtime_dependency 'redis'
|
23
|
-
# temporary until https://github.com/leandromoreira/redlock-rb/pull/20 is merged
|
24
|
-
spec.add_runtime_dependency 'redlock', '>=0.1.3'
|
25
23
|
|
26
24
|
spec.add_development_dependency 'pry'
|
27
25
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
@@ -21,13 +21,23 @@ describe LockAndCache::Key do
|
|
21
21
|
expect(described_class.new(a: 1).send(:parts)).to eq(described_class.new([[:a, 1]]).send(:parts))
|
22
22
|
end
|
23
23
|
|
24
|
+
now = Time.now
|
25
|
+
today = Date.today
|
24
26
|
{
|
25
27
|
[1] => [1],
|
26
|
-
[
|
27
|
-
[[
|
28
|
-
[[
|
29
|
-
[[
|
30
|
-
|
28
|
+
['you'] => ['you'],
|
29
|
+
[['you']] => [['you']],
|
30
|
+
[['you'], "person"] => [['you'], "person"],
|
31
|
+
[['you'], {:silly=>:person}] => [['you'], [[:silly, :person]] ],
|
32
|
+
[now] => [now.to_s],
|
33
|
+
[[now]] => [[now.to_s]],
|
34
|
+
[today] => [today.to_s],
|
35
|
+
[[today]] => [[today.to_s]],
|
36
|
+
{ hi: 'you' } => [[:hi, 'you']],
|
37
|
+
{ hi: 123 } => [[:hi, 123]],
|
38
|
+
{ hi: 123.0 } => [[:hi, 123.0]],
|
39
|
+
{ hi: now } => [[:hi, now.to_s]],
|
40
|
+
{ hi: today } => [[:hi, today.to_s]],
|
31
41
|
[KeyTestId.new] => ['id'],
|
32
42
|
[[KeyTestId.new]] => [['id']],
|
33
43
|
{ a: KeyTestId.new } => [[:a, "id"]],
|
data/spec/lock_and_cache_spec.rb
CHANGED
@@ -140,7 +140,8 @@ end
|
|
140
140
|
|
141
141
|
describe LockAndCache do
|
142
142
|
before do
|
143
|
-
LockAndCache.
|
143
|
+
LockAndCache.flush_locks
|
144
|
+
LockAndCache.flush_cache
|
144
145
|
end
|
145
146
|
|
146
147
|
it 'has a version number' do
|
@@ -337,6 +338,34 @@ describe LockAndCache do
|
|
337
338
|
expect(count).to eq(1)
|
338
339
|
end
|
339
340
|
|
341
|
+
it 'really caches' do
|
342
|
+
expect(LockAndCache.lock_and_cache('hello') { :red }).to eq(:red)
|
343
|
+
expect(LockAndCache.lock_and_cache('hello') { raise(Exception.new("stop")) }).to eq(:red)
|
344
|
+
end
|
345
|
+
|
346
|
+
it 'caches errors (briefly)' do
|
347
|
+
count = 0
|
348
|
+
expect {
|
349
|
+
LockAndCache.lock_and_cache('hello') { count += 1; raise("stop") }
|
350
|
+
}.to raise_error(/stop/)
|
351
|
+
expect(count).to eq(1)
|
352
|
+
expect {
|
353
|
+
LockAndCache.lock_and_cache('hello') { count += 1; raise("no no not me") }
|
354
|
+
}.to raise_error(/LockAndCache.*stop/)
|
355
|
+
expect(count).to eq(1)
|
356
|
+
sleep 1
|
357
|
+
expect {
|
358
|
+
LockAndCache.lock_and_cache('hello') { count += 1; raise("retrying") }
|
359
|
+
}.to raise_error(/retrying/)
|
360
|
+
expect(count).to eq(2)
|
361
|
+
end
|
362
|
+
|
363
|
+
it "can be queried for cached?" do
|
364
|
+
expect(LockAndCache.cached?('hello')).to be_falsy
|
365
|
+
LockAndCache.lock_and_cache('hello') { nil }
|
366
|
+
expect(LockAndCache.cached?('hello')).to be_truthy
|
367
|
+
end
|
368
|
+
|
340
369
|
it 'allows expiry' do
|
341
370
|
count = 0
|
342
371
|
expect(LockAndCache.lock_and_cache('hello', expires: 1) { count += 1 }).to eq(1)
|
data/spec/spec_helper.rb
CHANGED
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: 6.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Seamus Abshere
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: redlock
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 0.1.3
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 0.1.3
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: pry
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -167,6 +153,7 @@ files:
|
|
167
153
|
- LICENSE.txt
|
168
154
|
- README.md
|
169
155
|
- Rakefile
|
156
|
+
- benchmarks/allowed_in_keys.rb
|
170
157
|
- lib/lock_and_cache.rb
|
171
158
|
- lib/lock_and_cache/action.rb
|
172
159
|
- lib/lock_and_cache/key.rb
|
@@ -194,8 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
194
181
|
- !ruby/object:Gem::Version
|
195
182
|
version: '0'
|
196
183
|
requirements: []
|
197
|
-
|
198
|
-
rubygems_version: 2.2.2
|
184
|
+
rubygems_version: 3.0.3
|
199
185
|
signing_key:
|
200
186
|
specification_version: 4
|
201
187
|
summary: Lock and cache methods.
|
@@ -203,4 +189,3 @@ test_files:
|
|
203
189
|
- spec/lock_and_cache/key_spec.rb
|
204
190
|
- spec/lock_and_cache_spec.rb
|
205
191
|
- spec/spec_helper.rb
|
206
|
-
has_rdoc:
|