zache 0.15.0 → 0.15.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
2
  SHA256:
3
- metadata.gz: f2c6bee2ddda59d3ddba9115b7f1eaf2037fe28cc217bec57aa1bc2dfb91b660
4
- data.tar.gz: 3b1fafcbe3afd075992c0a36df45ca5b51228bd4d1d9c5e086c7523b462ef343
3
+ metadata.gz: 0ed02e68e04482b0ac7df8addc19dd0c02683191c53b3f3b391930e9b8ed4842
4
+ data.tar.gz: ec695943e4b8e9ba6793dd8db9dde826e6bc23cbd2ba92122aae5f52567aace2
5
5
  SHA512:
6
- metadata.gz: 24f22827fe4b5ea546ea2e121b8aaf35c2646ecbcc4ec7134b0d27693bfa4345d29829d29db1d417ab7759c556f3c21c8cea151f267fdaa282d45d69b8e53a57
7
- data.tar.gz: 2a8a4c661c2424a126c8b309825d8d558206db695359793191caf5c1b3e348a147381149258418e7e7469a3e9bfdab30f0164eedfe59383012ed8173fae5fbdf
6
+ metadata.gz: d8758c5554cc1686ec3b82819977982103754cccebefb4b4e3df38d23b77541d82b13ef36626677b5fc4685ea6c7013e14f6e2da113b8cc6d6af376feb56fef3
7
+ data.tar.gz: 261616852da299a0376422d7f86bd62c3e89497874e859c620ba71017825f3e6be9db1cc6beca6feeacb091432137d2fac0fe8b79b1bd8a4595c67f322900810
data/Gemfile CHANGED
@@ -12,11 +12,12 @@ gem 'minitest-reporters', '~>1.7', require: false
12
12
  gem 'os', '>0', require: false
13
13
  gem 'qbash', '>0', require: false
14
14
  gem 'rake', '~>13.2', require: false
15
+ gem 'rdoc', '~>6.15', require: false
15
16
  gem 'rubocop', '~>1.75', require: false
16
- gem 'rubocop-minitest', '>0', require: false
17
- gem 'rubocop-performance', '>0', require: false
18
- gem 'rubocop-rake', '>0', require: false
17
+ gem 'rubocop-minitest', '~>0.38', require: false
18
+ gem 'rubocop-performance', '~>1.25', require: false
19
+ gem 'rubocop-rake', '~>0.7', require: false
19
20
  gem 'simplecov', '~>0.22', require: false
20
- gem 'simplecov-cobertura', '~>2.1', require: false
21
+ gem 'simplecov-cobertura', '~>3.0', require: false
21
22
  gem 'threads', '~>0.4', require: false
22
23
  gem 'yard', '~>0.9', require: false
data/Gemfile.lock CHANGED
@@ -8,40 +8,51 @@ GEM
8
8
  specs:
9
9
  ansi (1.5.0)
10
10
  ast (2.4.3)
11
- backtrace (0.4.0)
11
+ backtrace (0.4.1)
12
12
  builder (3.3.0)
13
13
  concurrent-ruby (1.3.5)
14
+ date (3.5.0)
14
15
  docile (1.4.1)
15
- elapsed (0.0.1)
16
- loog (> 0)
17
- tago (> 0)
18
- json (2.10.2)
19
- language_server-protocol (3.17.0.4)
16
+ elapsed (0.2.0)
17
+ loog (~> 0.6)
18
+ tago (~> 0.1)
19
+ erb (6.0.0)
20
+ json (2.16.0)
21
+ language_server-protocol (3.17.0.5)
20
22
  lint_roller (1.1.0)
21
- loog (0.6.0)
22
- minitest (5.25.5)
23
+ logger (1.7.0)
24
+ loog (0.6.1)
25
+ logger (~> 1.0)
26
+ minitest (5.26.2)
23
27
  minitest-reporters (1.7.1)
24
28
  ansi
25
29
  builder
26
30
  minitest (>= 5.0)
27
31
  ruby-progressbar
28
32
  os (1.1.4)
29
- parallel (1.26.3)
30
- parser (3.3.7.4)
33
+ parallel (1.27.0)
34
+ parser (3.3.10.0)
31
35
  ast (~> 2.4.1)
32
36
  racc
33
- prism (1.4.0)
34
- qbash (0.4.0)
37
+ prism (1.6.0)
38
+ psych (5.2.6)
39
+ date
40
+ stringio
41
+ qbash (0.4.8)
35
42
  backtrace (> 0)
36
43
  elapsed (> 0)
37
44
  loog (> 0)
38
45
  tago (> 0)
39
46
  racc (1.8.1)
40
47
  rainbow (3.1.1)
41
- rake (13.2.1)
42
- regexp_parser (2.10.0)
43
- rexml (3.4.1)
44
- rubocop (1.75.2)
48
+ rake (13.3.1)
49
+ rdoc (6.15.1)
50
+ erb
51
+ psych (>= 4.0.0)
52
+ tsort
53
+ regexp_parser (2.11.3)
54
+ rexml (3.4.4)
55
+ rubocop (1.81.7)
45
56
  json (~> 2.3)
46
57
  language_server-protocol (~> 3.17.0.2)
47
58
  lint_roller (~> 1.1.0)
