lock_and_cache 4.0.4 → 6.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/seamusabshere/lock_and_cache.svg?branch=master&v=2.2.0)](https://travis-ci.org/seamusabshere/lock_and_cache)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/seamusabshere/lock_and_cache/badges/gpa.svg?v=2.2.0)](https://codeclimate.com/github/seamusabshere/lock_and_cache)
|
5
|
-
[![Dependency Status](https://gemnasium.com/seamusabshere/lock_and_cache.svg?v=2.2.0)](https://gemnasium.com/seamusabshere/lock_and_cache)
|
6
5
|
[![Gem Version](https://badge.fury.io/rb/lock_and_cache.svg?v=2.2.0)](http://badge.fury.io/rb/lock_and_cache)
|
7
6
|
[![Security](https://hakiri.io/github/seamusabshere/lock_and_cache/master.svg?v=2.2.0)](https://hakiri.io/github/seamusabshere/lock_and_cache/master)
|
8
7
|
[![Inline docs](http://inch-ci.org/github/seamusabshere/lock_and_cache.svg?branch=master&v=2.2.0)](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:
|