zache 0.15.1 → 0.15.2

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.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/Rakefile +4 -1
  4. data/lib/zache.rb +100 -35
  5. data/zache.gemspec +1 -1
  6. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ed02e68e04482b0ac7df8addc19dd0c02683191c53b3f3b391930e9b8ed4842
4
- data.tar.gz: ec695943e4b8e9ba6793dd8db9dde826e6bc23cbd2ba92122aae5f52567aace2
3
+ metadata.gz: e9cc0b6c8fece285753b4a7bc12bc296f9cab08b7adacee09dd888f556263e4c
4
+ data.tar.gz: 722f973b74565b70225b44f1de8f6c63ddccf18bbb534ba31a9b1c4c24bbb60d
5
5
  SHA512:
6
- metadata.gz: d8758c5554cc1686ec3b82819977982103754cccebefb4b4e3df38d23b77541d82b13ef36626677b5fc4685ea6c7013e14f6e2da113b8cc6d6af376feb56fef3
7
- data.tar.gz: 261616852da299a0376422d7f86bd62c3e89497874e859c620ba71017825f3e6be9db1cc6beca6feeacb091432137d2fac0fe8b79b1bd8a4595c67f322900810
6
+ metadata.gz: 9ff5b92b44b011b8cb689538e7d4eade3d7d216a89a94b0b85da769bece6610ba9aa27e143f766bbac0b736de65bd7d7faba803b1af4c93876e02913843b819a
7
+ data.tar.gz: dfb50539962ba04cf80db2ceff76dc01abfa3596fd3dc46a21e7d23cc03219e52098a2c77525d0e1ed35b4ea6b31fed6a5f548c228f1105639d66852c4594ac8
data/Gemfile.lock CHANGED
@@ -46,7 +46,7 @@ GEM
46
46
  racc (1.8.1)
47
47
  rainbow (3.1.1)
48
48
  rake (13.3.1)
49
- rdoc (6.15.1)
49
+ rdoc (6.16.1)
50
50
  erb
51
51
  psych (>= 4.0.0)
52
52
  tsort
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ require 'rake'
10
10
  require 'rake/clean'
11
11
  require 'shellwords'
12
12
 
13
- CLEAN = FileList['coverage']
13
+ CLEAN.include('coverage')
14
14
 
15
15
  def name
16
16
  @name ||= File.basename(Dir['*.gemspec'].first, '.*')
@@ -28,6 +28,9 @@ Rake::TestTask.new(:test) do |test|
28
28
  test.libs << 'lib' << 'test'
29
29
  test.pattern = 'test/**/test_*.rb'
30
30
  test.verbose = false
31
+ test.options = '--verbose' if ENV['VERBOSE']
32
+ # Disable minitest plugins on Windows to avoid gem conflicts
33
+ ENV['MT_NO_PLUGINS'] = '1' if OS.windows?
31
34
  end
32
35
 
33
36
  desc 'Run them via Ruby, one by one'
data/lib/zache.rb CHANGED
@@ -115,33 +115,11 @@ class Zache
115
115
  # @return [Object] The cached value
116
116
  def get(key, lifetime: 2**32, dirty: false, placeholder: nil, eager: false, &block)
117
117
  if block_given?
118
- if (dirty || @dirty) && locked?(key)
119
- synchronize_all do
120
- return @hash[key][:value] if expired_unsafe?(key) && @hash.key?(key)
121
- end
122
- end
123
- if eager
124
- has_key = synchronize_all { @hash.key?(key) }
125
- return synchronize_all { @hash[key][:value] } if has_key
126
- put(key, placeholder, lifetime: 0)
127
- Thread.new do
128
- synchronize_one(key) { calc(key, lifetime, &block) }
129
- end
130
- placeholder
131
- else
132
- synchronize_one(key) { calc(key, lifetime, &block) }
133
- end
118
+ return get_dirty_value(key) if should_return_dirty?(key, dirty)
119
+ return get_eager(key, lifetime, placeholder, &block) if eager
120
+ synchronize_one(key) { calc(key, lifetime, &block) }
134
121
  else
135
- synchronize_all do
136
- rec = @hash[key]
137
- if expired_unsafe?(key)
138
- return rec[:value] if dirty || @dirty
139
- @hash.delete(key)
140
- rec = nil
141
- end
142
- raise 'The key is absent in the cache' if rec.nil?
143
- rec[:value]
144
- end
122
+ get_without_block(key, dirty)
145
123
  end
146
124
  end
147
125
 
@@ -215,14 +193,19 @@ class Zache
215
193
  # @yield Block to call if the key is not found
216
194
  # @return [Object] The removed value or the result of the block
217
195
  def remove(key)
218
- synchronize_one(key) { @hash.delete(key) { yield if block_given? } }
196
+ result = synchronize_one(key) { @hash.delete(key) { yield if block_given? } }
197
+ synchronize_all { @locks.delete(key) }
198
+ result
219
199
  end
220
200
 
221
201
  # Remove all keys from the cache.
222
202
  #
223
203
  # @return [Hash] Empty hash
224
204
  def remove_all
225
- synchronize_all { @hash = {} }
205
+ synchronize_all do
206
+ @hash = {}
207
+ @locks = {}
208
+ end
226
209
  end