@@ -49,20 +60,20 @@ GEM
49
60
  parser (>= 3.3.0.2)
50
61
  rainbow (>= 2.2.2, < 4.0)
51
62
  regexp_parser (>= 2.9.3, < 3.0)
52
- rubocop-ast (>= 1.44.0, < 2.0)
63
+ rubocop-ast (>= 1.47.1, < 2.0)
53
64
  ruby-progressbar (~> 1.7)
54
65
  unicode-display_width (>= 2.4.0, < 4.0)
55
- rubocop-ast (1.44.1)
66
+ rubocop-ast (1.48.0)
56
67
  parser (>= 3.3.7.2)
57
68
  prism (~> 1.4)
58
- rubocop-minitest (0.38.0)
69
+ rubocop-minitest (0.38.2)
59
70
  lint_roller (~> 1.1)
60
71
  rubocop (>= 1.75.0, < 2.0)
61
72
  rubocop-ast (>= 1.38.0, < 2.0)
62
- rubocop-performance (1.25.0)
73
+ rubocop-performance (1.26.1)
63
74
  lint_roller (~> 1.1)
64
75
  rubocop (>= 1.75.0, < 2.0)
65
- rubocop-ast (>= 1.38.0, < 2.0)
76
+ rubocop-ast (>= 1.47.1, < 2.0)
66
77
  rubocop-rake (0.7.1)
67
78
  lint_roller (~> 1.1)
68
79
  rubocop (>= 1.72.1)
@@ -71,18 +82,20 @@ GEM
71
82
  docile (~> 1.1)
72
83
  simplecov-html (~> 0.11)
73
84
  simplecov_json_formatter (~> 0.1)
74
- simplecov-cobertura (2.1.0)
85
+ simplecov-cobertura (3.1.0)
75
86
  rexml
76
87
  simplecov (~> 0.19)
77
- simplecov-html (0.13.1)
88
+ simplecov-html (0.13.2)
78
89
  simplecov_json_formatter (0.1.4)
79
- tago (0.1.0)
80
- threads (0.4.1)
90
+ stringio (3.1.8)
91
+ tago (0.4.0)
92
+ threads (0.5.0)
81
93
  backtrace (~> 0)
82
94
  concurrent-ruby (~> 1.0)
83
- unicode-display_width (3.1.4)
84
- unicode-emoji (~> 4.0, >= 4.0.4)
85
- unicode-emoji (4.0.4)
95
+ tsort (0.2.0)
96
+ unicode-display_width (3.2.0)
97
+ unicode-emoji (~> 4.1)
98
+ unicode-emoji (4.1.0)
86
99
  yard (0.9.37)
87
100
 
88
101
  PLATFORMS
@@ -101,12 +114,13 @@ DEPENDENCIES
101
114
  os (> 0)
102
115
  qbash (> 0)
103
116
  rake (~> 13.2)
117
+ rdoc (~> 6.15)
104
118
  rubocop (~> 1.75)
105
- rubocop-minitest (> 0)
106
- rubocop-performance (> 0)
107
- rubocop-rake (> 0)
119
+ rubocop-minitest (~> 0.38)
120
+ rubocop-performance (~> 1.25)
121
+ rubocop-rake (~> 0.7)
108
122
  simplecov (~> 0.22)
109
- simplecov-cobertura (~> 2.1)
123
+ simplecov-cobertura (~> 3.0)
110
124
  threads (~> 0.4)
111
125
  yard (~> 0.9)
112
126
  zache!
data/README.md CHANGED
@@ -13,8 +13,7 @@
13
13
  [![Hits-of-Code](https://hitsofcode.com/github/yegor256/zache)](https://hitsofcode.com/view/github/yegor256/zache)
14
14
 
15
15
  This is a simple Ruby gem for in-memory caching.
16
- Read [this blog post](https://www.yegor256.com/2019/02/05/zache.html)
17
- to understand what Zache is designed for.
16
+ Read this [blog post] to understand what Zache is designed for.
18
17
 
19
18
  First, install it:
20
19
 
@@ -31,22 +30,26 @@ zache = Zache.new
31
30
  v = zache.get(:count, lifetime: 5 * 60) { expensive_calculation() }
32
31
  ```
33
32
 
34
- If you omit the `lifetime` parameter, the key will never expire.
33
+ If you omit the `lifetime` parameter or set it to `nil`,
34
+ the key will never expire.
35
35
 
36
- By default `Zache` is thread-safe. It locks the entire cache on each
37
- `get` call. You can turn that off by using the `sync` argument:
36
+ By default `Zache` is thread-safe.
37
+ It locks the entire cache on each `get` call.
38
+ You can turn that off by using the `sync` argument:
38
39
 
39
40
  ```ruby
40
41
  zache = Zache.new(sync: false)
41
42
  v = zache.get(:count) { expensive_calculation() }
42
43
  ```
43
44
 
44
- You may use "dirty" mode, which will return an expired value while
45
- calculation is in progress. For example, if you have a value in the cache that's
46
- expired, and you call `get` with a long-running block, the thread waits.
47
- If another thread calls `get` again, that second thread won't wait, but will
48
- receive the expired value from the cache. This is a very convenient mode for situations
49
- where absolute data accuracy is less important than performance:
45
+ You may use "dirty" mode, which will return
46
+ an expired value while calculation is in progress.
47
+ For example, if you have a value in the cache that's expired,
48
+ and you call `get` with a long-running block, the thread waits.
49
+ If another thread calls `get` again, that second thread won't wait,
50
+ but will receive the expired value from the cache.
51
+ This is a very convenient mode for situations
52
+ where absolute data accuracy is less important than performance:
50
53
 
51
54
  ```ruby
52
55
  zache = Zache.new(dirty: true)
@@ -54,8 +57,19 @@ zache = Zache.new(dirty: true)
54
57
  value = zache.get(:key, dirty: true) { expensive_calculation() }
55
58
  ```
56
59
 
57
- The entire API is documented
58
- [here](https://www.rubydoc.info/github/yegor256/zache/master/Zache).
60
+ You may use "eager" mode with a placeholder value to return immediately
61
+ while the calculation happens in the background.
62
+ The cache returns the placeholder instantly and spawns
63
+ a thread to calculate the actual value.
64
+ This is useful when you need to avoid blocking
65
+ while expensive operations complete:
66
+
67
+ ```ruby
68
+ # Returns 0 immediately, calculates in background
69
+ value = zache.get(:key, eager: true, placeholder: 0) { expensive_calculation() }
70
+ ```
71
+
72
+ The entire API is [documented][rubydoc].
59
73
  Here are some additional useful methods:
60
74
 
61
75
  ```ruby
@@ -80,12 +94,11 @@ zache.empty?
80
94
 
81
95
  ## How to contribute
82
96
 
83
- Read
84
- [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
85
- Make sure your build is green before you contribute
86
- your pull request. You will need to have
87
- [Ruby](https://www.ruby-lang.org/en/) 2.3+ and
88
- [Bundler](https://bundler.io/) installed. Then:
97
+ Read these [guidelines].
98
+ Make sure your build is green before you contribute your pull request.
99
+ You will need to have [Ruby](https://www.ruby-lang.org/en/) 2.3+ and
100
+ [Bundler](https://bundler.io/) installed.
101
+ Then:
89
102
 
90
103
  ```bash
91
104
  bundle update
@@ -93,3 +106,7 @@ bundle exec rake
93
106
  ```
94
107
 
95
108
  If it's clean and you don't see any error messages, submit your pull request.
109
+
110
+ [blog post]: https://www.yegor256.com/2019/02/05/zache.html
111
+ [rubydoc]: https://www.rubydoc.info/github/yegor256/zache/master/Zache
112
+ [guidelines]: https://www.yegor256.com/2014/04/15/github-guidelines.html
data/REUSE.toml CHANGED
@@ -4,6 +4,18 @@
4
4
  version = 1
5
5
  [[annotations]]
6
6
  path = [
7
+ ".DS_Store",
8
+ ".gitattributes",
9
+ ".gitignore",
10
+ ".pdd",
11
+ "**.json",
12
+ "**.md",
13
+ "**.png",
14
+ "**.svg",
15
+ "**.txt",
16
+ "**/.DS_Store",
17
+ "**/.gitignore",
18
+ "**/.pdd",
7
19
  "**/*.csv",
8
20
  "**/*.jpg",
9
21
  "**/*.json",
@@ -13,15 +25,8 @@ path = [
13
25
  "**/*.svg",
14
26
  "**/*.txt",
15
27
  "**/*.vm",
16
- "**/.DS_Store",
17
- "**/.gitignore",
18
- "**/.pdd",
19
28
  "**/CNAME",
20
29
  "**/Gemfile.lock",
21
- ".DS_Store",
22
- ".gitattributes",
23
- ".gitignore",
24
- ".pdd",
25
30
  "Gemfile.lock",
26
31
  "README.md",
27
32
  "renovate.json",
data/Rakefile CHANGED
@@ -8,6 +8,7 @@ require 'qbash'
8
8
  require 'rubygems'
9
9
  require 'rake'
10
10
  require 'rake/clean'
11
+ require 'shellwords'
11
12
 
12
13
  CLEAN = FileList['coverage']
13
14
 
@@ -43,6 +44,7 @@ require 'yard'
43
44
  desc 'Build Yard documentation'
44
45
  YARD::Rake::YardocTask.new do |t|
45
46
  t.files = ['lib/**/*.rb']
47
+ t.options = ['--fail-on-warning']
46
48
  end
47
49
 
48
50
  require 'rubocop/rake_task'
data/lib/zache.rb CHANGED
@@ -29,8 +29,6 @@ class Zache
29
29
  end
30
30
 
31
31
  # Always returns the result of the block, never caches.
32
- # @param [Object] key Ignored
33
- # @param [Hash] opts Ignored
34
32
  # @yield Block that provides the value
35
33
  # @return [Object] The result of the block
36
34
  def get(*)
@@ -38,24 +36,19 @@ class Zache
38
36
  end
39
37
 
40
38
  # Always returns true regardless of the key.
41
- # @param [Object] key Ignored
42
- # @param [Hash] opts Ignored
43
39
  # @return [Boolean] Always returns true
44
40
  def exists?(*)
45
41
  true
46
42
  end
47
43
 
48
44
  # Always returns false.
49
- # @param [Object] key Ignored
45
+ # @param [Object] _key Ignored
50
46
  # @return [Boolean] Always returns false
51
47
  def locked?(_key)
52
48
  false
53
49
  end
54
50
 
55
51
  # No-op method that ignores the input.
56
- # @param [Object] key Ignored
57
- # @param [Object] value Ignored
58
- # @param [Hash] opts Ignored
59
52
  # @return [nil] Always returns nil
60
53
  def put(*); end
61
54
 
@@ -97,7 +90,7 @@ class Zache
97
90
  #
98
91
  # @return [Integer] Number of keys in the cache
99
92
  def size
100
- @hash.size
93
+ synchronize_all { @hash.size }
101
94
  end
102
95
 
103
96
  # Gets the value from the cache by the provided key.
@@ -122,30 +115,33 @@ class Zache
122
115
  # @return [Object] The cached value
123
116
  def get(key, lifetime: 2**32, dirty: false, placeholder: nil, eager: false, &block)
124
117
  if block_given?
125
- return @hash[key][:value] if (dirty || @dirty) && locked?(key) && expired?(key) && @hash.key?(key)
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
126
123
  if eager
127
- return @hash[key][:value] if @hash.key?(key)
124
+ has_key = synchronize_all { @hash.key?(key) }
125
+ return synchronize_all { @hash[key][:value] } if has_key
128
126
  put(key, placeholder, lifetime: 0)
129
127
  Thread.new do
130
- synchronize_one(key) do
131
- calc(key, lifetime, &block)
132
- end
128
+ synchronize_one(key) { calc(key, lifetime, &block) }
133
129
  end
134
130
  placeholder
135
131
  else
136
- synchronize_one(key) do
137
- calc(key, lifetime, &block)
138
- end
132
+ synchronize_one(key) { calc(key, lifetime, &block) }
139
133
  end
140
134
  else
141
- rec = @hash[key]
142
- if expired?(key)
143
- return rec[:value] if dirty || @dirty
144
- @hash.delete(key)
145
- rec = nil
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]
146
144
  end
147
- raise 'The key is absent in the cache' if rec.nil?
148
- rec[:value]
149
145
  end
150
146
  end
151
147
 
@@ -157,12 +153,14 @@ class Zache
157
153
  # @param dirty [Boolean] Whether to consider expired values as existing
158
154
  # @return [Boolean] True if the key exists and is not expired (unless dirty is true)
159
155
  def exists?(key, dirty: false)
160
- rec = @hash[key]
161
- if expired?(key) && !dirty && !@dirty
162
- @hash.delete(key)
163
- rec = nil
156
+ synchronize_all do
157
+ rec = @hash[key]
158
+ if expired_unsafe?(key) && !dirty && !@dirty
159
+ @hash.delete(key)
160
+ rec = nil
161
+ end
162
+ !rec.nil?
164
163
  end
165
- !rec.nil?
166
164
  end
167
165
 
168
166
  # Checks whether the key exists in the cache and is expired. If the
@@ -171,8 +169,7 @@ class Zache
171
169
  # @param key [Object] The key to check in the cache
172
170
  # @return [Boolean] True if the key exists and is expired
173
171
  def expired?(key)
174
- rec = @hash[key]
175
- !rec.nil? && rec[:start] < Time.now - rec[:lifetime]
172
+ synchronize_all { expired_unsafe?(key) }
176
173
  end
177
174
 
178
175
  # Returns the modification time of the key, if it exists.
@@ -181,8 +178,10 @@ class Zache
181
178
  # @param key [Object] The key to get the modification time for
182
179
  # @return [Time] The modification time of the key or current time if key doesn't exist
183
180
  def mtime(key)
184
- rec = @hash[key]
185
- rec.nil? ? Time.now : rec[:start]
181
+ synchronize_all do
182
+ rec = @hash[key]
183
+ rec.nil? ? Time.now : rec[:start]
184
+ end
186
185
  end
187
186
 
188
187
  # Is key currently locked doing something?
@@ -190,7 +189,7 @@ class Zache
190
189
  # @param [Object] key The key to check
191
190
  # @return [Boolean] True if the cache is locked
192
191
  def locked?(key)
193
- @locks[key]&.locked?
192
+ synchronize_all { @locks[key]&.locked? }
194
193
  end
195
194
 
196
195
  # Put a value into the cache.
@@ -251,7 +250,7 @@ class Zache
251
250
  def clean
252
251
  synchronize_all do
253
252
  size_before = @hash.size
254
- @hash.delete_if { |key, _value| expired?(key) }
253
+ @hash.delete_if { |key, _value| expired_unsafe?(key) }
255
254
  size_before - @hash.size
256
255
  end
257
256
  end
@@ -260,7 +259,7 @@ class Zache
260
259
  #
261
260
  # @return [Boolean] True if the cache is empty
262
261
  def empty?
263
- @hash.empty?
262
+ synchronize_all { @hash.empty? }
264
263
  end
265
264
 
266
265
  private
@@ -272,15 +271,25 @@ class Zache
272
271
  # @return [Object] The cached or newly calculated value
273
272
  def calc(key, lifetime)
274
273
  rec = @hash[key]
275
- rec = nil if expired?(key)
274
+ rec = nil if expired_unsafe?(key)
276
275
  if rec.nil?
277
- @hash[key] = {
276
+ rec = {
278
277
  value: yield,
279
278
  start: Time.now,
280
279
  lifetime: lifetime
281
280
  }
281
+ @hash[key] = rec
282
282
  end
283
- @hash[key][:value]
283
+ rec[:value]
284
+ end
285
+
286
+ # Internal method that checks if a key is expired without acquiring locks.
287
+ # This should only be called from within a synchronized block.
288
+ # @param key [Object] The key to check in the cache
289
+ # @return [Boolean] True if the key exists and is expired
290
+ def expired_unsafe?(key)
291
+ rec = @hash[key]
292
+ !rec.nil? && rec[:lifetime] && rec[:start] < Time.now - rec[:lifetime]
284
293
  end
285
294
 
286
295
  # Executes a block within a synchronized context if sync is enabled.
data/zache.gemspec CHANGED
@@ -8,14 +8,14 @@ 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.0' # Version should be updated before release
11
+ s.version = '0.15.1' # 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'
15
15
  s.authors = ['Yegor Bugayenko']
16
16
  s.email = 'yegor256@gmail.com'
17
17
  s.homepage = 'https://github.com/yegor256/zache'
18
- s.files = `git ls-files`.split($RS)
18
+ s.files = `git ls-files | grep -v -E '^(test/|\\.|renovate)'`.split($RS)
19
19
  s.rdoc_options = ['--charset=UTF-8']
20
20
  s.extra_rdoc_files = ['README.md']
21
21
  s.metadata['rubygems_mfa_required'] = 'true'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zache
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-04-15 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: Zero-footprint in-memory thread-safe cache
13
13
  email: yegor256@gmail.com
@@ -16,21 +16,6 @@ extensions: []
16
16
  extra_rdoc_files:
17
17
  - README.md
18
18
  files:
19
- - ".0pdd.yml"
20
- - ".github/workflows/actionlint.yml"
21
- - ".github/workflows/codecov.yml"
22
- - ".github/workflows/copyrights.yml"
23
- - ".github/workflows/license.yml"
24
- - ".github/workflows/markdown-lint.yml"
25
- - ".github/workflows/pdd.yml"
26
- - ".github/workflows/rake.yml"
27
- - ".github/workflows/reuse.yml"
28
- - ".github/workflows/xcop.yml"
29
- - ".github/workflows/yamllint.yml"
30
- - ".gitignore"
31
- - ".pdd"
32
- - ".rubocop.yml"
33
- - ".rultor.yml"
34
19
  - Gemfile
35
20
  - Gemfile.lock
36
21
  - LICENSE.txt
@@ -40,9 +25,6 @@ files:
40
25
  - Rakefile
41
26
  - lib/zache.rb
42
27
  - logo.svg
43
- - renovate.json
44
- - test/test__helper.rb
45
- - test/test_zache.rb
46
28
  - zache.gemspec
47
29
  homepage: https://github.com/yegor256/zache
48
30
  licenses:
@@ -64,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
46
  - !ruby/object:Gem::Version
65
47
  version: '0'
66
48
  requirements: []
67
- rubygems_version: 3.6.2
49
+ rubygems_version: 3.6.9
68
50
  specification_version: 4
69
51
  summary: In-memory Cache
70
52
  test_files: []
data/.0pdd.yml DELETED
@@ -1,12 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- errors:
5
- - yegor256@gmail.com
6
- # alerts:
7
- # github:
8
- # - yegor256
9
-
10
- tags:
11
- - pdd
12
- - bug
@@ -1,25 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: actionlint
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- actionlint:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - name: Download actionlint
20
- id: get_actionlint
21
- run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
22
- shell: bash
23
- - name: Check workflow files
24
- run: ${{ steps.get_actionlint.outputs.executable }} -color
25
- shell: bash
@@ -1,25 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: codecov
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- jobs:
11
- codecov:
12
- timeout-minutes: 15
13
- runs-on: ubuntu-24.04
14
- steps:
15
- - uses: actions/checkout@v4
16
- - uses: actions/setup-ruby@v1
17
- with:
18
- ruby-version: 3.3
19
- bundler-cache: true
20
- - run: bundle config set --global path "$(pwd)/vendor/bundle"
21
- - run: bundle install --no-color
22
- - run: bundle exec rake
23
- - uses: codecov/codecov-action@v5
24
- with:
25
- token: ${{ secrets.CODECOV_TOKEN }}
@@ -1,15 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: copyrights
6
- 'on':
7
- push:
8
- pull_request:
9
- jobs:
10
- copyrights:
11
- timeout-minutes: 15
12
- runs-on: ubuntu-24.04
13
- steps:
14
- - uses: actions/checkout@v4
15
- - uses: yegor256/copyrights-action@0.0.8
@@ -1,42 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: license
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- license:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - shell: bash
20
- run: |
21
- header="Copyright (c) 2018-$(date +%Y) Yegor Bugayenko"
22
- failed="false"
23
- while IFS= read -r file; do
24
- if ! grep -q "${header}" "${file}"; then
25
- failed="true"
26
- echo "⚠️ Copyright header is not found in: ${file}"
27
- else
28
- echo "File looks good: ${file}"
29
- fi
30
- done < <(find . -type f \( \
31
- -name "Dockerfile" -o \
32
- -name "LICENSE.txt" -o \
33
- -name "Makefile" -o \
34
- -name "Rakefile" -o \
35
- -name "*.sh" -o \
36
- -name "*.rb" -o \
37
- -name "*.fe" -o \
38
- -name "*.yml" \
39
- \) -print)
40
- if [ "${failed}" = "true" ]; then
41
- exit 1
42
- fi
@@ -1,23 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: markdown-lint
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- paths-ignore: ['paper/**', 'sandbox/**']
14
- concurrency:
15
- group: markdown-lint-${{ github.ref }}
16
- cancel-in-progress: true
17
- jobs:
18
- markdown-lint:
19
- timeout-minutes: 15
20
- runs-on: ubuntu-24.04
21
- steps:
22
- - uses: actions/checkout@v4
23
- - uses: articulate/actions-markdownlint@v1
@@ -1,19 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: pdd
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- pdd:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - uses: volodya-lombrozo/pdd-action@master
@@ -1,28 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: rake
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- test:
15
- strategy:
16
- matrix:
17
- os: [ubuntu-24.04, macos-15]
18
- ruby: [3.3]
19
- runs-on: ${{ matrix.os }}
20
- steps:
21
- - uses: actions/checkout@v4
22
- - uses: ruby/setup-ruby@v1
23
- with:
24
- ruby-version: ${{ matrix.ruby }}
25
- bundler-cache: true
26
- - run: bundle config set --global path "$(pwd)/vendor/bundle"
27
- - run: bundle install --no-color
28
- - run: bundle exec rake
@@ -1,19 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: reuse
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- reuse:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - uses: fsfe/reuse-action@v5
@@ -1,21 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: xcop
6
- "on":
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- xcop:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - uses: g4s8/xcop-action@master
20
- with:
21
- files: '**/*.xml'
@@ -1,19 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- name: yamllint
6
- 'on':
7
- push:
8
- branches:
9
- - master
10
- pull_request:
11
- branches:
12
- - master
13
- jobs:
14
- yamllint:
15
- timeout-minutes: 15
16
- runs-on: ubuntu-24.04
17
- steps:
18
- - uses: actions/checkout@v4
19
- - uses: ibiqlik/action-yamllint@v3
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- *.gem
2
- .DS_Store
3
- .bundle/
4
- .idea/
5
- .yardoc/
6
- coverage/
7
- doc/
8
- node_modules/
9
- rdoc/
10
- vendor/
data/.pdd DELETED
@@ -1,5 +0,0 @@
1
- --source=.
2
- --verbose
3
- --rule min-words:20
4
- --rule min-estimate:15
5
- --rule max-estimate:90
data/.rubocop.yml DELETED
@@ -1,40 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- AllCops:
5
- Exclude:
6
- - 'bin/**/*'
7
- - 'assets/**/*'
8
- - 'vendor/**/**'
9
- DisplayCopNames: true
10
- TargetRubyVersion: 2.5
11
- NewCops: enable
12
- SuggestExtensions: false
13
- plugins:
14
- - rubocop-rake
15
- - rubocop-minitest
16
- - rubocop-performance
17
- Minitest/EmptyLineBeforeAssertionMethods:
18
- Enabled: false
19
- Minitest/AssertOperator:
20
- Enabled: false
21
- Layout/EmptyLineAfterGuardClause:
22
- Enabled: false
23
- Layout/MultilineMethodCallIndentation:
24
- Enabled: false
25
- Metrics/ClassLength:
26
- Max: 300
27
- Minitest/MultipleAssertions:
28
- Max: 5
29
- Metrics/AbcSize:
30
- Max: 30
31
- Metrics/MethodLength:
32
- Max: 30
33
- Metrics/CyclomaticComplexity:
34
- Max: 20
35
- Metrics/PerceivedComplexity:
36
- Max: 20
37
- Layout/EndOfLine:
38
- EnforcedStyle: lf
39
- Metrics/BlockLength:
40
- Max: 28
data/.rultor.yml DELETED
@@ -1,26 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
2
- # SPDX-License-Identifier: MIT
3
- ---
4
- # yamllint disable rule:line-length
5
- docker:
6
- image: yegor256/ruby
7
- assets:
8
- rubygems.yml: yegor256/home#assets/rubygems.yml
9
- install: |
10
- pdd -f /dev/null
11
- sudo bundle install --no-color "--gemfile=$(pwd)/Gemfile"
12
- release:
13
- pre: false
14
- script: |-
15
- bundle exec rake
16
- pdd -f /dev/null
17
- rm -rf *.gem
18
- sed -i "s/0\.0\.0/${tag}/g" zache.gemspec
19
- git add zache.gemspec
20
- git commit -m "Version set to ${tag}"
21
- gem build zache.gemspec
22
- chmod 0600 ../rubygems.yml
23
- gem push *.gem --config-file ../rubygems.yml
24
- merge:
25
- script: |-
26
- bundle exec rake clean test rubocop copyright
data/renovate.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
- "extends": [
4
- "config:base"
5
- ]
6
- }
data/test/test__helper.rb DELETED
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
4
- # SPDX-License-Identifier: MIT
5
-
6
- $stdout.sync = true
7
-
8
- require 'simplecov'
9
- require 'simplecov-cobertura'
10
- unless SimpleCov.running || ENV['PICKS']
11
- SimpleCov.command_name('test')
12
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
13
- [
14
- SimpleCov::Formatter::HTMLFormatter,
15
- SimpleCov::Formatter::CoberturaFormatter
16
- ]
17
- )
18
- SimpleCov.minimum_coverage 90
19
- SimpleCov.minimum_coverage_by_file 90
20
- SimpleCov.start do
21
- add_filter 'test/'
22
- add_filter 'vendor/'
23
- add_filter 'target/'
24
- track_files 'lib/**/*.rb'
25
- track_files '*.rb'
26
- end
27
- end
28
-
29
- require 'minitest/autorun'
30
- require 'minitest/reporters'
31
- Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
data/test/test_zache.rb DELETED
@@ -1,311 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # SPDX-FileCopyrightText: Copyright (c) 2018-2025 Yegor Bugayenko
4
- # SPDX-License-Identifier: MIT
5
-
6
- require 'concurrent'
7
- require 'minitest/autorun'
8
- require 'securerandom'
9
- require 'threads'
10
- require 'timeout'
11
- require_relative '../lib/zache'
12
- require_relative 'test__helper'
13
-
14
- Thread.report_on_exception = true
15
-
16
- # Cache test.
17
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
18
- # Copyright:: Copyright (c) 2018-2025 Yegor Bugayenko
19
- # License:: MIT
20
- class ZacheTest < Minitest::Test
21
- def test_caches
22
- z = Zache.new(sync: false)
23
- first = z.get(:hey, lifetime: 5) { rand }
24
- second = z.get(:hey) { rand }
25
- assert_equal(first, second)
26
- assert_equal(1, z.size)
27
- end
28
-
29
- def test_caches_and_expires
30
- z = Zache.new
31
- first = z.get(:hey, lifetime: 0.01) { rand }
32
- sleep 0.1
33
- second = z.get(:hey) { rand }
34
- refute_equal(first, second)
35
- end
36
-
37
- def test_calculates_age
38
- z = Zache.new
39
- z.get(:hey) { rand }
40
- sleep 0.1
41
- assert_operator(z.mtime(:hey), :<, Time.now - 0.05)
42
- end
43
-
44
- def test_caches_in_threads
45
- z = Zache.new
46
- Threads.new(10).assert(100) do
47
- z.get(:hey, lifetime: 0.0001) { rand }
48
- end
49
- end
50
-
51
- def test_key_exists
52
- z = Zache.new
53
- z.get(:hey) { rand }
54
- exists_result = z.exists?(:hey)
55
- not_exists_result = z.exists?(:bye)
56
- assert(exists_result)
57
- refute(not_exists_result)
58
- end
59
-
60
- def test_put_and_exists
61
- z = Zache.new
62
- z.put(:hey, 'hello', lifetime: 0.1)
63
- sleep 0.2
64
- refute(z.exists?(:hey))
65
- end
66
-
67
- def test_remove_key
68
- z = Zache.new
69
- z.get(:hey) { rand }
70
- z.get(:wey) { rand }
71
- assert(z.exists?(:hey))
72
- assert(z.exists?(:wey))
73
- z.remove(:hey)
74
- refute(z.exists?(:hey))
75
- assert(z.exists?(:wey))
76
- end
77
-
78
- def test_remove_by_block
79
- z = Zache.new
80
- z.get('first') { rand }
81
- z.get('second') { rand }
82
- z.remove_by { |k| k == 'first' }
83
- refute(z.exists?('first'))
84
- assert(z.exists?('second'))
85
- end
86
-
87
- def test_remove_key_with_sync_false
88
- z = Zache.new(sync: false)
89
- z.get(:hey) { rand }
90
- z.get(:wey) { rand }
91
- assert(z.exists?(:hey))
92
- assert(z.exists?(:wey))
93
- z.remove(:hey)
94
- refute(z.exists?(:hey))
95
- assert(z.exists?(:wey))
96
- end
97
-
98
- def test_clean_with_threads
99
- z = Zache.new
100
- Threads.new(300).assert(3000) do
101
- z.get(:hey) { rand }
102
- z.get(:bye, lifetime: 0.01) { rand }
103
- sleep 0.1
104
- z.clean
105
- end
106
- assert(z.exists?(:hey))
107
- refute(z.exists?(:bye))
108
- end
109
-
110
- def test_clean
111
- z = Zache.new
112
- z.get(:hey) { rand }
113
- z.get(:bye, lifetime: 0.01) { rand }
114
- sleep 0.1
115
- z.clean
116
- assert(z.exists?(:hey))
117
- refute(z.exists?(:bye))
118
- end
119
-
120
- def test_clean_size
121
- z = Zache.new
122
- z.get(:hey, lifetime: 0.01) { rand }
123
- sleep 0.1
124
- z.clean
125
- assert_empty(z)
126
- end
127
-
128
- def test_clean_with_sync_false
129
- z = Zache.new(sync: false)
130
- z.get(:hey) { rand }
131
- z.get(:bye, lifetime: 0.01) { rand }
132
- sleep 0.1
133
- z.clean
134
- assert(z.exists?(:hey))
135
- refute(z.exists?(:bye))
136
- end
137
-
138
- def test_remove_absent_key
139
- z = Zache.new
140
- z.remove(:hey)
141
- end
142
-
143
- def test_check_and_remove
144
- z = Zache.new
145
- z.get(:hey, lifetime: 0) { rand }
146
- refute(z.exists?(:hey))
147
- end
148
-
149
- def test_remove_all_with_threads
150
- z = Zache.new
151
- Threads.new(10).assert(100) do |i|
152
- z.get(:"hey#{i}") { rand }
153
- assert(z.exists?(:"hey#{i}"))
154
- z.remove_all
155
- end
156
- 10.times do |i|
157
- refute(z.exists?(:"hey#{i}"))
158
- end
159
- end
160
-
161
- def test_remove_all_with_sync
162
- z = Zache.new
163
- z.get(:hey) { rand }
164
- z.get(:bye) { rand }
165
- z.remove_all
166
- refute(z.exists?(:hey))
167
- refute(z.exists?(:bye))
168
- end
169
-
170
- def test_remove_all_without_sync
171
- z = Zache.new(sync: false)
172
- z.get(:hey) { rand }
173
- z.get(:bye) { rand }
174
- z.remove_all
175
- refute(z.exists?(:hey))
176
- refute(z.exists?(:bye))
177
- end
178
-
179
- def test_puts_something_in
180
- z = Zache.new(sync: false)
181
- z.get(:hey) { rand }
182
- z.put(:hey, 123)
183
- assert_equal(123, z.get(:hey))
184
- end
185
-
186
- def test_sync_zache_is_not_reentrant
187
- z = Zache.new
188
- assert_raises ThreadError do
189
- z.get(:first) { z.get(:first) { 1 } }
190
- end
191
- end
192
-
193
- def test_sync_zache_is_reentrant_for_different_keys
194
- z = Zache.new
195
- z.get(:first) { z.get(:second) { 1 } }
196
- end
197
-
198
- def test_calculates_only_once
199
- z = Zache.new
200
- long = Thread.start do
201
- z.get(:x) do
202
- sleep 0.5
203
- 'first'
204
- end
205
- end
206
- sleep 0.1
207
- assert(z.locked?(:x))
208
- z.get(:x) { 'second' }
209
- refute(z.locked?(:x))
210
- long.kill
211
- end
212
-
213
- def test_checks_locked_status_from_inside
214
- z = Zache.new
215
- z.get(:x) do
216
- assert(z.locked?(:x))
217
- 'done'
218
- end
219
- refute(z.locked?(:x))
220
- end
221
-
222
- def test_returns_dirty_result
223
- z = Zache.new(dirty: true)
224
- z.get(:x, lifetime: 0) { 1 }
225
- long = Thread.start do
226
- z.get(:x) do
227
- sleep 1000
228
- 2
229
- end
230
- end
231
- sleep 0.1
232
- Timeout.timeout(1) do
233
- assert(z.exists?(:x))
234
- assert(z.expired?(:x))
235
- assert_equal(1, z.get(:x))
236
- assert_equal(1, z.get(:x) { 2 })
237
- end
238
- long.kill
239
- end
240
-
241
- def test_returns_dirty_result_when_not_locked
242
- z = Zache.new(dirty: true)
243
- z.get(:x, lifetime: 0) { 1 }
244
- assert(z.exists?(:x))
245
- assert_equal(1, z.get(:x))
246
- assert_equal(2, z.get(:x) { 2 })
247
- end
248
-
249
- def test_fetches_multiple_keys_in_many_threads_in_dirty_mode
250
- z = Zache.new(dirty: true)
251
- set = Concurrent::Set.new
252
- threads = 50
253
- barrier = Concurrent::CyclicBarrier.new(threads)
254
- Threads.new(threads).assert(threads * 2) do |i|
255
- barrier.wait if i < threads
256
- set << z.get(i, lifetime: 0.001) { i }
257
- end
258
- assert_equal(threads, set.size)
259
- end
260
-
261
- def test_fetches_multiple_keys_in_many_threads
262
- z = Zache.new
263
- set = Concurrent::Set.new
264
- threads = 50
265
- barrier = Concurrent::CyclicBarrier.new(threads)
266
- Threads.new(threads).assert(threads * 2) do |i|
267
- barrier.wait if i < threads
268
- set << z.get(i) { i }
269
- end
270
- assert_equal(threads, set.size)
271
- end
272
-
273
- def test_fake_class_works
274
- z = Zache::Fake.new
275
- assert_equal(1, z.get(:x) { 1 })
276
- end
277
-
278
- def test_rethrows
279
- z = Zache.new
280
- assert_raises RuntimeError do
281
- z.get(:hey) { raise 'intentional' }
282
- end
283
- end
284
-
285
- def test_returns_placeholder_in_eager_mode
286
- z = Zache.new
287
- a = z.get(:me, placeholder: 42, eager: true) do
288
- sleep 0.1
289
- 43
290
- end
291
- assert_equal(42, a)
292
- sleep 0.2
293
- b = z.get(:me)
294
- assert_equal(43, b)
295
- end
296
-
297
- def test_returns_placeholder_and_releases_lock
298
- z = Zache.new
299
- z.get(:slow, placeholder: 42, eager: true) do
300
- sleep 9999
301
- end
302
- sleep 0.1
303
- assert_equal(555, z.get(:fast) { 555 })
304
- end
305
-
306
- private
307
-
308
- def rand
309
- SecureRandom.uuid
310
- end
311
- end