zache 0.14.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 +73 -51
- 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 -297
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,23 +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.
|
|
45
|
+
# @param [Object] _key Ignored
|
|
49
46
|
# @return [Boolean] Always returns false
|
|
50
|
-
def locked?
|
|
47
|
+
def locked?(_key)
|
|
51
48
|
false
|
|
52
49
|
end
|
|
53
50
|
|
|
54
51
|
# No-op method that ignores the input.
|
|
55
|
-
# @param [Object] key Ignored
|
|
56
|
-
# @param [Object] value Ignored
|
|
57
|
-
# @param [Hash] opts Ignored
|
|
58
52
|
# @return [nil] Always returns nil
|
|
59
53
|
def put(*); end
|
|
60
54
|
|
|
@@ -89,13 +83,14 @@ class Zache
|
|
|
89
83
|
@sync = sync
|
|
90
84
|
@dirty = dirty
|
|
91
85
|
@mutex = Mutex.new
|
|
86
|
+
@locks = {}
|
|
92
87
|
end
|
|
93
88
|
|
|
94
89
|
# Total number of keys currently in cache.
|
|
95
90
|
#
|
|
96
91
|
# @return [Integer] Number of keys in the cache
|
|
97
92
|
def size
|
|
98
|
-
@hash.size
|
|
93
|
+
synchronize_all { @hash.size }
|
|
99
94
|
end
|
|
100
95
|
|
|
101
96
|
# Gets the value from the cache by the provided key.
|
|
@@ -120,30 +115,33 @@ class Zache
|
|
|
120
115
|
# @return [Object] The cached value
|
|
121
116
|
def get(key, lifetime: 2**32, dirty: false, placeholder: nil, eager: false, &block)
|
|
122
117
|
if block_given?
|
|
123
|
-
|
|
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
|
|
124
123
|
if eager
|
|
125
|
-
|
|
124
|
+
has_key = synchronize_all { @hash.key?(key) }
|
|
125
|
+
return synchronize_all { @hash[key][:value] } if has_key
|
|
126
126
|
put(key, placeholder, lifetime: 0)
|
|
127
127
|
Thread.new do
|
|
128
|
-
|
|
129
|
-
calc(key, lifetime, &block)
|
|
130
|
-
end
|
|
128
|
+
synchronize_one(key) { calc(key, lifetime, &block) }
|
|
131
129
|
end
|
|
132
130
|
placeholder
|
|
133
131
|
else
|
|
134
|
-
|
|
135
|
-
calc(key, lifetime, &block)
|
|
136
|
-
end
|
|
132
|
+
synchronize_one(key) { calc(key, lifetime, &block) }
|
|
137
133
|
end
|
|
138
134
|
else
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
144
|
end
|
|
145
|
-
raise 'The key is absent in the cache' if rec.nil?
|
|
146
|
-
rec[:value]
|
|
147
145
|
end
|
|
148
146
|
end
|
|
149
147
|
|
|
@@ -155,12 +153,14 @@ class Zache
|
|
|
155
153
|
# @param dirty [Boolean] Whether to consider expired values as existing
|
|
156
154
|
# @return [Boolean] True if the key exists and is not expired (unless dirty is true)
|
|
157
155
|
def exists?(key, dirty: false)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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?
|
|
162
163
|
end
|
|
163
|
-
!rec.nil?
|
|
164
164
|
end
|
|
165
165
|
|
|
166
166
|
# Checks whether the key exists in the cache and is expired. If the
|
|
@@ -169,8 +169,7 @@ class Zache
|
|
|
169
169
|
# @param key [Object] The key to check in the cache
|
|
170
170
|
# @return [Boolean] True if the key exists and is expired
|
|
171
171
|
def expired?(key)
|
|
172
|
-
|
|
173
|
-
!rec.nil? && rec[:start] < Time.now - rec[:lifetime]
|
|
172
|
+
synchronize_all { expired_unsafe?(key) }
|
|
174
173
|
end
|
|
175
174
|
|
|
176
175
|
# Returns the modification time of the key, if it exists.
|
|
@@ -179,15 +178,18 @@ class Zache
|
|
|
179
178
|
# @param key [Object] The key to get the modification time for
|
|
180
179
|
# @return [Time] The modification time of the key or current time if key doesn't exist
|
|
181
180
|
def mtime(key)
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
synchronize_all do
|
|
182
|
+
rec = @hash[key]
|
|
183
|
+
rec.nil? ? Time.now : rec[:start]
|
|
184
|
+
end
|
|
184
185
|
end
|
|
185
186
|
|
|
186
|
-
# Is
|
|
187
|
+
# Is key currently locked doing something?
|
|
187
188
|
#
|
|
189
|
+
# @param [Object] key The key to check
|
|
188
190
|
# @return [Boolean] True if the cache is locked
|
|
189
|
-
def locked?
|
|
190
|
-
@
|
|
191
|
+
def locked?(key)
|
|
192
|
+
synchronize_all { @locks[key]&.locked? }
|
|
191
193
|
end
|
|
192
194
|
|
|
193
195
|
# Put a value into the cache.
|
|
@@ -197,7 +199,7 @@ class Zache
|
|
|
197
199
|
# @param lifetime [Integer] Time in seconds until the key expires (default: never expires)
|
|
198
200
|
# @return [Object] The value stored
|
|
199
201
|
def put(key, value, lifetime: 2**32)
|
|
200
|
-
|
|
202
|
+
synchronize_one(key) do
|
|
201
203
|
@hash[key] = {
|
|
202
204
|
value: value,
|
|
203
205
|
start: Time.now,
|
|
@@ -213,14 +215,14 @@ class Zache
|
|
|
213
215
|
# @yield Block to call if the key is not found
|
|
214
216
|
# @return [Object] The removed value or the result of the block
|
|
215
217
|
def remove(key)
|
|
216
|
-
|
|
218
|
+
synchronize_one(key) { @hash.delete(key) { yield if block_given? } }
|
|
217
219
|
end
|
|
218
220
|
|
|
219
221
|
# Remove all keys from the cache.
|
|
220
222
|
#
|
|
221
223
|
# @return [Hash] Empty hash
|
|
222
224
|
def remove_all
|
|
223
|
-
|
|
225
|
+
synchronize_all { @hash = {} }
|
|
224
226
|
end
|
|
225
227
|
|
|
226
228
|
# Remove all keys that match the block.
|
|
@@ -229,7 +231,7 @@ class Zache
|
|
|
229
231
|
# @yieldparam key [Object] The cache key to evaluate
|
|
230
232
|
# @return [Integer] Number of keys removed
|
|
231
233
|
def remove_by
|
|
232
|
-
|
|
234
|
+
synchronize_all do
|
|
233
235
|
count = 0
|
|
234
236
|
@hash.each_key do |k|
|
|
235
237
|
if yield(k)
|
|
@@ -246,9 +248,9 @@ class Zache
|
|
|
246
248
|
#
|
|
247
249
|
# @return [Integer] Number of keys removed
|
|
248
250
|
def clean
|
|
249
|
-
|
|
251
|
+
synchronize_all do
|
|
250
252
|
size_before = @hash.size
|
|
251
|
-
@hash.delete_if { |key, _value|
|
|
253
|
+
@hash.delete_if { |key, _value| expired_unsafe?(key) }
|
|
252
254
|
size_before - @hash.size
|
|
253
255
|
end
|
|
254
256
|
end
|
|
@@ -257,7 +259,7 @@ class Zache
|
|
|
257
259
|
#
|
|
258
260
|
# @return [Boolean] True if the cache is empty
|
|
259
261
|
def empty?
|
|
260
|
-
@hash.empty?
|
|
262
|
+
synchronize_all { @hash.empty? }
|
|
261
263
|
end
|
|
262
264
|
|
|
263
265
|
private
|
|
@@ -269,26 +271,46 @@ class Zache
|
|
|
269
271
|
# @return [Object] The cached or newly calculated value
|
|
270
272
|
def calc(key, lifetime)
|
|
271
273
|
rec = @hash[key]
|
|
272
|
-
rec = nil if
|
|
274
|
+
rec = nil if expired_unsafe?(key)
|
|
273
275
|
if rec.nil?
|
|
274
|
-
|
|
276
|
+
rec = {
|
|
275
277
|
value: yield,
|
|
276
278
|
start: Time.now,
|
|
277
279
|
lifetime: lifetime
|
|
278
280
|
}
|
|
281
|
+
@hash[key] = rec
|
|
279
282
|
end
|
|
280
|
-
|
|
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]
|
|
281
293
|
end
|
|
282
294
|
|
|
283
295
|
# Executes a block within a synchronized context if sync is enabled.
|
|
284
296
|
# @param block [Proc] The block to execute
|
|
285
297
|
# @yield The block to execute in a synchronized context
|
|
286
298
|
# @return [Object] The result of the block
|
|
287
|
-
def
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
299
|
+
def synchronize_all(&block)
|
|
300
|
+
return yield unless @sync
|
|
301
|
+
@mutex.synchronize(&block)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Executes a block within a synchronized context if sync is enabled.
|
|
305
|
+
# @param key [Object] The object to sync
|
|
306
|
+
# @param block [Proc] The block to execute
|
|
307
|
+
# @yield The block to execute in a synchronized context
|
|
308
|
+
# @return [Object] The result of the block
|
|
309
|
+
def synchronize_one(key, &block)
|
|
310
|
+
return yield unless @sync
|
|
311
|
+
@mutex.synchronize do
|
|
312
|
+
@locks[key] ||= Mutex.new
|
|
292
313
|
end
|
|
314
|
+
@locks[key].synchronize(&block)
|
|
293
315
|
end
|
|
294
316
|
end
|
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.
|
|
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.
|
|
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,297 +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
|
-
cache = Zache.new(sync: false)
|
|
23
|
-
first = cache.get(:hey, lifetime: 5) { rand }
|
|
24
|
-
second = cache.get(:hey) { rand }
|
|
25
|
-
assert_equal(first, second)
|
|
26
|
-
assert_equal(1, cache.size)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def test_caches_and_expires
|
|
30
|
-
cache = Zache.new
|
|
31
|
-
first = cache.get(:hey, lifetime: 0.01) { rand }
|
|
32
|
-
sleep 0.1
|
|
33
|
-
second = cache.get(:hey) { rand }
|
|
34
|
-
refute_equal(first, second)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def test_calculates_age
|
|
38
|
-
cache = Zache.new
|
|
39
|
-
cache.get(:hey) { rand }
|
|
40
|
-
sleep 0.1
|
|
41
|
-
assert_operator(cache.mtime(:hey), :<, Time.now - 0.05)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def test_caches_in_threads
|
|
45
|
-
cache = Zache.new
|
|
46
|
-
Threads.new(10).assert(100) do
|
|
47
|
-
cache.get(:hey, lifetime: 0.0001) { rand }
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def test_key_exists
|
|
52
|
-
cache = Zache.new
|
|
53
|
-
cache.get(:hey) { rand }
|
|
54
|
-
exists_result = cache.exists?(:hey)
|
|
55
|
-
not_exists_result = cache.exists?(:bye)
|
|
56
|
-
assert(exists_result)
|
|
57
|
-
refute(not_exists_result)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def test_put_and_exists
|
|
61
|
-
cache = Zache.new
|
|
62
|
-
cache.put(:hey, 'hello', lifetime: 0.1)
|
|
63
|
-
sleep 0.2
|
|
64
|
-
refute(cache.exists?(:hey))
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def test_remove_key
|
|
68
|
-
cache = Zache.new
|
|
69
|
-
cache.get(:hey) { rand }
|
|
70
|
-
cache.get(:wey) { rand }
|
|
71
|
-
assert(cache.exists?(:hey))
|
|
72
|
-
assert(cache.exists?(:wey))
|
|
73
|
-
cache.remove(:hey)
|
|
74
|
-
refute(cache.exists?(:hey))
|
|
75
|
-
assert(cache.exists?(:wey))
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def test_remove_by_block
|
|
79
|
-
cache = Zache.new
|
|
80
|
-
cache.get('first') { rand }
|
|
81
|
-
cache.get('second') { rand }
|
|
82
|
-
cache.remove_by { |k| k == 'first' }
|
|
83
|
-
refute(cache.exists?('first'))
|
|
84
|
-
assert(cache.exists?('second'))
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def test_remove_key_with_sync_false
|
|
88
|
-
cache = Zache.new(sync: false)
|
|
89
|
-
cache.get(:hey) { rand }
|
|
90
|
-
cache.get(:wey) { rand }
|
|
91
|
-
assert(cache.exists?(:hey))
|
|
92
|
-
assert(cache.exists?(:wey))
|
|
93
|
-
cache.remove(:hey)
|
|
94
|
-
refute(cache.exists?(:hey))
|
|
95
|
-
assert(cache.exists?(:wey))
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def test_clean_with_threads
|
|
99
|
-
cache = Zache.new
|
|
100
|
-
Threads.new(300).assert(3000) do
|
|
101
|
-
cache.get(:hey) { rand }
|
|
102
|
-
cache.get(:bye, lifetime: 0.01) { rand }
|
|
103
|
-
sleep 0.1
|
|
104
|
-
cache.clean
|
|
105
|
-
end
|
|
106
|
-
assert(cache.exists?(:hey))
|
|
107
|
-
refute(cache.exists?(:bye))
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def test_clean
|
|
111
|
-
cache = Zache.new
|
|
112
|
-
cache.get(:hey) { rand }
|
|
113
|
-
cache.get(:bye, lifetime: 0.01) { rand }
|
|
114
|
-
sleep 0.1
|
|
115
|
-
cache.clean
|
|
116
|
-
assert(cache.exists?(:hey))
|
|
117
|
-
refute(cache.exists?(:bye))
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def test_clean_size
|
|
121
|
-
cache = Zache.new
|
|
122
|
-
cache.get(:hey, lifetime: 0.01) { rand }
|
|
123
|
-
sleep 0.1
|
|
124
|
-
cache.clean
|
|
125
|
-
assert_empty(cache)
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def test_clean_with_sync_false
|
|
129
|
-
cache = Zache.new(sync: false)
|
|
130
|
-
cache.get(:hey) { rand }
|
|
131
|
-
cache.get(:bye, lifetime: 0.01) { rand }
|
|
132
|
-
sleep 0.1
|
|
133
|
-
cache.clean
|
|
134
|
-
assert(cache.exists?(:hey))
|
|
135
|
-
refute(cache.exists?(:bye))
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def test_remove_absent_key
|
|
139
|
-
cache = Zache.new
|
|
140
|
-
cache.remove(:hey)
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def test_check_and_remove
|
|
144
|
-
cache = Zache.new
|
|
145
|
-
cache.get(:hey, lifetime: 0) { rand }
|
|
146
|
-
refute(cache.exists?(:hey))
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def test_remove_all_with_threads
|
|
150
|
-
cache = Zache.new
|
|
151
|
-
Threads.new(10).assert(100) do |i|
|
|
152
|
-
cache.get(:"hey#{i}") { rand }
|
|
153
|
-
assert(cache.exists?(:"hey#{i}"))
|
|
154
|
-
cache.remove_all
|
|
155
|
-
end
|
|
156
|
-
10.times do |i|
|
|
157
|
-
refute(cache.exists?(:"hey#{i}"))
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def test_remove_all_with_sync
|
|
162
|
-
cache = Zache.new
|
|
163
|
-
cache.get(:hey) { rand }
|
|
164
|
-
cache.get(:bye) { rand }
|
|
165
|
-
cache.remove_all
|
|
166
|
-
refute(cache.exists?(:hey))
|
|
167
|
-
refute(cache.exists?(:bye))
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def test_remove_all_without_sync
|
|
171
|
-
cache = Zache.new(sync: false)
|
|
172
|
-
cache.get(:hey) { rand }
|
|
173
|
-
cache.get(:bye) { rand }
|
|
174
|
-
cache.remove_all
|
|
175
|
-
refute(cache.exists?(:hey))
|
|
176
|
-
refute(cache.exists?(:bye))
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
def test_puts_something_in
|
|
180
|
-
cache = Zache.new(sync: false)
|
|
181
|
-
cache.get(:hey) { rand }
|
|
182
|
-
cache.put(:hey, 123)
|
|
183
|
-
assert_equal(123, cache.get(:hey))
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def test_sync_zache_is_not_reentrant
|
|
187
|
-
cache = Zache.new
|
|
188
|
-
assert_raises ThreadError do
|
|
189
|
-
cache.get(:first) { cache.get(:second) { 1 } }
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def test_calculates_only_once
|
|
194
|
-
cache = Zache.new
|
|
195
|
-
long = Thread.start do
|
|
196
|
-
cache.get(:x) do
|
|
197
|
-
sleep 0.5
|
|
198
|
-
'first'
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
sleep 0.1
|
|
202
|
-
assert_predicate(cache, :locked?)
|
|
203
|
-
cache.get(:x) { 'second' }
|
|
204
|
-
refute_predicate(cache, :locked?)
|
|
205
|
-
long.kill
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def test_checks_locked_status_from_inside
|
|
209
|
-
cache = Zache.new
|
|
210
|
-
cache.get(:x) do
|
|
211
|
-
assert_predicate(cache, :locked?)
|
|
212
|
-
'done'
|
|
213
|
-
end
|
|
214
|
-
refute_predicate(cache, :locked?)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def test_returns_dirty_result
|
|
218
|
-
cache = Zache.new(dirty: true)
|
|
219
|
-
cache.get(:x, lifetime: 0) { 1 }
|
|
220
|
-
long = Thread.start do
|
|
221
|
-
cache.get(:x) do
|
|
222
|
-
sleep 1000
|
|
223
|
-
2
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
sleep 0.1
|
|
227
|
-
Timeout.timeout(1) do
|
|
228
|
-
assert(cache.exists?(:x))
|
|
229
|
-
assert(cache.expired?(:x))
|
|
230
|
-
assert_equal(1, cache.get(:x))
|
|
231
|
-
assert_equal(1, cache.get(:x) { 2 })
|
|
232
|
-
end
|
|
233
|
-
long.kill
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def test_returns_dirty_result_when_not_locked
|
|
237
|
-
cache = Zache.new(dirty: true)
|
|
238
|
-
cache.get(:x, lifetime: 0) { 1 }
|
|
239
|
-
assert(cache.exists?(:x))
|
|
240
|
-
assert_equal(1, cache.get(:x))
|
|
241
|
-
assert_equal(2, cache.get(:x) { 2 })
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def test_fetches_multiple_keys_in_many_threads_in_dirty_mode
|
|
245
|
-
cache = Zache.new(dirty: true)
|
|
246
|
-
set = Concurrent::Set.new
|
|
247
|
-
threads = 50
|
|
248
|
-
barrier = Concurrent::CyclicBarrier.new(threads)
|
|
249
|
-
Threads.new(threads).assert(threads * 2) do |i|
|
|
250
|
-
barrier.wait if i < threads
|
|
251
|
-
set << cache.get(i, lifetime: 0.001) { i }
|
|
252
|
-
end
|
|
253
|
-
assert_equal(threads, set.size)
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
def test_fetches_multiple_keys_in_many_threads
|
|
257
|
-
cache = Zache.new
|
|
258
|
-
set = Concurrent::Set.new
|
|
259
|
-
threads = 50
|
|
260
|
-
barrier = Concurrent::CyclicBarrier.new(threads)
|
|
261
|
-
Threads.new(threads).assert(threads * 2) do |i|
|
|
262
|
-
barrier.wait if i < threads
|
|
263
|
-
set << cache.get(i) { i }
|
|
264
|
-
end
|
|
265
|
-
assert_equal(threads, set.size)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
def test_fake_class_works
|
|
269
|
-
cache = Zache::Fake.new
|
|
270
|
-
assert_equal(1, cache.get(:x) { 1 })
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def test_rethrows
|
|
274
|
-
cache = Zache.new
|
|
275
|
-
assert_raises RuntimeError do
|
|
276
|
-
cache.get(:hey) { raise 'intentional' }
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
def test_returns_placeholder_in_eager_mode
|
|
281
|
-
cache = Zache.new
|
|
282
|
-
a = cache.get(:me, placeholder: 42, eager: true) do
|
|
283
|
-
sleep 0.1
|
|
284
|
-
43
|
|
285
|
-
end
|
|
286
|
-
assert_equal(42, a)
|
|
287
|
-
sleep 0.2
|
|
288
|
-
b = cache.get(:me)
|
|
289
|
-
assert_equal(43, b)
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
private
|
|
293
|
-
|
|
294
|
-
def rand
|
|
295
|
-
SecureRandom.uuid
|
|
296
|
-
end
|
|
297
|
-
end
|