227
210
 
228
211
  # Remove all keys that match the block.
@@ -234,10 +217,10 @@ class Zache
234
217
  synchronize_all do
235
218
  count = 0
236
219
  @hash.each_key do |k|
237
- if yield(k)
238
- @hash.delete(k)
239
- count += 1
240
- end
220
+ next unless yield(k)
221
+ @hash.delete(k)
222
+ @locks.delete(k)
223
+ count += 1
241
224
  end
242
225
  count
243
226
  end
@@ -250,7 +233,11 @@ class Zache
250
233
  def clean
251
234
  synchronize_all do
252
235
  size_before = @hash.size
253
- @hash.delete_if { |key, _value| expired_unsafe?(key) }
236
+ @hash.delete_if do |key, _value|
237
+ expired = expired_unsafe?(key)
238
+ @locks.delete(key) if expired
239
+ expired
240
+ end
254
241
  size_before - @hash.size
255
242
  end
256
243
  end
@@ -264,6 +251,84 @@ class Zache
264
251
 
265
252
  private
266
253
 
254
+ # Checks if dirty value should be returned for a locked key
255
+ # @param key [Object] The key to check
256
+ # @param dirty [Boolean] Whether dirty reads are allowed
257
+ # @return [Boolean] True if dirty value should be returned
258
+ def should_return_dirty?(key, dirty)
259
+ (dirty || @dirty) && locked?(key) && expired_value?(key)
260
+ end
261
+
262
+ # Checks if key has an expired value in cache
263
+ # @param key [Object] The key to check
264
+ # @return [Boolean] True if key exists and is expired
265
+ def expired_value?(key)
266
+ synchronize_all do
267
+ rec = @hash[key]
268
+ !rec.nil? && expired_unsafe?(key)
269
+ end
270
+ end
271
+
272
+ # Gets the dirty cached value without recalculation
273
+ # @param key [Object] The key to retrieve
274
+ # @return [Object] The cached value
275
+ def get_dirty_value(key)
276
+ synchronize_all { @hash[key][:value] }
277
+ end
278
+
279
+ # Handles eager mode get operation
280
+ # @param key [Object] The key to retrieve
281
+ # @param lifetime [Integer] Time in seconds until the key expires
282
+ # @param placeholder [Object] The placeholder to return immediately
283
+ # @yield Block that provides the value
284
+ # @return [Object] The placeholder value
285
+ def get_eager(key, lifetime, placeholder, &block)
286
+ return synchronize_all { @hash[key][:value] } if synchronize_all { @hash.key?(key) }
287
+
288
+ put(key, placeholder, lifetime: 0)
289
+ spawn_calculation_thread(key, lifetime, &block)
290
+ placeholder
291
+ end
292
+
293
+ # Spawns a background thread to calculate the value
294
+ # @param key [Object] The key to calculate for
295
+ # @param lifetime [Integer] Time in seconds until the key expires
296
+ # @yield Block that provides the value
297
+ def spawn_calculation_thread(key, lifetime, &block)
298
+ Thread.new do
299
+ synchronize_one(key) { calc(key, lifetime, &block) }
300
+ rescue StandardError => e
301
+ cleanup_failed_key(key)
302
+ raise e
303
+ end
304
+ end
305
+
306
+ # Cleans up a key after calculation failure
307
+ # @param key [Object] The key to clean up
308
+ def cleanup_failed_key(key)
309
+ synchronize_all do
310
+ @hash.delete(key)
311
+ @locks.delete(key)
312
+ end
313
+ end
314
+
315
+ # Gets value without a block (retrieval only mode)
316
+ # @param key [Object] The key to retrieve
317
+ # @param dirty [Boolean] Whether to return expired values
318
+ # @return [Object] The cached value
319
+ def get_without_block(key, dirty)
320
+ synchronize_all do
321
+ rec = @hash[key]
322
+ if expired_unsafe?(key)
323
+ return rec[:value] if dirty || @dirty
324
+ @hash.delete(key)
325
+ rec = nil
326
+ end
327
+ raise 'The key is absent in the cache' if rec.nil?
328
+ rec[:value]
329
+ end
330
+ end
331
+
267
332
  # Calculates or retrieves a cached value for the given key.
268
333
  # @param key [Object] The key to store the value under
269
334
  # @param lifetime [Integer] Time in seconds until the key expires
@@ -308,9 +373,9 @@ class Zache
308
373
  # @return [Object] The result of the block
309
374
  def synchronize_one(key, &block)
310
375
  return yield unless @sync
311
- @mutex.synchronize do
376
+ mtx = @mutex.synchronize do
312
377
  @locks[key] ||= Mutex.new
313
378
  end
314
- @locks[key].synchronize(&block)
379
+ mtx.synchronize(&block)
315
380
  end
316
381
  end
data/zache.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
9
9
  s.required_ruby_version = '>= 2.5'
10
10
  s.name = 'zache'
11
- s.version = '0.15.1' # Version should be updated before release
11
+ s.version = '0.15.2' # Version should be updated before release
12
12
  s.license = 'MIT'
13
13
  s.summary = 'In-memory Cache'
14
14
  s.description = 'Zero-footprint in-memory thread-safe cache'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zache
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.1
4
+ version: 0.15.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko