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 +4 -4
- data/Gemfile +5 -4
- data/Gemfile.lock +46 -32
- data/README.md +36 -19
- data/REUSE.toml +12 -7
- data/Rakefile +2 -0
- data/lib/zache.rb +48 -39
- data/zache.gemspec +2 -2
- metadata +3 -21
- data/.0pdd.yml +0 -12
- data/.github/workflows/actionlint.yml +0 -25
- data/.github/workflows/codecov.yml +0 -25
- data/.github/workflows/copyrights.yml +0 -15
- data/.github/workflows/license.yml +0 -42
- data/.github/workflows/markdown-lint.yml +0 -23
- data/.github/workflows/pdd.yml +0 -19
- data/.github/workflows/rake.yml +0 -28
- data/.github/workflows/reuse.yml +0 -19
- data/.github/workflows/xcop.yml +0 -21
- data/.github/workflows/yamllint.yml +0 -19
- data/.gitignore +0 -10
- data/.pdd +0 -5
- data/.rubocop.yml +0 -40
- data/.rultor.yml +0 -26
- data/renovate.json +0 -6
- data/test/test__helper.rb +0 -31
- data/test/test_zache.rb +0 -311
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ed02e68e04482b0ac7df8addc19dd0c02683191c53b3f3b391930e9b8ed4842
|
|
4
|
+
data.tar.gz: ec695943e4b8e9ba6793dd8db9dde826e6bc23cbd2ba92122aae5f52567aace2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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', '
|
|
17
|
-
gem 'rubocop-performance', '
|
|
18
|
-
gem 'rubocop-rake', '
|
|
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', '~>
|
|
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.
|
|
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
|
|
16
|
-
loog (
|
|
17
|
-
tago (
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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.
|
|
30
|
-
parser (3.3.
|
|
33
|
+
parallel (1.27.0)
|
|
34
|
+
parser (3.3.10.0)
|
|
31
35
|
ast (~> 2.4.1)
|
|
32
36
|
racc
|
|
33
|
-
prism (1.
|
|
34
|
-
|
|
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.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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.
|
|
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.
|
|
66
|
+
rubocop-ast (1.48.0)
|
|
56
67
|
parser (>= 3.3.7.2)
|
|
57
68
|
prism (~> 1.4)
|
|
58
|
-
rubocop-minitest (0.38.
|
|
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.
|
|
73
|
+
rubocop-performance (1.26.1)
|
|
63
74
|
lint_roller (~> 1.1)
|
|
64
75
|
rubocop (>= 1.75.0, < 2.0)
|
|
65
|
-
rubocop-ast (>= 1.
|
|
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 (
|
|
85
|
+
simplecov-cobertura (3.1.0)
|
|
75
86
|
rexml
|
|
76
87
|
simplecov (~> 0.19)
|
|
77
|
-
simplecov-html (0.13.
|
|
88
|
+
simplecov-html (0.13.2)
|
|
78
89
|
simplecov_json_formatter (0.1.4)
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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 (
|
|
106
|
-
rubocop-performance (
|
|
107
|
-
rubocop-rake (
|
|
119
|
+
rubocop-minitest (~> 0.38)
|
|
120
|
+
rubocop-performance (~> 1.25)
|
|
121
|
+
rubocop-rake (~> 0.7)
|
|
108
122
|
simplecov (~> 0.22)
|
|
109
|
-
simplecov-cobertura (~>
|
|
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
|
[](https://hitsofcode.com/view/github/yegor256/zache)
|
|
14
14
|
|
|
15
15
|
This is a simple Ruby gem for in-memory caching.
|
|
16
|
-
Read
|
|
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
|
|
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.
|
|
37
|
-
|
|
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
|
|
45
|
-
calculation is in progress.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
137
|
-
calc(key, lifetime, &block)
|
|
138
|
-
end
|
|
132
|
+
synchronize_one(key) { calc(key, lifetime, &block) }
|
|
139
133
|
end
|
|
140
134
|
else
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
185
|
-
|
|
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|
|
|
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
|
|
274
|
+
rec = nil if expired_unsafe?(key)
|
|
276
275
|
if rec.nil?
|
|
277
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
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.
|
|
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,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
|
data/.github/workflows/pdd.yml
DELETED
|
@@ -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
|
data/.github/workflows/rake.yml
DELETED
|
@@ -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
|
data/.github/workflows/reuse.yml
DELETED
|
@@ -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
|
data/.github/workflows/xcop.yml
DELETED
|
@@ -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
data/.pdd
DELETED
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
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
|