atomic_cache 0.2.4.rc1 → 0.2.5.rc1

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
2
  SHA256:
3
- metadata.gz: 372963cc5a7e83a92d2dd2ca4336cac86f9664efc0a8cabdffcf7dfb9ba86b17
4
- data.tar.gz: 2db0bb7c54a07280c70349ce6527836d80898e671c0b49eb427f86340094ed6f
3
+ metadata.gz: 5c6e0bf96718fb99b6047009b67c12f3fb4da0b36fd18ba8eb7aded71b744cb2
4
+ data.tar.gz: 8443307bb59ff3e3ca393cb6401f6c30974ec97bc3729024848be74504c5abda
5
5
  SHA512:
6
- metadata.gz: 32a9f8189dcdbb5793dc785d0521fd39c42743d017e8763d0ac20cdf030fea10bbe28d7365cdaf2386c3b0cc121db954b73f053f691d8450ca170fdd9eae9f19
7
- data.tar.gz: f17e1bd01c14c07d43be161edb08f0601e85c526b4c2354ae2bd06e26ae5441f7437900e5af6afe8d83a618bbe434a231a77810201865ee6eb237a06239911fc
6
+ metadata.gz: f438b868a3b60d2d64be72fa314f8cb4ca2a04d9972286d623393bf9754b488577f150d4726c26ee236a0a0cbaae9444fc892fc808b8cda3b23052e2adb8fb8d
7
+ data.tar.gz: d84517bed76804507f0dc395205eaad4e114e0dfda3023b33f6a1e1191e396d1643b7fb501ebd66e677d8d682ef822b5fa5329660ee5c0c2947d3adb4009c99c
@@ -27,7 +27,6 @@ module AtomicCache
27
27
  raise ArgumentError.new("`storage` required but none given") unless @storage.present?
28
28
  end
29
29
 
30
-
31
30
  # Attempts to fetch the given keyspace, using an optional block to generate
32
31
  # a new value when the cache is expired
33
32
  #
@@ -60,13 +59,13 @@ module AtomicCache
60
59
 
61
60
  # quick check back to see if the other process has finished
62
61
  # or fall back to the last known value
63
- value = quick_retry(keyspace, options, tags) || last_known_value(keyspace, options, tags)
62
+ value = quick_retry(key, options, tags) || last_known_value(keyspace, options, tags)
64
63
  return value if value.present?
65
64
 
66
65
  # wait for the other process if a last known value isn't there
67
66
  if key.present?
68
67
  return time('wait.run', tags: tags) do
69
- wait_for_new_value(key, options, tags)
68
+ wait_for_new_value(keyspace, options, tags)
70
69
  end
71
70
  end
72
71
 
@@ -110,10 +109,8 @@ module AtomicCache
110
109
  nil
111
110
  end
112
111
 
113
- def quick_retry(keyspace, options, tags)
114
- key = @timestamp_manager.current_key(keyspace)
112
+ def quick_retry(key, options, tags)
115
113
  duration = option(:quick_retry_ms, options, DEFAULT_quick_retry_ms)
116
-
117
114
  if duration.present? and key.present?
118
115
  sleep(duration.to_f / 1000)
119
116
  value = @storage.read(key, options)
@@ -151,7 +148,7 @@ module AtomicCache
151
148
  nil
152
149
  end
153
150
 
154
- def wait_for_new_value(key, options, tags)
151
+ def wait_for_new_value(keyspace, options, tags)
155
152
  max_retries = option(:max_retries, options, DEFAULT_MAX_RETRIES)
156
153
  max_retries.times do |attempt|
157
154
  metrics_tags = tags.clone.push("attempt:#{attempt}")
@@ -162,6 +159,8 @@ module AtomicCache
162
159
  backoff_duration_ms = option(:backoff_duration_ms, options, backoff_duration_ms)
163
160
  sleep((backoff_duration_ms.to_f / 1000) * attempt)
164
161
 
162
+ # re-fetch the key each time, to make sure we're actually getting the latest key with the correct LMT
163
+ key = @timestamp_manager.current_key(keyspace)
165
164
  value = @storage.read(key, options)
166
165
  if !value.nil?
167
166
  metrics(:increment, 'wait.present', tags: metrics_tags)
@@ -170,7 +169,7 @@ module AtomicCache
170
169
  end
171
170
 
172
171
  metrics(:increment, 'wait.give-up')
173
- log(:warn, "Giving up fetching cache key `#{key}`. Exceeded max retries (#{max_retries}).")
172
+ log(:warn, "Giving up waiting. Exceeded max retries (#{max_retries}).")
174
173
  nil
175
174
  end
