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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4a3eaf745d9a9532d2062ea332fb63420fabbdd9
4
- data.tar.gz: d1f4ec88da5e0f728da43d9b2f40d536c4224498
2
+ SHA256:
3
+ metadata.gz: c89613cc8fdd7ed23861149ccfc49e664af988d6b917757f09c18b18b5415f0b
4
+ data.tar.gz: 286efd9473fedfbd8e7de5175d9a4ec374bde72757d103bd9c964faa6af1bc70
5
5
  SHA512:
6
- metadata.gz: f34529d617b048841b0d33234619846f49a7367ff5627e653298098f0780401314f37df6aa9afe73d967ad358eeaffc3fd0d70ccbc043ecb6a8c1b360c2b61f7
7
- data.tar.gz: d006e7f9e798425f6137d5611c3688b3e78fd906e15390ad0ff015f0834f3fd3e9a4d820c4ca546fa606f17f9ea481296d7f444dc0189cb48b4e368643251648
6
+ metadata.gz: 15f13a8204942588e436ddd5a0eec963148d179a10bf3268401316b8479f2857f50e5e83a47d324765c57f614c7e11b2656b7643c139ebbce6e6b67f1c2969ba
7
+ data.tar.gz: 28d901547a8b96a9065514151149176eef91c5ed9fb76274b7a7e655bba4f8f584b6246e20534342022ae1bddbfe4918cbe73cedcb7936fa886962b117c698bd
@@ -1,6 +1,7 @@
1
1
  language: ruby
2
2
  sudo: false
3
3
  rvm:
4
- - 2.1.2
4
+ - 2.3.0
5
+ - 2.4.1
5
6
  services:
6
7
  - redis-server
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.storage = Redis.new
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="http://faraday.io"><img src="http://cdn2.hubspot.net/hubfs/515497/img/logo.svg" alt="Faraday logo"/></a></p>
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 [data-driven marketing at Faraday](http://faraday.io).
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.storage = Redis.new
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
- Based on [antirez's Redlock algorithm](http://redis.io/topics/distlock).
88
+ Just uses Redis naive locking with NX.
86
89
 
87
- Above and beyond Redlock, a 32-second heartbeat is used that will clear the lock if a process is killed. This is implemented using lock extensions.
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 and redlock extends).
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.storage=[redis]`
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
@@ -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 and cached value storage
25
- def LockAndCache.storage=(redis_connection)
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
- @storage = redis_connection
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.storage
33
- @storage
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.flush
52
- storage.flushdb
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 storage
34
- @storage ||= LockAndCache.storage or raise("must set LockAndCache.storage=[Redis]")
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 storage.exists(digest) and (existing = storage.get(digest)).is_a?(String)
44
- return ::Marshal.load(existing)
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
- lock_manager = LockAndCache.lock_manager
49
- lock_info = nil
63
+ lock_secret = SecureRandom.hex 16
64
+ acquired = false
50
65
  begin
51
66
  Timeout.timeout(max_lock_wait, TimeoutWaitingForLock) do
52
- until lock_info = lock_manager.lock(lock_digest, heartbeat_expires*1000)
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 storage.exists(digest) and (existing = storage.get(digest)).is_a?(String)
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 = ::Marshal.load existing
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
- lock_manager.lock lock_digest, heartbeat_expires*1000, extend: lock_info
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
- retval = blk.call
77
- retval.nil? ? set_nil : set_non_nil(retval)
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
- lock_manager.unlock lock_info if lock_info
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
- storage.setex digest, nil_expires, NIL
119
+ cache_storage.set digest, NIL, ex: nil_expires
93
120
  elsif expires
94
- storage.setex digest, expires, NIL
121
+ cache_storage.set digest, NIL, ex: expires
95
122
  else
96
- storage.set digest, NIL
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
- storage.setex digest, expires, ::Marshal.dump(retval)
130
+ cache_storage.set digest, ::Marshal.dump(retval), ex: expires
104
131
  else
105
- storage.set digest, ::Marshal.dump(retval)
132
+ cache_storage.set digest, ::Marshal.dump(retval)
106
133
  end
107
134
  end
108
135
  end
@@ -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
- if ALLOWED_IN_KEYS.any? { |k| obj.is_a?(k) }
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.storage.exists lock_digest
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
- storage = LockAndCache.storage
90
- storage.del digest
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
@@ -1,3 +1,3 @@
1
1
  module LockAndCache
2
- VERSION = '4.0.4'
2
+ VERSION = '6.0.1'
3
3
  end
@@ -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
- ["you"] => ['you'],
27
- [["you"]] => [['you']],
28
- [["you"], "person"] => [["you"], "person"],
29
- [["you"], {:silly=>:person}] => [["you"], [[:silly, :person]] ],
30
- { hi: 'you' } => [[:hi, "you"]],
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"]],
@@ -140,7 +140,8 @@ end
140
140
 
141
141
  describe LockAndCache do
142
142
  before do
143
- LockAndCache.flush
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)
@@ -4,7 +4,8 @@ require 'lock_and_cache'
4
4
  require 'timeout'
5
5
 
6
6
  require 'redis'
7
- LockAndCache.storage = Redis.new
7
+ LockAndCache.lock_storage = Redis.new db: 3
8
+ LockAndCache.cache_storage = Redis.new db: 4
8
9
 
9
10
  require 'thread/pool'
10
11
 
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.0.4
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: 2016-04-11 00:00:00.000000000 Z
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
- rubyforge_project:
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: