atomic_cache 0.2.4.rc1 → 0.2.5.rc1

Sign up to get free protection for your applications and to get access to all the features.
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: []