176
175
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AtomicCache
4
- VERSION = "0.2.4.rc1"
4
+ VERSION = "0.2.5.rc1"
5
5
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Integration' do
6
+ let(:key_storage) { AtomicCache::Storage::SharedMemory.new }
7
+ let(:cache_storage) { AtomicCache::Storage::SharedMemory.new }
8
+ let(:keyspace) { AtomicCache::Keyspace.new(namespace: 'int.waiting') }
9
+ let(:timestamp_manager) { AtomicCache::LastModTimeKeyManager.new(keyspace: keyspace, storage: key_storage) }
10
+
11
+ let(:generating_client) { AtomicCache::AtomicCacheClient.new(storage: cache_storage, timestamp_manager: timestamp_manager) }
12
+ let(:waiting_client) { AtomicCache::AtomicCacheClient.new(storage: cache_storage, timestamp_manager: timestamp_manager) }
13
+
14
+ it 'correctly waits for a key when no last know value is available' do
15
+ generating_thread = ClientThread.new(generating_client, keyspace)
16
+ generating_thread.start
17
+ waiting_thread = ClientThread.new(waiting_client, keyspace)
18
+ waiting_thread.start
19
+
20
+ generating_thread.generate
21
+ sleep 0.05
22
+ waiting_thread.fetch
23
+ sleep 0.05
24
+ generating_thread.complete
25
+ sleep 0.05
26
+
27
+ generating_thread.terminate
28
+ waiting_thread.terminate
29
+
30
+ expect(generating_thread.result).to eq([1, 2, 3])
31
+ expect(waiting_thread.result).to eq([1, 2, 3])
32
+ end
33
+ end
34
+
35
+
36
+ # Avert your eyes:
37
+ # this class allows atomic client interaction to happen asynchronously so that
38
+ # the waiting behavior of the client can be tested simultaneous to controlling how
39
+ # long the 'generate' behavior takes
40
+ #
41
+ # It works by accepting an incoming 'message' which it places onto one of two queues
42
+ class ClientThread
43
+ attr_reader :result
44
+
45
+ def initialize(client, keyspace)
46
+ @keyspace = keyspace
47
+ @client = client
48
+ @msg_queue = Queue.new
49
+ @generate_queue = Queue.new
50
+ @result = nil
51
+ end
52
+
53
+ def start
54
+ @thread = Thread.new(&method(:run))
55
+ end
56
+
57
+ def fetch
58
+ @msg_queue << :fetch
59
+ end
60
+
61
+ def generate
62
+ @msg_queue << :generate
63
+ end
64
+
65
+ def complete
66
+ @generate_queue << :complete
67
+ end
68
+
69
+ def terminate
70
+ @msg_queue << :terminate
71
+ end
72
+
73
+ private
74
+
75
+ def run
76
+ loop do
77
+ msg = @msg_queue.pop
78
+ sleep 0.001; next unless msg
79
+
80
+ case msg
81
+ when :terminate
82
+ Thread.stop
83
+ when :generate
84
+ do_generate
85
+ when :fetch
86
+ @result = @client.fetch(@keyspace)
87
+ end
88
+ end
89
+ end
90
+
91
+ def do_generate
92
+ @client.fetch(@keyspace) do
93
+ loop do
94
+ msg = @generate_queue.pop
95
+ sleep 0.001; next unless msg
96
+ break if msg == :complete
97
+ end
98
+ @result = [1, 2, 3] # generated value
99
+ @result
100
+ end
101
+ end
102
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atomic_cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4.rc1
4
+ version: 0.2.5.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ibotta Developers
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-06-24 00:00:00.000000000 Z
12
+ date: 2021-06-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -171,7 +171,8 @@ dependencies:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
173
  version: '0.1'
174
- description: desc
174
+ description: A gem which prevents the thundering herd problem through a distributed
175
+ lock
175
176
  email: osscompliance@ibotta.com
176
177
  executables: []
177
178
  extensions: []
@@ -200,6 +201,7 @@ files:
200
201
  - spec/atomic_cache/atomic_cache_client_spec.rb
201
202
  - spec/atomic_cache/concerns/global_lmt_cache_concern_spec.rb
202
203
  - spec/atomic_cache/default_config_spec.rb
204
+ - spec/atomic_cache/integration/waiting_spec.rb
203
205
  - spec/atomic_cache/key/keyspace_spec.rb
204
206
  - spec/atomic_cache/key/last_mod_time_key_manager_spec.rb
205
207
  - spec/atomic_cache/storage/dalli_spec.rb
@@ -229,5 +231,9 @@ requirements: []
229
231
  rubygems_version: 3.0.8
230
232
  signing_key:
231
233
  specification_version: 4
232
- summary: summary
234
+ summary: In a nutshell:* The key of every cached value includes a timestamp* Once
235
+ a cache key is written to, it is never written over* When a newer version of a cached
236
+ value is available, it is written to a new key* When a new value is being generated
237
+ for a new key only 1 process is allowed to do so at a time* While the new value
238
+ is being generated, other processes read one key older than most recent
233
239
  test_files: []